TP 3 — Le son ============= Objectifs --------- - commander un **buzzer actif** par une simple sortie ; - produire une **note** de fréquence choisie avec un timer, sur un **buzzer passif** ; - jouer une mélodie, en changeant la fréquence du timer pendant qu'il tourne. Durée indicative : une séance de 3 h. Prérequis : :doc:`tp02_pwm` (réglage d'un timer en PWM). Ce qu'il faut savoir -------------------- Deux buzzers ~~~~~~~~~~~~ La carte porte deux buzzers, tous deux alimentés en +5 V et commutés à la masse par un transistor MOSFET, comme le voyant RGB du TP 2 : .. list-table:: :header-rows: 1 :widths: 20 20 60 * - Buzzer - Broche - Fonctionnement * - **actif** (BUZZER1) - PC13 - contient son propre oscillateur : il suffit de l'alimenter pour qu'il sonne, toujours à la même fréquence * - **passif** (BUZZER2) - PB9 (TIM17_CH1) - une simple membrane : il reproduit le signal qu'on lui envoie. Un signal carré à 440 Hz donne un la Les deux commandes sont **actives à l'état haut**, et une résistance de 10 kΩ sur chaque grille garde les buzzers muets au démarrage. .. warning:: **PC13 est une broche à courant limité** sur le STM32H750 (elle dépend du domaine de sauvegarde) : quelques milliampères au plus. Ici, elle ne commande que la grille d'un transistor, ce qui est sans risque. N'y branchez jamais directement une LED ou une autre charge. Une note avec un timer ~~~~~~~~~~~~~~~~~~~~~~ Une note de fréquence *f* est un signal carré de période 1/*f*. On réutilise le PWM du TP 2, mais en réglant cette fois la **période** du timer, et en gardant un rapport cyclique de 50 % : .. code-block:: text prédiviseur PSC = 63 -> compteur à 64 MHz / 64 = 1 MHz (1 pas = 1 µs) période ARR = 1 000 000 / f − 1 comparaison CCR = (ARR + 1) / 2 (rapport cyclique 50 %) Pour un la à 440 Hz : ARR = 2 272, CCR = 1 136. Pour faire silence sans arrêter le timer : CCR = 0. Fréquences de la gamme (octave du la 440 Hz) : .. list-table:: :header-rows: 1 * - do - ré - mi - fa - sol - la - si - do (aigu) * - 262 Hz - 294 Hz - 330 Hz - 349 Hz - 392 Hz - 440 Hz - 494 Hz - 523 Hz Étape 1 — Le buzzer actif ------------------------- #. Créez un projet ``tp03_son`` comme au TP 1 (STM32H750VBTx, **SYS → Debug : Serial Wire**, horloge par défaut). #. Configurez **PC13** en **GPIO_Output**, label ``BUZZER_ACTIF``. #. Faites trois bips courts (100 ms de son, 100 ms de silence), une pause d'une seconde, puis recommencez. .. admonition:: Questions 1. Peut-on changer la hauteur du son du buzzer actif ? Pourquoi ? 2. Codez le mot **SOS** en morse (trois courts, trois longs, trois courts). Quelle durée choisir pour un signal long ? .. dropdown:: Solution de l'étape 1 :icon: code :color: secondary Une petite fonction évite de répéter quatre lignes par bip (``USER CODE BEGIN 0``) : .. code-block:: c static void bip(uint32_t on_ms, uint32_t off_ms) { HAL_GPIO_WritePin(BUZZER_ACTIF_GPIO_Port, BUZZER_ACTIF_Pin, GPIO_PIN_SET); HAL_Delay(on_ms); HAL_GPIO_WritePin(BUZZER_ACTIF_GPIO_Port, BUZZER_ACTIF_Pin, GPIO_PIN_RESET); HAL_Delay(off_ms); } Dans la boucle : .. code-block:: c bip(100, 100); bip(100, 100); bip(100, 100); HAL_Delay(1000); La broche PC13 commande la grille du transistor : à 1, le transistor relie le buzzer à la masse, et le buzzer reçoit ses 5 V. La broche ne fournit presque aucun courant (la grille d'un MOSFET est un condensateur de quelques centaines de picofarads, chargé en une fraction de microseconde) : c'est ce qui rend l'usage de PC13, broche à courant limité, sans danger ici. .. dropdown:: Réponses aux questions 1 et 2 :icon: light-bulb :color: info **1. La hauteur du son.** Non. Le buzzer actif contient un **oscillateur** (un petit circuit et une pastille piézoélectrique) qui vibre toujours à sa fréquence propre, typiquement 2 à 4 kHz. La broche ne fait que l'**alimenter ou non** : elle décide **quand** il sonne, pas **comment**. C'est un avantage (un seul bit suffit, sans timer) et sa limite (une seule note). **2. SOS.** En morse, la **durée de référence** est celle du point (signal court) ; un trait (signal long) dure **trois points**, l'espace entre deux signaux d'une lettre un point, entre deux lettres trois points : .. code-block:: c for (uint32_t k = 0u; k < 3u; k++) { bip(100, 100); } /* S : ... */ HAL_Delay(200); /* + 200 = 3 points */ for (uint32_t k = 0u; k < 3u; k++) { bip(300, 100); } /* O : --- */ HAL_Delay(200); for (uint32_t k = 0u; k < 3u; k++) { bip(100, 100); } /* S : ... */ HAL_Delay(1000); Avec un point de 100 ms, le trait dure **300 ms**. Les espaces respectent le rythme du morse : sans eux, « SOS » ressemble à une suite de neuf signaux qu'on ne sait pas découper. Étape 2 — Une note sur le buzzer passif --------------------------------------- #. Dans CubeMX, cliquez sur **PB9** et choisissez **TIM17_CH1**. #. Dans **Timers → TIM17**, cochez **Activated**, et réglez **Channel1** sur **PWM Generation CH1**. **Prescaler** = 63, **Counter Period** = 2272, **Pulse** = 1136. #. Générez le code, puis démarrez le PWM dans ``USER CODE BEGIN 2`` : .. Capture d'écran à insérer : CubeMX, TIM17 : Channel1 en PWM Generation CH1, Prescaler 63, Counter Period 2272, Pulse 1136. Déposez _static/captures/sans_os/tp03-tim17.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp03-tim17.png .. :align: center .. :width: 90% .. code-block:: c HAL_TIM_PWM_Start(&htim17, TIM_CHANNEL_1); Le buzzer doit jouer un la, sans s'arrêter. .. note:: ``HAL_TIM_PWM_Start`` est indispensable, même si tous les registres sont déjà réglés : TIM17 a une sortie protégée qui reste coupée tant que son bit d'activation (**MOE**) n'est pas mis, et c'est cette fonction qui le met. .. admonition:: Question 3. Vérifiez par le calcul que les réglages donnent bien 440 Hz. .. dropdown:: Solution de l'étape 2 et réponse à la question 3 :icon: light-bulb :color: info Il n'y a rien d'autre à écrire que ``HAL_TIM_PWM_Start(&htim17, TIM_CHANNEL_1);`` : tout le reste est réglé dans CubeMX, et le timer produit seul le signal. **3. Le calcul.** L'horloge de TIM17 est à 64 MHz : .. code-block:: text compteur : 64 000 000 / (PSC + 1) = 64 000 000 / 64 = 1 000 000 Hz (1 pas = 1 µs) période : ARR + 1 = 2 273 pas = 2 273 µs fréquence : 1 000 000 / 2 273 = 439,96 Hz rapport : CCR / (ARR + 1) = 1 136 / 2 273 = 49,98 % L'écart à 440 Hz est de 0,04 Hz, soit 0,0001 %, ou 0,15 cent (un cent est le centième d'un demi-ton) : bien en dessous de ce que l'oreille perçoit (quelques cents). En revanche, l'oscillateur HSI lui-même n'est précis qu'à environ 1 % près (réglé en usine, variable avec la température) : c'est lui qui limite la justesse, de l'ordre de 17 cents, pas le calcul. **Pourquoi 50 % ?** Un signal carré symétrique ne contient que des harmoniques impairs (3*f*, 5*f*…) : son timbre est le plus « pur » qu'un signal carré puisse donner. Un rapport cyclique différent change le timbre (plus nasillard), pas la hauteur. Étape 3 — Changer de note ------------------------- Écrivez une fonction ``note(uint32_t f)`` qui fait jouer la fréquence *f*, et ``silence()`` qui coupe le son. Le timer peut changer de période pendant qu'il tourne : .. code-block:: c __HAL_TIM_SET_AUTORELOAD(&htim17, arr); /* nouvelle période */ __HAL_TIM_SET_COMPARE(&htim17, TIM_CHANNEL_1, ccr); /* nouvelle comparaison */ Jouez la gamme de do à do, une note toutes les 300 ms. .. admonition:: Question 4. Quelle est la plus haute fréquence que ce réglage peut produire ? Et la plus basse ? (Le compteur de TIM17 est sur 16 bits.) .. dropdown:: Solution de l'étape 3 :icon: code :color: secondary .. code-block:: c /* Compteur à 1 MHz (PSC = 63) : ARR = 1 000 000 / f − 1 ; CCR à la moitié. */ static void note(uint32_t f) { uint32_t arr = (1000000u / f) - 1u; __HAL_TIM_SET_AUTORELOAD(&htim17, arr); __HAL_TIM_SET_COMPARE(&htim17, TIM_CHANNEL_1, (arr + 1u) / 2u); } static void silence(void) { __HAL_TIM_SET_COMPARE(&htim17, TIM_CHANNEL_1, 0u); /* sortie toujours à 0 */ } et la gamme : .. code-block:: c static const uint16_t gamme[8] = { 262, 294, 330, 349, 392, 440, 494, 523 }; for (uint32_t k = 0u; k < 8u; k++) { note(gamme[k]); HAL_Delay(300); } silence(); HAL_Delay(1000); **Le silence par CCR = 0.** Avec une comparaison nulle, le compteur n'est jamais sous CCR : la sortie reste à 0, le transistor bloqué, la membrane au repos. Le timer continue de tourner, prêt pour la note suivante. Couper le timer (``HAL_TIM_PWM_Stop``) marcherait aussi, mais la sortie pourrait s'arrêter à 1 et laisser le buzzer alimenté en continu. **Changer la période en marche : un piège.** Si le compteur est déjà à 2 000 quand on écrit ARR = 1 910 (passage du la au si), il ne rencontre plus jamais la valeur d'ARR : il continue jusqu'à 65 535 avant de repartir de 0, ce qui donne une période parasite de 65 ms — un petit « clic » entre deux notes. Le remède est de cocher **auto-reload preload** (*Enable*) dans les paramètres de TIM17 : la nouvelle valeur n'est alors prise en compte qu'à la fin de la période en cours (bit ``ARPE`` du registre ``CR1``). .. dropdown:: Réponse à la question 4 :icon: light-bulb :color: info **4. Les limites.** Le compteur avance à 1 MHz, et ARR est sur 16 bits : - **la plus basse** : ARR = 65 535, soit une période de 65 536 µs, et *f* = 1 000 000 / 65 536 ≈ **15,3 Hz**, sous le seuil de l'audition (20 Hz) ; - **la plus haute** : ARR = 1 (deux pas : un à 1, un à 0), soit *f* = 1 000 000 / 2 = **500 kHz**, bien au-delà de l'audition (≈ 20 kHz) et de ce que la membrane peut suivre. La **résolution** se dégrade dans les aigus : ARR est entier, et vers 5 kHz (ARR ≈ 199), passer d'ARR = 199 à 198 change la fréquence de 25 Hz, soit 9 cents. Pour descendre plus bas que 15 Hz ou être plus fin dans les aigus, on change le prédiviseur : c'est le compromis permanent des timers entre **étendue** et **résolution**. Étape 4 — Une mélodie --------------------- Rangez une mélodie dans un tableau de notes, chacune avec sa fréquence et sa durée, et jouez-la. Une fréquence nulle code un silence : .. code-block:: c typedef struct { uint16_t freq_hz; uint16_t duree_ms; } note_t; static const note_t melodie[] = { { 262, 300 }, { 262, 300 }, { 262, 300 }, { 294, 300 }, { 330, 600 }, { 294, 600 }, /* … */ }; Laissez un court silence (20 à 30 ms) entre deux notes : sans lui, deux notes identiques qui se suivent se confondent en une seule. .. dropdown:: Solution de l'étape 4 :icon: code :color: secondary La mélodie (« Au clair de la lune », première phrase) dans ``USER CODE BEGIN PV`` : .. code-block:: c typedef struct { uint16_t freq_hz; uint16_t duree_ms; } note_t; static const note_t melodie[] = { { 262, 300 }, { 262, 300 }, { 262, 300 }, { 294, 300 }, { 330, 600 }, { 294, 600 }, { 262, 300 }, { 330, 300 }, { 294, 300 }, { 294, 300 }, { 262, 900 }, { 0, 600 }, }; et la boucle qui la joue : .. code-block:: c for (uint32_t k = 0u; k < (sizeof(melodie) / sizeof(melodie[0])); k++) { if (melodie[k].freq_hz == 0u) { silence(); } else { note(melodie[k].freq_hz); } HAL_Delay(melodie[k].duree_ms - 25u); /* la note… */ silence(); HAL_Delay(25); /* …puis 25 ms de silence */ } **sizeof(melodie) / sizeof(melodie[0])** donne le nombre d'éléments du tableau, calculé par le compilateur : on peut allonger la mélodie sans toucher à la boucle. **const.** Le tableau ne change jamais : ``const`` permet au compilateur de le laisser en **flash**, sans le recopier en RAM au démarrage. Sur un microcontrôleur, où la RAM est rare, toutes les tables fixes devraient être ``const``. **La durée totale est conservée** : on retire les 25 ms de silence de la durée de chaque note, pour que le rythme reste juste. .. dropdown:: Tout le code de la correction :icon: download :color: secondary :download:`main_user_code.c <../../corrections/sans_os/tp03/main_user_code.c>` — compilé sans avertissement contre la HAL du STM32H7. Ce n'est **pas** un ``main.c`` complet : ce sont les extraits à placer dans les blocs ``USER CODE`` du ``main.c`` généré par CubeMX, chacun sous le commentaire qui indique son bloc. La boucle ``while (1)`` contient une variante par étape, encadrée par ``#if 0`` / ``#if 1`` : n'en activer **qu'une à la fois**. La configuration CubeMX attendue est rappelée en tête du fichier, et les réponses aux questions à la fin. Exercices supplémentaires ------------------------- Ces exercices n'ont pas de correction. - **Sirène** : une fréquence qui monte de 500 à 1 500 Hz puis redescend, en continu, comme une sirène de pompiers. - **Piano** : chacun des quatre boutons SW1 à SW4 joue une note différente tant qu'il est enfoncé. - **Volume** : le buzzer passif joue plus ou moins fort selon le rapport cyclique. Essayez 50 %, 10 %, 2 %. Qu'entend-on ? Est-ce un vrai réglage de volume ? - **Sans bloquer** : jouez la mélodie sans ``HAL_Delay``, à l'aide de ``HAL_GetTick()``, pour que les LED puissent clignoter en même temps. Après le TP ----------- Pour rendre la carte au robot, voir :ref:`etat-ratbot`.