TRAVAUX PRATIQUES — SYSTÈMES EMBARQUÉS STM32
ADC 12-bit · Potentiomètre
→ Alarme LED Rouge
// STM32F411CEU6 Black Pill · STM32CubeIDE · HAL Library
MCU · STM32F411CEU6
ADC · 12 bits · 3 modes
ENTRÉE · PA1 – ADC1_IN1
ALARME · LED PC13
DURÉE · 3H
SIMULATEUR ADC EN TEMPS RÉEL
2047
Tension : 1.65 V
Binaire : 011111111111
Hex : 0x7FF
◀ Tourner le potentiomètre ▶
LED Rouge : OFF
Seuil alarme : ADC > (0–4095)
// INTRODUCTION
Objectifs du TP
Maîtriser l'ADC 12 bits du STM32F411 en 3 modes : Polling, Interruption, DMA.
◈
Ce que vous allez apprendre
Compétences visées
📐 THÉORIE
- Principe de la conversion A/N
- Résolution 12 bits, LSB, pleine échelle
- Formule tension ↔ valeur numérique
- Clock ADC et temps de conversion
⚙️ MODES ADC
- Mode Polling (bloquant)
- Mode Interruption (non-bloquant)
- Mode DMA (automatique)
- Comparaison des 3 approches
🔴 APPLICATION
- Lire un potentiomètre 10kΩ
- Convertir valeur ADC → Tension
- Seuil personnalisable en code
- Alarme LED + debug UART
MATÉRIEL REQUIS
Carte STM32F4 Black Pill (STM32F411CEU6) · Potentiomètre 10 kΩ · LED rouge + résistance 220Ω · Câble ST-Link V2 · PC avec STM32CubeIDE 1.12+
// PARTIE 1 — RAPPELS THÉORIQUES
L'ADC 12 bits du STM32F411
Comprendre la conversion analogique-numérique avant de coder.
T1
Principe de la Conversion Analogique-Numérique
Signal continu → valeur discrète 12 bits
L'ADC (Analog-to-Digital Converter) transforme une tension analogique continue en un nombre entier discret. Le STM32F411 dispose d'un ADC 12 bits successifs approximations (SAR).
FIG. 1 — Conversion analogique → numérique : signal continu discretisé en valeurs 12 bits
Caractéristiques STM32F411 ADC
| Paramètre | Valeur |
|---|---|
| Résolution | 12 bits (défaut) |
| Niveaux discrets | 4096 (2¹²) |
| Tension référence | VREF+ = 3.3 V |
| LSB (1 pas) | ≈ 0.806 mV |
| Canaux | 16 canaux externes |
| Vitesse max | 2.4 MSPS |
| Type | SAR (Successive Approx.) |
| Modes | Single / Cont. / Scan |
Formule de conversion
TENSION → VALEUR ADC
ADC_val = (V_in / 3.3) × 4095
Ex : V=1.65V → ADC = (1.65/3.3)×4095 = 2047
VALEUR ADC → TENSION
V_in = (ADC_val / 4095.0) × 3.3
Ex : ADC=2047 → V = (2047/4095)×3.3 = 1.65 V
LSB (1 pas minimum)
LSB = VREF / (2¹² - 1) = 3.3 / 4095
LSB = 0.000806 V ≈ 0.8 mV
T2
Comparaison des 3 modes ADC
Polling vs Interrupt vs DMA
| Critère | POLLING | INTERRUPT | DMA |
|---|---|---|---|
| Principe | CPU attend la fin | IRQ signale la fin | Transfert auto mémoire |
| CPU bloqué ? | ✗ OUI | ✓ NON | ✓ NON |
| Complexité | Simple | Moyenne | Plus complexe |
| Performance | Faible | Bonne | Excellente |
| Usage typique | Débutant / test | Système réactif | Multi-canaux / haute freq. |
| Fonction HAL | HAL_ADC_PollForConversion | HAL_ADC_Start_IT | HAL_ADC_Start_DMA |
| Callback | Aucun | HAL_ADC_ConvCpltCallback | HAL_ADC_ConvCpltCallback |
CONSEIL PÉDAGOGIQUE
Commencer par le Polling pour comprendre le principe, puis progresser vers Interrupt et DMA pour les systèmes temps réel.// PARTIE 2 — CRÉATION DU PROJET
Nouveau Projet dans STM32CubeIDE
01
Créer un nouveau projet STM32
File → New → STM32 Project
- Ouvrir STM32CubeIDE → File▶New▶STM32 Project
- Dans l'onglet MCU/MPU Selector, chercher :
STM32F411CEU6 - Sélectionner le MCU dans la liste → Cliquer Next
- Nom du projet :
ADC_BlackPill→ Language : C → Finish - Répondre Yes à "Open associated perspective?"
RÉSULTAT
Le fichier ADC_BlackPill.ioc s'ouvre dans STM32CubeMX intégré. Le pinout du STM32F411CEU6 s'affiche.02
Configurer l'horloge système
Clock Configuration — 100 MHz
IMPORTANT
La fréquence de l'ADC dépend de l'horloge APB2. Configurer correctement le clock tree pour obtenir une fréquence ADC ≤ 36 MHz.FIG. 2 — Clock tree STM32F411 : SYSCLK 100MHz → APB2 50MHz → ADC CLK 25MHz
- Dans CubeMX → onglet Clock Configuration
- Input Frequency →
25MHz (HSE externe) - PLL Source → HSE · PLLM=25 · PLLN=200 · PLLP=2
- System Clock Mux → PLLCLK
- HCLK →
100MHz → Appuyer Enter pour valider - APB2 Prescaler → /2 → ADC Prescaler → /2 (= 25 MHz ✓)
// PARTIE 3 — CONFIGURATION CUBEMX
Configurer ADC1 + GPIO via CubeMX
03
Configurer la broche PA1 en ADC
Pinout & Configuration → ADC1
FIG. 3 — Black Pill : PA1 (ADC1_IN1) ← Potentiomètre · PC13 (GPIO OUT) → LED Rouge
- Dans CubeMX → onglet Pinout & Configuration
- Cliquer sur la broche PA1 sur le schéma → sélectionner ADC1_IN1
- Cliquer sur la broche PC13 → sélectionner GPIO_Output
- Dans le panneau gauche → Analog → ADC1
Paramètres ADC1 à configurer
| Paramètre | Valeur | Explication |
|---|---|---|
| Mode | Single Ended | Mesure par rapport à GND |
| Channel | Channel 1 (PA1) | Entrée analogique PA1 |
| Resolution | 12 bits | 0 à 4095 |
| Continuous Conv. Mode | ENABLE | Conversion continue en boucle |
| Sampling Time | 480 Cycles | Temps d'échantillonnage (le plus lent = plus précis) |
| Data Alignment | Right alignment | Bits de poids fort à gauche |
| Scan Conversion Mode | DISABLE | 1 seul canal pour ce TP |
| End Of Conversion | EOC flag at end of single conv. | Drapeau fin de conversion |
Configurer l'UART pour le debug
- Dans CubeMX → Connectivity → USART1
- Mode → Asynchronous
- Baud Rate →
115200· Word Length →8 bits· Stop Bits →1
GÉNÉRER LE CODE
Project▶Generate Code ou cliquer l'icône ⚙️STM32CubeIDE génère automatiquement le code d'initialisation HAL.
// PARTIE 4 — MODE POLLING
ADC en Mode Polling — Bloquant
Le CPU attend activement la fin de la conversion. Simple, idéal pour débuter.
04
Code ADC Mode Polling
main.c — dans la boucle while(1)
MODE : POLLING
FIG. 4 — Flux d'exécution Mode Polling : CPU bloqué pendant PollForConversion()
C — main.c · Mode Polling
/* ============================================================ TP ADC Black Pill — Mode POLLING MCU : STM32F411CEU6 PA1 → ADC1_CH1 (Potentiomètre) PC13 → LED Rouge (alarme) ============================================================ */ #include "main.h" #include <stdio.h> #include <string.h> /* ─── Variables globales ─── */ ADC_HandleTypeDef hadc1; UART_HandleTypeDef huart1; uint32_t adc_value = 0; /* Valeur brute ADC : 0 à 4095 */ float voltage = 0.0f; /* Tension calculée en Volts */ char uart_buf[64]; /* Buffer pour les messages UART */ /* ─── SEUIL D'ALARME PERSONNALISABLE ─── */ #define ALARM_THRESHOLD 2048 /* Modifier ici : 0 à 4095 */ #define VREF 3.3f /* Tension de référence ADC (Volts) */ /* ─── Prototypes ─── */ void SystemClock_Config(void); static void MX_ADC1_Init(void); static void MX_USART1_UART_Init(void); static void MX_GPIO_Init(void); /* ─── Redirection printf → UART ─── */ int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 100); return ch; } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); printf("=== TP ADC Black Pill — Mode Polling ===\r\n"); while (1) { /* Étape 1 : Démarrer la conversion ADC */ HAL_ADC_Start(&hadc1); /* Étape 2 : Attendre la fin de conversion (timeout 100 ms) */ if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) { /* Étape 3 : Lire la valeur numérique 12 bits */ adc_value = HAL_ADC_GetValue(&hadc1); /* Étape 4 : Convertir en tension réelle */ voltage = ((float)adc_value / 4095.0f) * VREF; /* Étape 5 : Logique d'alarme LED rouge */ if (adc_value > ALARM_THRESHOLD) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); /* LED ON (actif bas) */ } else { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); /* LED OFF */ } /* Étape 6 : Afficher via UART */ printf("ADC=%4lu | V=%.3f V | Seuil=%d | LED=%s\r\n", adc_value, voltage, ALARM_THRESHOLD, (adc_value > ALARM_THRESHOLD) ? "ON" : "OFF"); } HAL_ADC_Stop(&hadc1); HAL_Delay(200); /* 200 ms entre chaque mesure */ } }
AVANTAGES POLLING
✓ Code simple et linéaire✓ Facile à déboguer
✓ Idéal pour apprendre
INCONVÉNIENTS POLLING
✗ CPU bloqué pendant la conversion✗ Impossible de faire autre chose
✗ Non adapté aux systèmes temps réel
// PARTIE 5 — MODE INTERRUPTION
ADC en Mode Interruption — Non Bloquant
Le CPU est libéré. La conversion termine → une IRQ appelle automatiquement le callback.
05
Code ADC Mode Interrupt (IT)
HAL_ADC_Start_IT + Callback
MODE : INTERRUPT
FIG. 5 — Mode Interrupt : le CPU est libre, l'IRQ déclenche le callback à chaque fin de conversion
C — main.c · Mode Interrupt
/* ============================================================ TP ADC Black Pill — Mode INTERRUPTION ============================================================ */ #include "main.h" #include <stdio.h> ADC_HandleTypeDef hadc1; UART_HandleTypeDef huart1; /* ─── Variables partagées (volatile = accès ISR + main) ─── */ volatile uint32_t adc_value = 0; volatile uint8_t conv_complete = 0; /* Drapeau : conversion terminée */ float voltage = 0.0f; #define ALARM_THRESHOLD 2048 /* ← Modifier le seuil ici */ #define VREF 3.3f int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_USART1_UART_Init(); printf("=== TP ADC Black Pill — Mode Interrupt ===\r\n"); /* Démarrer la première conversion en mode interruption */ HAL_ADC_Start_IT(&hadc1); /* Lance l'ADC — non bloquant ! */ while (1) { /* Le CPU peut faire autre chose ici... */ /* (calculs, communication, gestion d'écran, etc.) */ /* Vérifier si une conversion est prête */ if (conv_complete) { conv_complete = 0; voltage = ((float)adc_value / 4095.0f) * VREF; if (adc_value > ALARM_THRESHOLD) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); } printf("[IT] ADC=%4lu | V=%.3f V | LED=%s\r\n", adc_value, voltage, (adc_value > ALARM_THRESHOLD) ? "ON" : "OFF"); } HAL_Delay(100); } } /* ─── CALLBACK : appelé automatiquement par l'IRQ ADC ─── */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADC1) { /* Lire la valeur ADC depuis l'ISR */ adc_value = HAL_ADC_GetValue(hadc); conv_complete = 1; /* Signaler au main() */ /* Relancer la prochaine conversion */ HAL_ADC_Start_IT(hadc); } }
POINT CLEF — volatile
Les variables partagées entre le main() et l'ISR doivent être déclarées volatile. Sans cela, le compilateur peut les optimiser incorrectement et le programme aura un comportement indéfini.
// PARTIE 6 — MODE DMA
ADC en Mode DMA — Transfert Direct Mémoire
Le DMA transfère automatiquement les valeurs ADC en RAM sans intervention du CPU.
06
Activer le DMA dans CubeMX
Configuration préalable obligatoire
MODE : DMA
CONFIGURATION CUBEMX DMA
Dans CubeMX → ADC1 → onglet DMA Settings :Cliquer Add → Sélectionner ADC1 → Direction : Peripheral to Memory → Mode : Circular → Data Width : Word
ATTENTION
En mode DMA Circular, la conversion tourne en boucle infinie automatiquement. Le buffer en RAM est mis à jour continuellement sans intervention du CPU.07
Code ADC Mode DMA
Buffer circulaire — Transfert automatique
FIG. 6 — Mode DMA Circular : ADC → RAM automatiquement, CPU lit le buffer quand nécessaire
C — main.c · Mode DMA
/* ============================================================ TP ADC Black Pill — Mode DMA (Circular) ============================================================ */ #include "main.h" #include <stdio.h> ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; UART_HandleTypeDef huart1; #define ADC_BUF_SIZE 4 /* Taille du buffer DMA (nb conversions) */ #define ALARM_THRESHOLD 2048 /* ← Modifier le seuil ici */ #define VREF 3.3f /* ─── Buffer DMA : rempli automatiquement ─── */ volatile uint32_t adc_buf[ADC_BUF_SIZE] = {0}; volatile uint8_t dma_ready = 0; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); /* Init DMA AVANT ADC ! */ MX_ADC1_Init(); MX_USART1_UART_Init(); printf("=== TP ADC Black Pill — Mode DMA ===\r\n"); /* Lancer ADC+DMA en mode circulaire — 1 seul appel ! */ HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_BUF_SIZE); while (1) { /* Le buffer adc_buf[] est mis à jour automatiquement */ /* Le CPU peut lire les valeurs à tout moment */ if (dma_ready) { dma_ready = 0; /* Calculer la moyenne du buffer pour réduire le bruit */ uint32_t avg = 0; for (int i = 0; i < ADC_BUF_SIZE; i++) avg += adc_buf[i]; avg /= ADC_BUF_SIZE; float voltage = ((float)avg / 4095.0f) * VREF; if (avg > ALARM_THRESHOLD) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); } printf("[DMA] ADC(moy)=%4lu | V=%.3fV | LED=%s\r\n", avg, voltage, (avg > ALARM_THRESHOLD) ? "ON" : "OFF"); } HAL_Delay(200); } } /* ─── Callback DMA : buffer completement rempli ─── */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADC1) { dma_ready = 1; /* Signaler que le buffer est frais */ } }
AVANTAGE DMA — MOYENNAGE
En utilisant un buffer de 4 valeurs et en calculant la moyenne, on réduit le bruit ADC causé par les parasites électriques. C'est une technique appelée oversampling.
// PARTIE 7 — LOGIQUE D'ALARME
Seuil Personnalisable + LED Rouge
08
Personnaliser le seuil d'alarme
Modifier ALARM_THRESHOLD dans le code
Le seuil est défini par la macro #define ALARM_THRESHOLD en haut de chaque fichier. Il suffit de modifier cette valeur et de recompiler.
MI-COURSE (50%)
#define ALARM_THRESHOLD 2048
Potentiomètre à mi-course
V = 1.65V
V = 1.65V
75% (HAUT)
#define ALARM_THRESHOLD 3072
LED s'allume à 3/4 tour
V = 2.475V
V = 2.475V
25% (BAS)
#define ALARM_THRESHOLD 1024
Très sensible : LED dès 1/4
V = 0.825V
V = 0.825V
C — Logique d'alarme complète avec hysteresis
/* ─── Seuils avec hystérésis (évite les oscillations) ─── */ #define ALARM_THRESHOLD 2048 /* Seuil montant : LED s'allume */ #define ALARM_HYSTERESIS 100 /* Marge : LED s'éteint à 1948 */ /* Variable d'état de la LED */ static uint8_t alarm_active = 0; void update_alarm(uint32_t adc_val) { if (!alarm_active && adc_val > ALARM_THRESHOLD) { /* Seuil dépassé → Alarme ON */ alarm_active = 1; HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); printf("[ALARME] ⚠ Seuil dépassé ! ADC=%lu\r\n", adc_val); } else if (alarm_active && adc_val < (ALARM_THRESHOLD - ALARM_HYSTERESIS)) { /* En dessous du seuil - hystérésis → Alarme OFF */ alarm_active = 0; HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); printf("[ALARME] ✓ Retour normal. ADC=%lu\r\n", adc_val); } }
C'EST QUOI L'HYSTÉRÉSIS ?
Sans hystérésis, si l'ADC oscille autour du seuil (ex: 2047-2049-2048...), la LED clignoterait rapidement. Avec une marge de 100 LSB : la LED s'allume à 2048 mais ne s'éteint qu'à 1948. Cela évite les oscillations parasites.
// PARTIE 8 — CÂBLAGE ET TEST
Schéma de Câblage + Vérification
09
Schéma de câblage complet
Black Pill + Potentiomètre + LED
FIG. 7 — Schéma complet : Potentiomètre 10kΩ sur PA1 · LED Rouge + 220Ω sur PC13 · GND commun
Tableau de connexions
| Composant | Broche composant | Broche Black Pill | Couleur fil | Remarque |
|---|---|---|---|---|
| Potentiomètre | Patte gauche (VCC) | 3.3V | Orange | Alimentation du pot |
| Potentiomètre | Patte droite (GND) | GND | Rouge | Masse du pot |
| Potentiomètre | Curseur (milieu) | PA1 | Violet | Signal ADC |
| Résistance 220Ω | Patte 1 | PC13 | Rouge | Limite le courant LED |
| LED Rouge | Anode (+) | Résistance 220Ω | Rouge | Patte longue = anode |
| LED Rouge | Cathode (−) | GND | Noir | Patte courte = cathode |
⚠ PC13 ACTIF BAS sur BLACK PILL
La LED intégrée de la Black Pill sur PC13 est active basse : GPIO_PIN_RESET = LED allumée, GPIO_PIN_SET = LED éteinte. C'est déjà pris en compte dans les codes fournis.
10
Vérification UART + debug en temps réel
Sortie série attendue dans le terminal
UART OUTPUT — Terminal série (115200 baud)
=== TP ADC Black Pill — Mode Polling === ADC= 0 | V=0.000 V | Seuil=2048 | LED=OFF ADC= 512 | V=0.412 V | Seuil=2048 | LED=OFF ADC=1024 | V=0.825 V | Seuil=2048 | LED=OFF ADC=2047 | V=1.650 V | Seuil=2048 | LED=OFF ADC=2049 | V=1.651 V | Seuil=2048 | LED=ON ← Seuil franchi ! ADC=3072 | V=2.475 V | Seuil=2048 | LED=ON ADC=4095 | V=3.300 V | Seuil=2048 | LED=ON
Points de vérification
- Potentiomètre à zéro → ADC =
0, Tension =0.000V, LED = OFF - Potentiomètre à mi-course → ADC ≈
2047, Tension ≈1.650V - Dépasser le seuil → ADC >
ALARM_THRESHOLD→ LED rouge s'allume - Potentiomètre au maximum → ADC =
4095, Tension =3.300V - Valeur ADC stable (pas de bruit important) → câblage correct ✓
BRUIT ADC — NORMAL OU PAS ?
Une variation de ±2 à ±5 LSB quand le potentiomètre est immobile est normale (bruit thermique). Si la variation dépasse ±20 LSB, vérifier : fils trop longs, absence de condensateur de découplage (100nF entre VDDA et GNDA), ou alimentation instable.
✓
Bilan et questions pour aller plus loin
✅ ACQUIS DU TP
- Principe ADC 12 bits compris
- Formule Tension ↔ ADC maîtrisée
- 3 modes codés et testés
- Seuil personnalisable
- Alarme LED fonctionnelle
- Debug UART opérationnel
📝 QUESTIONS
- Quelle résolution choisir pour ADC 10 bits ?
- Quel est le temps de conversion à 25 MHz avec 480 cycles ?
- Pourquoi utiliser DMA pour 4 canaux simultanés ?
- Comment ajouter un filtre passe-bas logiciel ?
POUR ALLER PLUS LOIN
→ Afficher la valeur sur un écran OLED SSD1306 via I2C→ Réaliser un voltmètre numérique avec affichage LCD
→ Utiliser 4 canaux ADC en DMA Scan Mode simultanément
→ Tracer la courbe tension en temps réel avec Python + matplotlib