// ADC · Convertisseur Analogique-Numérique Du Zéro à l'Expert
Architecture SAR, registres bas niveau, drivers HAL, DMA circulaire et 5 problèmes réels avec code C complet. Chaque bit expliqué. Zéro hypothèse.
Fondements de la Conversion Numérique
Quatre piliers théoriques universels — ils s'appliquent à tout ADC. Maîtrisez ces bases avant de toucher un registre.
① Échantillonnage
Un signal analogique est continu dans le temps. Un MCU ne peut pas traiter l'infini — il prélève des mesures à des instants discrets nTs.
Définitions fondamentales
| Symbole | Nom | Unité |
|---|---|---|
Ts | Période d'échantillonnage | secondes |
fs | Fréquence d'échantillonnage | Hz = 1/Ts |
x(t) | Signal analogique continu | Volts |
x[n] | x(n·Ts) — séquence discrète | n = 0,1,2… |
Physique de l'opération
À chaque instant nTs, un MOSFET interne se ferme quelques nanosecondes. La tension est capturée sur le condensateur interne (~4 pF). Le MOSFET se rouvre et la conversion commence.
Durée du S/H : 3 / 15 / 28 / 56 / 84 / 112 / 144 / 480 cycles d'horloge CAN.
② Blocage — Sample & Hold
La conversion SAR dure plusieurs cycles. Pendant ce temps, l'entrée ne doit pas changer. Le S&H gèle la tension grâce à un condensateur isolé.
Pour une thermistance 10 kΩ utiliser ADC_SAMPLETIME_480CYCLES. Pour un potentiomètre ≤ 1 kΩ, 3 cycles suffisent.
③ Théorème de Shannon-Nyquist
Trop lentement → repliement spectral (aliasing). Shannon démontre la condition minimale absolue pour une reconstruction parfaite.
Pour reconstruire parfaitement un signal de fréquence max f_max, la fréquence d'échantillonnage f_s doit être au moins le double.
| Application | f_max signal | fs minimum | fs pratique |
|---|---|---|---|
| 🌡️ Température NTC | ~0,1 Hz | 0,2 Hz | 1 Hz |
| 🔋 Tension batterie | ~10 Hz | 20 Hz | 100 Hz |
| 🕹️ Joystick | ~50 Hz | 100 Hz | 1 kHz |
| 🎙️ Son / voix | ~4 kHz | 8 kHz | 8–16 kHz |
| 🎵 Audio hi-fi | ~20 kHz | 40 kHz | 44,1 / 48 kHz |
| ⚡ Courant DC/DC | ~100 kHz | 200 kHz | 1 MHz+ |
④ Approximations Successives (SAR)
Comment transformer une tension en binaire ? Le SAR applique la recherche binaire. N bits = N cycles. MSB d'abord, LSB en dernier.
📊 Résolution & LSB
| Résolution | Niveaux | LSB @ 3,3V |
|---|---|---|
| 6 bits | 64 | 51,6 mV |
| 8 bits | 256 | 12,9 mV |
| 10 bits | 1 024 | 3,22 mV |
| 12 bits | 4 096 | 0,806 mV |
⏱️ Temps de Conversion
| S/H | @ 21 MHz | Débit max |
|---|---|---|
| 3 cycles | 738 ns | 1,35 MSPS |
| 84 cycles | 4,60 µs | 217 kSPS |
| 480 cycles | 23,4 µs | 42,7 kSPS |
📝 Exercices de Vérification
Quatre questions clés avec réponses directes — testez votre compréhension des bases.
Q1 — Théorème de Shannon
Capteur de vibrations, fréquence maximale 8 kHz. Fréquence d'échantillonnage minimale ?
Q2 — Résolution & LSB
CAN 12 bits, V_REF = 3,3 V, valeur brute = 1638. Tension d'entrée ?
Q3 — Cycles SAR
CAN SAR 12 bits — combien de cycles pour la conversion seule (sans S/H) ?
Q4 — Durée totale
APB2 = 84 MHz, prescaler /4 → f_ADC = 21 MHz, S/H = 84 cycles. Durée totale ?
Architecture du CAN STM32F4
Jusqu'à trois CAN 12 bits (ADC1/2/3), jusqu'à 19 canaux, capteurs internes. Horloge dérivée de l'APB2.
Architecture SAR
Recherche binaire : compare l'entrée bit par bit à une tension CNA interne. 12 cycles = résultat 12 bits. Jusqu'à 2,4 MSPS.
Horloge CAN
Dérivée de l'APB2 (max 84 MHz) divisée par 2, 4, 6 ou 8. Horloge CAN max = 36 MHz. Typiquement 84/4 = 21 MHz.
Trois instances
ADC1, ADC2, ADC3 — indépendants ou couplés. Seul ADC1 accède au capteur de température interne et à VREFINT.
Modes de conversion
- Unique : une conversion, puis stop
- Continu : boucle automatique
- Balayage : séquence de canaux
- Discontinu : N canaux / déclenchement
Toujours entre V_REF− (GND) et V_REF+. Sur Nucleo/Discovery : V_REF+ = VDD = 3,3 V. N'appliquez jamais plus de 3,3 V — destruction immédiate.
Registres Clés
Même avec HAL, comprendre les registres est fondamental. Chaque appel HAL finit par écrire dans ces bits.
ADC_CR1 — Contrôle 1
ADC_CR2 — Contrôle 2
| Registre | Bit(s) | Description | HAL équivalent |
|---|---|---|---|
| ADC_CR1 | RES[1:0] | Résolution : 00=12b 01=10b 10=8b 11=6b | hadc.Init.Resolution |
| ADC_CR1 | SCAN | Mode balayage multi-canaux | hadc.Init.ScanConvMode |
| ADC_CR2 | ADON | Mise sous tension du CAN | HAL_ADC_Start() |
| ADC_CR2 | CONT | Mode continu automatique | hadc.Init.ContinuousConvMode |
| ADC_CR2 | DMA | Active requêtes DMA | HAL_ADC_Start_DMA() |
| ADC_CR2 | SWSTART | Déclenchement logiciel | HAL_ADC_Start() |
| ADC_DR | [11:0] | Registre de données (lecture efface EOC) | HAL_ADC_GetValue() |
Configuration CubeMX + HAL
Chaque option de configuration HAL expliquée — pourquoi elle existe et ce qu'elle fait au niveau registres.
Sélectionner le MCU
STM32CubeMX → "Start from MCU" → STM32F407VGTx → toolchain STM32CubeIDE.
Activer le périphérique ADC
Analogique → ADC1 → activer IN0 → PA0 passe automatiquement en mode Analogique.
Paramètres clés
Prescaler /4 (21 MHz), Résolution 12b, Alignement Droit, Balayage OFF, Mode Continu OFF.
Code généré par CubeMX
static void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; if (HAL_ADC_Init(&hadc1) != HAL_OK) Error_Handler(); sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) Error_Handler(); }
Mode Scrutation (Polling)
uint32_t adc_raw; float voltage; char buf[64]; while (1) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); adc_raw = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); voltage = ((float)adc_raw / 4095.0f) * 3.3f; sprintf(buf, "Raw: %lu | V: %.3f V\r\n", adc_raw, voltage); HAL_UART_Transmit(&huart2, (uint8_t*)buf, strlen(buf), 100); HAL_Delay(500); }
CAN avec DMA — Charge CPU Nulle
Le DMA transfère chaque résultat directement en RAM sans intervention CPU. Indispensable pour l'acquisition multi-canaux haute vitesse.
ADC1 → DMA2, Stream 0, Channel 0. Non configurable. Consultez le tableau "DMA2 request mapping" du Reference Manual RM0090.
/* Buffer GLOBAL — jamais sur la pile ! */ uint32_t dma_buf[4]; volatile uint8_t dma_ready = 0; HAL_ADC_Start_DMA(&hadc1, dma_buf, 4); while (1) { if (dma_ready) { dma_ready = 0; float ch0 = (dma_buf[0] / 4095.0f) * 3.3f; float ch1 = (dma_buf[1] / 4095.0f) * 3.3f; display_update(ch0, ch1); } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) dma_ready = 1; }
hadc1.Init.ScanConvMode = ENABLE; hadc1.Init.NbrOfConversion = 4; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DMAContinuousRequests = ENABLE; ADC_ChannelConfTypeDef sConfig; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); /* Capteur Température — 480 cycles obligatoires */ sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; sConfig.Rank = 4; sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); uint32_t buf[4]; HAL_ADC_Start_DMA(&hadc1, buf, 4);
5 Problèmes Réels
Situations terrain avec code C complet — toutes les solutions sont affichées directement.
Potentiomètre 10 kΩ entre 3,3 V et GND, curseur sur PA0. Lire en scrutation toutes les 500 ms, convertir en tension et pourcentage, envoyer via UART2 à 115 200 bps.
Start → PollForConversion → GetValue → Stopuint32_t raw; float v, pct; char buf[80]; while (1) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); raw = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); v = ((float)raw / 4095.0f) * 3.3f; pct = ((float)raw / 4095.0f) * 100.0f; sprintf(buf, "%.1f%% %.3f V\r\n", pct, v); HAL_UART_Transmit(&huart2, (uint8_t*)buf, strlen(buf), 100); HAL_Delay(500); }
STM32F407 intègre un capteur sur ADC1_IN16. Données usine : TS_CAL1 @ 0x1FFF7A2C (30°C), TS_CAL2 @ 0x1FFF7A2E (110°C). Afficher température °C chaque seconde.
Activer le bit TSVREFE (bit 23) dans ADC_CCR avant toute lecture. Désactivé par défaut.
float ReadTemperature(void) { ADC->CCR |= ADC_CCR_TSVREFE; ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); uint32_t raw = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); uint16_t *cal1 = (uint16_t*)0x1FFF7A2C; uint16_t *cal2 = (uint16_t*)0x1FFF7A2E; return (110.0f-30.0f) / ((float)(*cal2-*cal1)) * ((float)raw - *cal1) + 30.0f; }
LDR en diviseur de tension → PA1. Allumer LED sur PD12 si sombre (ADC > 3000), éteindre si clair (ADC < 1000). Implémenter une zone d'hystérésis.
#define THR_DARK 3000 #define THR_BRIGHT 1000 static uint8_t led = 0; HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); uint32_t raw = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); if (!led && raw > THR_DARK) { led = 1; HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET); } else if (led && raw < THR_BRIGHT) { led = 0; HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET); }
LiPo 1S : 3,0V–4,2V. Diviseur R1=R2=100kΩ → PA4. Suréchantillonnage ×16. Afficher tension réelle, SoC (0–100%), alarme si V < 3,2V.
#define N_OS 16 uint32_t sum = 0; for (int i=0; i<N_OS; i++) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); sum += HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); } float v_adc = ((float)(sum/N_OS) / 4095.0f) * 3.3f; float v_bat = v_adc * 2.0f; float soc = (v_bat - 3.0f) / (4.2f - 3.0f) * 100.0f; if(soc < 0) soc = 0; if(soc > 100) soc = 100; HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, v_bat < 3.2f ? GPIO_PIN_SET : GPIO_PIN_RESET);
Joystick : axe X sur PA0, axe Y sur PA1. DMA balayage continu. Normaliser −1,0 à +1,0. Zone morte ±150 pts autour du centre (2048). Sortie PWM moteur @ 50 Hz.
uint32_t joy[2]; #define CENTER 2048 #define DZ 150 float deadzone(uint32_t raw) { int32_t e = (int32_t)raw - CENTER; if (e > -DZ && e < DZ) return 0.0f; float v = ((float)e - (e > 0 ? DZ : -DZ)) / (float)(CENTER - DZ); return v > 1.0f ? 1.0f : v < -1.0f ? -1.0f : v; } HAL_ADC_Start_DMA(&hadc1, joy, 2); while(1) { float x = deadzone(joy[0]); float y = deadzone(joy[1]); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, (uint32_t)(500 + x * 500)); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, (uint32_t)(500 + y * 500)); HAL_Delay(20); }
Conseils Pro & Erreurs Courantes
Ce qui distingue un débutant d'un ingénieur embarqué professionnel — condensé de leçons terrain.
Guide Temps S/H
| Impédance | S/H recommandé |
|---|---|
| < 1 kΩ | 3 – 15 cycles |
| 1 – 10 kΩ | 28 – 56 cycles |
| > 10 kΩ | 84 – 480 cycles |
| Temp. interne | 480 cycles |
Ne Jamais Faire
- ❌ Appliquer >3,3V sur broche ADC
- ❌ Broche GPIO flottante
- ❌ Oublier
volatilesur buffer DMA - ❌ TSVREFE non activé
- ❌ Buffer DMA sur la pile
- ❌ Horloge ADC > 36 MHz
Filtre Anti-Repliement
R = 1 kΩ en série + C = 100 nF vers GND → f_coupure ≈ 1,6 kHz. Placer le plus proche possible de la broche MCU.
Quel Mode Choisir ?
| Mode | Usage |
|---|---|
| Scrutation | Débutant, <1 Hz |
| Interrupt | Événementiel |
| DMA Single | 1 canal rapide |
| DMA Scan | Multi-capteurs |
🧮 Calculateur de Temps de Conversion — Exemple
📚 Référence Rapide HAL
| Fonction HAL | Action registre | Usage |
|---|---|---|
HAL_ADC_Init() | Écrit CR1, CR2, SMPR | Init obligatoire |
HAL_ADC_ConfigChannel() | Configure SQR, SMPR | Séquenceur |
HAL_ADC_Start() | SWSTART ← 1 dans CR2 | Scrutation |
HAL_ADC_PollForConversion() | Scrute EOC dans SR | Attente bloquante |
HAL_ADC_GetValue() | Lit ADC_DR | Récupère résultat |
HAL_ADC_Stop() | ADON ← 0 dans CR2 | Arrêt propre |
HAL_ADC_Start_DMA() | DMA + CONT bits | Mode DMA circulaire |
HAL_ADC_Stop_DMA() | DMA ← 0, ADON ← 0 | Arrêt DMA |