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 : TP 2 — PWM et couleurs (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 :
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.
Avertissement
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 % :
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) :
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_soncomme 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.
Questions
Peut-on changer la hauteur du son du buzzer actif ? Pourquoi ?
Codez le mot SOS en morse (trois courts, trois longs, trois courts). Quelle durée choisir pour un signal long ?
Solution de l’étape 1
Une petite fonction évite de répéter quatre lignes par bip
(USER CODE BEGIN 0) :
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 :
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.
Réponses aux questions 1 et 2
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 :
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: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.
Question
Vérifiez par le calcul que les réglages donnent bien 440 Hz.
Solution de l’étape 2 et réponse à la question 3
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 :
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 :
__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.
Question
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.)
Solution de l’étape 3
/* 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 :
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).
Réponse à la question 4
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 :
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.
Solution de l’étape 4
La mélodie (« Au clair de la lune », première phrase) dans
USER CODE BEGIN PV :
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 :
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.
Tout le code de la correction
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 deHAL_GetTick(), pour que les LED puissent clignoter en même temps.
Après le TP
Pour rendre la carte au robot, voir Remettre la carte en état RatBot.