TP 6 — Le niveau de batterie ============================ Objectifs --------- - comprendre un **pont diviseur** et le dimensionner ; - configurer le **convertisseur analogique-numérique** (ADC) du STM32H750, l'étalonner et lire une tension ; - passer de la tension de la batterie à un **pourcentage de charge**, et l'afficher sur les quatre LED de façade. Durée indicative : une séance de 3 h. Prérequis : :doc:`tp01_gpio`. Ce qu'il faut savoir -------------------- La batterie ~~~~~~~~~~~ Le robot fonctionne sur une batterie **LiPo 3S** : trois éléments en série. Un élément vaut **4,2 V** chargé à fond, environ **3,7 V** à mi-charge, et ne doit pas descendre sous **3,2 V** : en dessous, il s'abîme de façon irréversible. Pour la batterie entière : .. list-table:: :header-rows: 1 * - État - Par élément - Batterie 3S * - chargée à fond - 4,2 V - 12,6 V * - nominale - 3,7 V - 11,1 V * - seuil critique - 3,2 V - 9,6 V Le pont diviseur ~~~~~~~~~~~~~~~~ Une broche du microcontrôleur ne supporte pas plus de 3,3 V : la tension de la batterie, jusqu'à 12,6 V, ne peut pas y arriver directement. Un **pont diviseur** de deux résistances la réduit dans un rapport fixe : .. tikz:: Le pont diviseur ramène la tension de la batterie sous 3,3 V : rapport 1/4. \draw (0,0) node[left, align=right]{batterie\\[-2pt]\scriptsize 9,6 à 12,6 V} to[short, o-] (0.4,0) to[R, l={R1 = $30\,\mathrm{k}\Omega$}] (3,0) coordinate (m) to[R, l={R2 = $10\,\mathrm{k}\Omega$}] (3,-2.6) node[ground]{}; \draw (m) to[short, *-] (6,0) coordinate (c) to[C, l={$100\,\mathrm{nF}$}, *-] (6,-2.6) node[ground]{}; \draw (c) to[short, -o] (7.4,0) node[right, align=left]{PB0\\[-2pt]\scriptsize ADC2, voie 9}; \node[font=\small, align=left, anchor=north west] at (0.2,-3.3) {$V_{\mathrm{PB0}} = V_{\mathrm{batterie}} \times \dfrac{R2}{R1 + R2} = \dfrac{V_{\mathrm{batterie}}}{4}$ \quad (12,6 V $\rightarrow$ 3,15 V)}; À 12,6 V de batterie, la broche reçoit 3,15 V : sous les 3,3 V, avec un peu de marge. Un condensateur de 100 nF entre PB0 et la masse filtre le bruit. .. warning:: **Le schéma de la carte est faux sur ce point.** Il indique R6 = 10 kΩ en série et R7 = 30 kΩ vers la masse, c'est-à-dire un rapport de 0,75. Les résistances **montées** sont permutées : 30 kΩ en série, 10 kΩ vers la masse, rapport de **0,25**. Une carte refaite d'après le schéma recevrait 9 V sur PB0, et la broche serait détruite. C'est une bonne raison de **toujours mesurer** avant de faire confiance à un schéma. .. admonition:: Question 1. Avec le rapport 0,75 du schéma, quelle tension arriverait sur PB0 pour une batterie à 12 V ? Pourquoi est-ce dangereux ? .. dropdown:: Réponse à la question 1 :icon: light-bulb :color: info **1. Avec le rapport du schéma.** 12 V × 0,75 = **9 V** sur PB0, près de trois fois la tension d'alimentation. Une broche utilisée en entrée analogique n'accepte pas plus que sa tension d'alimentation (3,3 V), à quelques dixièmes de volt près : c'est une **valeur maximale absolue** de la fiche technique du STM32H750 (tableau *Absolute maximum ratings*). Au-delà, les **diodes de protection** internes de la broche, reliées à l'alimentation, se mettent à conduire, et la batterie injecte du courant **dans le 3,3 V** de toute la carte, à travers la résistance en série. Au mieux, toutes les mesures de l'ADC sont faussées ; au pire, avec une batterie pleine, des parasites ou une résistance plus faible, la broche ou le microcontrôleur est détruit. La leçon vaut pour toute carte : un pont diviseur se **vérifie à l'ohmmètre, carte hors tension**, avant la première mise sous tension, et on calcule toujours le cas le plus défavorable (la batterie à **pleine charge**, 12,6 V, pas la tension nominale). Le convertisseur analogique-numérique ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ L'ADC du STM32H750 convertit une tension entre 0 et 3,3 V en un nombre sur **16 bits**, de 0 à 65 535 : .. code-block:: text V_PB0 (mV) = valeur × 3300 / 65535 V_batterie (mV) = V_PB0 × 4 Trois points à ne pas oublier : - **l'étalonnage** : l'ADC du H7 doit être étalonné une fois au démarrage (``HAL_ADCEx_Calibration_Start``), sinon ses mesures sont décalées ; - **le temps d'échantillonnage** : l'ADC charge un petit condensateur interne à travers le pont diviseur. Vu de la broche, le pont fait 30 kΩ en parallèle avec 10 kΩ, soit 7,5 kΩ : il faut laisser au condensateur le temps de se charger. Le réglage par défaut de CubeMX (1,5 cycle) est **beaucoup trop court** ; - **l'horloge** de l'ADC, à choisir dans CubeMX (voir l'étape 1). Étape 1 — Configurer l'ADC -------------------------- #. Créez un projet ``tp06_batterie`` comme au TP 1 (STM32H750VBTx, **SYS → Debug : Serial Wire**, horloge par défaut), avec **LED_1** à **LED_4** en sortie. #. Cliquez sur **PB0** et choisissez **ADC2_INP9**. Dans **Analog → ADC2**, la voie **IN9** passe en **IN9 Single-ended**. #. Dans **Parameter Settings** de l'ADC2 : **Resolution** sur **ADC 16-bit resolution** ; dans **ADC_Regular_ConversionMode → Rank 1**, **Sampling Time** sur **64.5 Cycles**. #. **L'horloge de l'ADC.** Dans l'onglet **Clock Configuration**, le multiplexeur **ADC Clock Mux** propose plusieurs sources. Les PLL ne sont pas actives : choisissez **PER_CK** (l'horloge périphérique, parfois notée CKPER, qui vaut ici la HSI à 64 MHz). Dans **Parameter Settings** de l'ADC2, réglez **Clock Prescaler** sur **Asynchronous clock mode divided by 4** : l'ADC tourne à 16 MHz. #. Générez le code. .. Capture d'écran à insérer : CubeMX, Clock Configuration : ADC Clock Mux sur PER_CK. Déposez _static/captures/sans_os/tp06-adc-clock.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp06-adc-clock.png .. :align: center .. :width: 90% .. Capture d'écran à insérer : CubeMX, ADC2 : IN9 Single-ended, résolution 16 bits, Sampling Time 64.5 Cycles. Déposez _static/captures/sans_os/tp06-adc2.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp06-adc2.png .. :align: center .. :width: 90% .. admonition:: Question 2. Avec une horloge d'ADC à 16 MHz, combien de temps dure un échantillonnage de 64,5 cycles ? Est-ce suffisant si le condensateur a besoin d'environ 0,8 µs pour se charger à travers 7,5 kΩ ? .. dropdown:: Réponse à la question 2 :icon: light-bulb :color: info **2. Le temps d'échantillonnage.** .. code-block:: text 64,5 cycles / 16 MHz = 4,03 µs soit environ **cinq fois** les 0,8 µs nécessaires : largement suffisant. Le réglage par défaut, 1,5 cycle, ne donnerait que 0,09 µs, dix fois trop peu. **D'où vient le besoin.** Pendant l'échantillonnage, l'ADC relie la broche à un petit condensateur interne (quelques picofarads), qui doit se charger jusqu'à la tension de la broche. À travers une résistance *R*, un condensateur se charge en exponentielle, de constante de temps τ = *R* × *C*. Pour une erreur inférieure au demi-pas d'un ADC de 16 bits, il faut ln(2 × 65 536) ≈ 12 constantes de temps : c'est de là que viennent les 0,8 µs. Un échantillonnage trop court donne une mesure **trop basse**, et qui dépend de la voie mesurée juste avant. **Le rôle du condensateur de 100 nF.** Il est vingt mille fois plus gros que celui de l'ADC : au moment de l'échantillonnage, il fournit la charge presque sans perdre de tension. Il se recharge ensuite à travers le pont (τ = 7,5 kΩ × 100 nF = 0,75 ms) : on ne doit donc pas mesurer plus de quelques centaines de fois par seconde, ce qui ne pose aucun problème pour une batterie. Étape 2 — Lire la tension ------------------------- #. Dans ``USER CODE BEGIN 2``, étalonnez l'ADC une fois : .. code-block:: c HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); #. Dans la boucle, toutes les 500 ms, faites une conversion et lisez le résultat : .. code-block:: c HAL_ADC_Start(&hadc2); HAL_ADC_PollForConversion(&hadc2, 10); uint32_t valeur = HAL_ADC_GetValue(&hadc2); HAL_ADC_Stop(&hadc2); #. Calculez la tension de la batterie en millivolts, avec des entiers (pas de ``float``). Regardez le résultat dans le débogueur (**Run → Debug**, puis la vue **Live Expressions**), ou attendez le TP sur la liaison série pour l'afficher sur le PC. .. Capture d'écran à insérer : CubeIDE en débogage, vue Live Expressions avec g_adc_raw et g_vbat_mV. Déposez _static/captures/sans_os/tp06-live-expressions.png puis décommentez le bloc ci-dessous (retirez le préfixe « .. » devant chaque ligne) : .. .. image:: ../_static/captures/sans_os/tp06-live-expressions.png .. :align: center .. :width: 90% .. admonition:: Questions 3. Comparez votre mesure à celle d'un multimètre, aux bornes de la batterie. Quel est l'écart, en millivolts et en pourcentage ? 4. Faites dix mesures de suite sans rien changer : sont-elles toutes identiques ? De combien varient-elles ? Proposez un moyen de réduire cette variation. .. dropdown:: Solution de l'étape 2 :icon: code :color: secondary Dans ``USER CODE BEGIN 2`` : .. code-block:: c HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); Les fonctions (``USER CODE BEGIN 0``) : .. code-block:: c #define VREF_MV 3300u #define ADC_FULL 65535u #define DIVIDER 4u /* (30 k + 10 k) / 10 k */ static uint32_t adc_read(void) { uint32_t v = 0u; HAL_ADC_Start(&hadc2); /* lance UNE conversion */ if (HAL_ADC_PollForConversion(&hadc2, 10u) == HAL_OK) { v = HAL_ADC_GetValue(&hadc2); /* lit le registre DR */ } HAL_ADC_Stop(&hadc2); return v; } static uint32_t vbat_mV(uint32_t raw) { return (raw * VREF_MV * DIVIDER) / ADC_FULL; } et dans la boucle, avec des variables globales ``volatile`` pour les voir dans **Live Expressions** : .. code-block:: c g_adc_raw = adc_read(); g_vbat_mV = vbat_mV(g_adc_raw); HAL_Delay(500); **L'étalonnage.** L'ADC du H7 a un petit décalage (*offset*) propre à chaque puce. ``HAL_ADCEx_Calibration_Start`` le mesure, en convertissant une tension interne connue, et range la correction dans un registre de l'ADC, appliquée ensuite à chaque conversion. Il se fait **une fois**, ADC arrêté, au démarrage. **Le calcul en entiers.** La formule multiplie d'abord, puis divise : la plus grande valeur intermédiaire vaut 65 535 × 3 300 × 4 = 865 millions, qui tient dans un ``uint32_t`` (jusqu'à 4,29 milliards). Diviser d'abord (``raw / 65535``) donnerait 0 ou 1 : toute la précision serait perdue. Les **millivolts** permettent de garder la précision sans nombre à virgule : plus rapide, et sans dépendre de la bibliothèque des flottants. .. dropdown:: Réponses aux questions 3 et 4 :icon: light-bulb :color: info **3. L'écart au multimètre.** À la mise en service des cartes, l'écart mesuré allait de −0,1 % à +0,25 %. Trois causes se cumulent : - les **résistances** à 1 % : le rapport du pont peut varier d'environ ±1,5 % dans le pire cas ; - la **référence** : la formule suppose que l'ADC compte jusqu'à exactement 3 300 mV, mais sa référence est l'alimentation 3,3 V de la carte, précise à 1 ou 2 % ; - l'**ADC lui-même** (non-linéarité, décalage résiduel), négligeable ici devant les deux autres. Pour faire mieux, on **étalonne la carte** : on mesure une fois au multimètre, et on corrige le rapport dans le programme. C'est ce que fait le firmware du robot, qui garde son coefficient dans sa configuration. **4. Dix mesures.** Elles ne sont pas identiques : les derniers bits varient de quelques unités, voire quelques dizaines (soit quelques millivolts sur la batterie). C'est le **bruit** : celui des résistances, de l'alimentation, du processeur qui commute à côté. Un ADC de 16 bits a un pas de 50 µV : il voit tout. Pour le réduire : faire **la moyenne** de *N* mesures (le bruit diminue comme √N : 16 mesures le divisent par 4) ; ou utiliser le **suréchantillonnage matériel** de l'ADC du H7 (*Oversampling*, dans CubeMX), qui fait la même chose sans le processeur. Pour une batterie qui change en minutes, une moyenne sur une seconde est sans inconvénient. Étape 3 — Le pourcentage et la jauge ------------------------------------ #. Convertissez la tension en un **pourcentage**, de 0 % à 9,6 V à 100 % à 12,6 V, en ligne droite, borné entre 0 et 100. #. Affichez-le sur les quatre LED, comme une jauge : une LED par tranche de 25 %. #. Sous **20 %**, faites clignoter LED_1 seule, pour signaler qu'il faut recharger. .. admonition:: Questions 5. La tension d'une LiPo ne baisse pas en ligne droite pendant la décharge : elle reste longtemps vers 3,7–3,8 V par élément, puis chute vite à la fin. Qu'est-ce que cela implique pour votre jauge ? Comment l'améliorer ? 6. Quand le robot roule, ses moteurs tirent du courant, et la tension mesurée baisse un peu. Pourquoi ? Comment éviter que la jauge saute d'une LED à l'autre ? .. dropdown:: Solution de l'étape 3 :icon: code :color: secondary .. code-block:: c #define VBAT_EMPTY_MV 9600u /* 3 × 3,2 V : seuil critique */ #define VBAT_FULL_MV 12600u /* 3 × 4,2 V : pleine charge */ static uint32_t percent(uint32_t mv) { if (mv <= VBAT_EMPTY_MV) { return 0u; } /* bornes d'abord */ if (mv >= VBAT_FULL_MV) { return 100u; } return ((mv - VBAT_EMPTY_MV) * 100u) / (VBAT_FULL_MV - VBAT_EMPTY_MV); } /* n LED allumées : une par tranche de 25 % entamée. */ static void gauge(uint32_t pct) { uint32_t n = (pct + 24u) / 25u; /* 0 → 0 ; 1..25 → 1 ; … ; 76..100 → 4 */ for (uint32_t k = 0u; k < 4u; k++) { HAL_GPIO_WritePin(leds[k].port, leds[k].pin, (k < n) ? GPIO_PIN_SET : GPIO_PIN_RESET); } } Dans la boucle : .. code-block:: c static uint32_t blink = 0u; g_pct = percent(vbat_mV(adc_read())); if (g_pct < 20u) { gauge(0u); /* tout éteint… */ blink ^= 1u; /* …sauf LED_1, qui clignote */ HAL_GPIO_WritePin(LED_1_GPIO_Port, LED_1_Pin, blink ? GPIO_PIN_SET : GPIO_PIN_RESET); } else { gauge(g_pct); } HAL_Delay(500); **Les bornes testées en premier.** Sans elles, une tension sous 9,6 V donnerait ``mv − 9600`` négatif dans un entier **non signé** : le calcul « boucle » sur une valeur énorme, et la jauge afficherait 100 % sur une batterie vide. **L'arrondi vers le haut.** ``(pct + 24) / 25`` arrondit au-dessus : dès 1 %, une LED reste allumée, et 0 LED veut dire vraiment vide. ``pct / 25`` éteindrait tout jusqu'à 24 %. .. dropdown:: Réponses aux questions 5 et 6 :icon: light-bulb :color: info **5. La courbe de décharge.** La tension d'une LiPo chute vite au début (de 4,2 à 3,9 V par élément), reste ensuite longtemps sur un **plateau** (3,9 à 3,7 V), puis s'effondre à la fin (sous 3,6 V). Une jauge en ligne droite entre 9,6 et 12,6 V affiche donc 90 % → 60 % très vite, **stagne** longtemps vers 50–60 %, puis passe de 30 % à 0 en quelques minutes : elle rassure à tort au moment où il faudrait s'inquiéter. Pour l'améliorer, on remplace la droite par une **table** de quelques points (tension → pourcentage), relevée sur une vraie décharge, et on interpole entre deux points. Mieux encore, on mesure le **courant** et on compte les ampères-heures consommés (coulométrie) : c'est ce que font les jauges des téléphones. **6. La tension qui baisse en roulant.** Une batterie se comporte comme une source de tension parfaite *E* en série avec une **résistance interne** *r* (plus celle des fils et du connecteur). Sous un courant *I*, la tension mesurée vaut *U* = *E* − *r* × *I* : avec *r* = 50 mΩ et 2 A pour les moteurs, elle baisse de 0,1 V — un écart de plus de 3 % sur la jauge, qui revient quand le robot s'arrête. Pour une jauge stable : - **filtrer** : moyenne sur plusieurs secondes, qui gomme les pointes de courant ; - ajouter une **hystérésis** (comme le thermostat du TP 4) : ne changer le nombre de LED que si le pourcentage dépasse le seuil d'une marge, par exemple 3 % ; - n'accepter que les **baisses** pendant que le robot roule : une batterie ne se recharge pas toute seule. .. dropdown:: Tout le code de la correction :icon: download :color: secondary :download:`main_user_code.c <../../corrections/sans_os/tp06/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. - **Moyenne glissante** : moyennez les 16 dernières mesures, et comparez la stabilité avec et sans. - **Hystérésis** : la jauge ne change de LED que si la tension a franchi le seuil de plus de 50 mV, pour ne pas clignoter à la limite. - **Courbe réelle** : remplacez la ligne droite par une table de correspondance tension → pourcentage, tirée de la fiche technique d'une batterie LiPo. - **Alarme** : sous le seuil critique de 9,6 V, ajoutez un bip du buzzer actif (voir :doc:`tp03_son`). - **Étalonnage** : mesurez trois tensions au multimètre (alimentation de laboratoire réglable), calculez la droite qui relie la mesure de l'ADC à la vraie tension, et corrigez vos mesures. Après le TP ----------- Pour rendre la carte au robot, voir :ref:`etat-ratbot`.