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 : TP 1 — Allumer une LED, lire un bouton.

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 :

É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 :

Figure made with TikZ

Le pont diviseur ramène la tension de la batterie sous 3,3 V : rapport 1/4.

À 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.

Avertissement

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.

Question

  1. Avec le rapport 0,75 du schéma, quelle tension arriverait sur PB0 pour une batterie à 12 V ? Pourquoi est-ce dangereux ?

Réponse à la question 1

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 :

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

  1. 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.

  2. Cliquez sur PB0 et choisissez ADC2_INP9. Dans Analog → ADC2, la voie IN9 passe en IN9 Single-ended.

  3. Dans Parameter Settings de l’ADC2 : Resolution sur ADC 16-bit resolution ; dans ADC_Regular_ConversionMode → Rank 1, Sampling Time sur 64.5 Cycles.

  4. 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.

  5. Générez le code.

Question

  1. 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Ω ?

Réponse à la question 2

2. Le temps d’échantillonnage.

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

  1. Dans USER CODE BEGIN 2, étalonnez l’ADC une fois :

    HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
    
  2. Dans la boucle, toutes les 500 ms, faites une conversion et lisez le résultat :

    HAL_ADC_Start(&hadc2);
    HAL_ADC_PollForConversion(&hadc2, 10);
    uint32_t valeur = HAL_ADC_GetValue(&hadc2);
    HAL_ADC_Stop(&hadc2);
    
  3. 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.

Questions

  1. Comparez votre mesure à celle d’un multimètre, aux bornes de la batterie. Quel est l’écart, en millivolts et en pourcentage ?

  2. Faites dix mesures de suite sans rien changer : sont-elles toutes identiques ? De combien varient-elles ? Proposez un moyen de réduire cette variation.

Solution de l’étape 2

Dans USER CODE BEGIN 2 :

HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);

Les fonctions (USER CODE BEGIN 0) :

#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 :

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.

Réponses aux questions 3 et 4

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

  1. Convertissez la tension en un pourcentage, de 0 % à 9,6 V à 100 % à 12,6 V, en ligne droite, borné entre 0 et 100.

  2. Affichez-le sur les quatre LED, comme une jauge : une LED par tranche de 25 %.

  3. Sous 20 %, faites clignoter LED_1 seule, pour signaler qu’il faut recharger.

Questions

  1. 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 ?

  2. 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 ?

Solution de l’étape 3
#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 :

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 %.

Réponses aux questions 5 et 6

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.

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.

  • 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 TP 3 — Le 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 Remettre la carte en état RatBot.