SÉRIE DE TRAVAUX PRATIQUES — STM32F4 BLACK PILL

7 TPs STM32F411CEU6
de Zéro à Système Intégré

// STM32CubeIDE · HAL Library · C · GPIO · Timer · ADC · I2C · UART
01
💡
// TP 1 — GPIO OUTPUT

LED Clignotante — Hello World embarqué

Premier programme STM32 : faire clignoter une LED sur PC13 à 1 Hz.

SIMULATION LED PC13
État : OFF · Cycle : 0
HAL_Delay(500ms) → Toggle → HAL_Delay(500ms)
01
Configuration CubeMX — PC13 en GPIO_Output
Pinout & Configuration
  1. Créer projet : FileNew STM32 Project → sélectionner STM32F411CEU6
  2. Dans CubeMX → cliquer sur la broche PC13 → choisir GPIO_Output
  3. Clic droit sur PC13 → Enter User Label → taper LED
  4. Générer le code : ProjectGenerate Code
PC13 ACTIF BAS
Sur la Black Pill, PC13 commande la LED intégrée en logique inverse : GPIO_PIN_RESET = LED allumée, GPIO_PIN_SET = éteinte.
02
Code HAL — Clignotement 1 Hz
main.c → while(1)
C — TP1 : LED Blink 1 Hz
/* TP1 — LED Clignotante sur PC13 · STM32F411CEU6 Black Pill */
#include "main.h"

#define BLINK_DELAY  500   /* ms — modifier pour changer la fréquence */
/* 500 ms ON + 500 ms OFF = période 1 s = fréquence 1 Hz */

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    while (1) {
        /* Méthode 1 : Toggle (recommandée) */
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        HAL_Delay(BLINK_DELAY);

        /* Méthode 2 : Write explicite (équivalent) */
        // HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // ON
        // HAL_Delay(BLINK_DELAY);
        // HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);   // OFF
        // HAL_Delay(BLINK_DELAY);
    }
}

Variantes de clignotement

SOS (· · · — — — · · ·)

void blink(uint32_t ms){
  HAL_GPIO_WritePin(GPIOC,
    GPIO_PIN_13,GPIO_PIN_RESET);
  HAL_Delay(ms);
  HAL_GPIO_WritePin(GPIOC,
    GPIO_PIN_13,GPIO_PIN_SET);
  HAL_Delay(100);
}
/* S: 3 courts */
for(int i=0;i<3;i++) blink(200);
/* O: 3 longs */
for(int i=0;i<3;i++) blink(600);
/* S: 3 courts */
for(int i=0;i<3;i++) blink(200);
HAL_Delay(2000);

Fréquence variable

uint32_t freq[] = {
  100,200,500,1000};
for(int f=0;f<4;f++){
  for(int i=0;i<5;i++){
    HAL_GPIO_TogglePin(
      GPIOC,GPIO_PIN_13);
    HAL_Delay(freq[f]);
  }
}

Schéma câblage

PC13 Black Pill 220Ω LED GND ou LED intégrée
FIG. PC13 → 220Ω → LED → GND
RÉSULTAT ATTENDU
LED clignote à 1 Hz (500ms ON / 500ms OFF). Modifier BLINK_DELAY pour changer la fréquence.
02
🚦
// TP 2 — GPIO MULTIPLE + SÉQUENCEUR

Feux Tricolores — Séquence ROUGE → ORANGE → VERT

Contrôler 3 LEDs avec des délais précis pour simuler des feux de circulation.

Phase : STOP
Appuyer sur Start
01
Configuration GPIO — 3 LEDs
PA0=ROUGE · PA1=ORANGE · PA2=VERT
BrocheCouleur LEDModeLabel
PA0● ROUGEGPIO_OutputLED_R
PA1● ORANGEGPIO_OutputLED_O
PA2● VERTGPIO_OutputLED_G
BLACK PILL PA0 PA1 PA2 GND
FIG. PA0/PA1/PA2 → 220Ω → LED → GND
02
Code HAL — Séquenceur Feux Tricolores
C — TP2 : Feux Tricolores
/* TP2 — Feux Tricolores · PA0=Rouge PA1=Orange PA2=Vert */
#include "main.h"

/* ── Durées de chaque phase (ms) — personnalisables ── */

#define T_ROUGE   4000  /* Rouge seul : 4 secondes  */
#define T_ORANGE  1500  /* Orange seul : 1.5 secondes */
#define T_VERT    3000  /* Vert seul  : 3 secondes  */

/* ── Macros : éteindre toutes les LEDs ── */
#define ALL_OFF() do { \
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET); \
} while(0)

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    while (1) {

        /* PHASE 1 : ROUGE — Stop */
        ALL_OFF();
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
        HAL_Delay(T_ROUGE);

        /* PHASE 2 : ORANGE — Attention */
        ALL_OFF();
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
        HAL_Delay(T_ORANGE);

        /* PHASE 3 : VERT — Passage autorisé */
        ALL_OFF();
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
        HAL_Delay(T_VERT);

        /* PHASE 4 : ORANGE — Avant rouge */
        ALL_OFF();
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
        HAL_Delay(T_ORANGE);
    }
}
03
🔘
// TP 3 — GPIO INPUT + INTERRUPTION EXTI

Bouton Poussoir — Polling et Interruption

Lire un bouton en mode Polling, puis en mode EXTI (interruption externe).

APPUYER
BTN
COMPTEUR APPUIS
0
LED OFF
01
Configuration : PB0 = Bouton, PC13 = LED
GPIO_Input + EXTI
  1. Broche PB0GPIO_Input · Label : BTN
  2. GPIO Pull-up/Pull-down : Pull-up (bouton câblé vers GND)
  3. Pour le mode EXTI : PB0 → GPIO_EXTI0 · Trigger : Falling Edge
  4. Activer l'IRQ : NVIC cocher EXTI line0 interrupt · Priority : 0
ANTI-REBOND
Un bouton physique génère des rebonds (oscillations rapides de quelques ms). Solution logicielle : ignorer les appuis à moins de 50ms d'intervalle (debounce).
02
Code Polling + EXTI avec Debounce
C — TP3 : Bouton Polling + EXTI
/* TP3 — Bouton PB0 (Pull-up) + LED PC13 */
#include "main.h"

#define DEBOUNCE_MS  50    /* Délai anti-rebond */

/* ════ MÉTHODE 1 : POLLING ════ */

int main_polling(void) {
    HAL_Init(); SystemClock_Config(); MX_GPIO_Init();
    uint32_t last_press = 0;

    while (1) {
        /* Bouton actif bas (Pull-up) : RESET = appuyé */
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) {

            /* Anti-rebond */
            if (HAL_GetTick() - last_press > DEBOUNCE_MS) {
                last_press = HAL_GetTick();
                HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
            }
            /* Attendre relâchement */
            while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET);
        }
    }
}

/* ════ MÉTHODE 2 : INTERRUPTION EXTI ════ */

volatile uint8_t  btn_flag    = 0;
volatile uint32_t last_irq    = 0;
volatile uint32_t press_count = 0;

int main(void) {
    HAL_Init(); SystemClock_Config(); MX_GPIO_Init();

    while (1) {
        if (btn_flag) {
            btn_flag = 0;
            press_count++;
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        }
    }
}


/* Callback EXTI — appelé automatiquement par l'IRQ */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_0) {
        /* Anti-rebond dans l'IRQ */
        if (HAL_GetTick() - last_irq > DEBOUNCE_MS) {
            last_irq = HAL_GetTick();
            btn_flag = 1;   /* Signaler au main() */
        }
    }
}
04
📡
// TP 4 — TIMER + CAPTURE — HC-SR04

Capteur Ultrason HC-SR04 — Mesure de Distance

Générer une impulsion TRIG, mesurer le temps de ECHO avec un Timer, calculer la distance.

SIMULATEUR HC-SR04
50 cm
Echo : 2941 µs · TOF : 1471 µs
2 cm120 cm400 cm
◀ Déplacer l'obstacle ▶
01
Principe HC-SR04 + Configuration Timer
PA8=TRIG · PA9=ECHO · TIM1 @ 1MHz
TRIG (PA8) 10µs ECHO (PA9) t_echo = 2×d / 340 m/s TIM1 mesure t_echo (µs) ))) sons ultras ))) → obstacle → écho d = t_echo × 340 ─────────── 2 × 10000
FIG. 4 — Protocole HC-SR04 : impulsion TRIG 10µs → mesure ECHO → calcul distance
d (cm) = t_echo(µs) × 0.017
Vitesse son = 340 m/s · Aller-retour ÷ 2 · Conversion µs→cm

Configuration Timer TIM1 pour délais µs

  1. CubeMX → TimersTIM1 → Clock Source : Internal Clock
  2. Prescaler = 99 · Counter Period = 65535
    → Tick TIM1 = 100MHz / (99+1) = 1 MHz → 1 tick = 1 µs
  3. PA8 → GPIO_Output (TRIG) · PA9 → GPIO_Input (ECHO)
02
Code HAL — Lecture distance HC-SR04
C — TP4 : HC-SR04 Ultrason
/* TP4 — HC-SR04 : TRIG=PA8 ECHO=PA9 · TIM1 @ 1 MHz */
#include "main.h"
#include <stdio.h>

TIM_HandleTypeDef htim1;
UART_HandleTypeDef huart1;

/* ── Délai précis en µs via TIM1 ── */

void delay_us(uint16_t us) {
    __HAL_TIM_SET_COUNTER(&htim1, 0);
    while (__HAL_TIM_GET_COUNTER(&htim1) < us);
}

/* ── Lecture distance HC-SR04 (en cm) ── */

float hcsr04_read_cm(void) {
    uint32_t t_start, t_echo, timeout;

    /* 1. Impulsion TRIG : 10 µs */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
    delay_us(10);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);

    /* 2. Attendre montée ECHO (timeout 30 ms) */
    timeout = HAL_GetTick() + 30;
    while (!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9))
        if (HAL_GetTick() > timeout) return -1;

    /* 3. Mesurer durée ECHO */
    __HAL_TIM_SET_COUNTER(&htim1, 0);
    while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9));
    t_echo = __HAL_TIM_GET_COUNTER(&htim1);  /* en µs */

    /* 4. Calculer distance (vitesse son = 340 m/s) */
    return (float)t_echo * 0.017f;  /* cm */
}

int main(void) {
    HAL_Init(); SystemClock_Config();
    MX_GPIO_Init(); MX_TIM1_Init(); MX_USART1_UART_Init();
    HAL_TIM_Base_Start(&htim1);   /* Démarrer TIM1 */

    while (1) {
        float dist = hcsr04_read_cm();
        if (dist > 0)
            printf("Distance : %.1f cm\r\n", dist);
        else
            printf("Hors portée !\r\n");
        HAL_Delay(100);
    }
}
PORTÉE HC-SR04
Distance mesurable : 2 cm à 400 cm. Au-delà, le signal ECHO ne revient pas dans le timeout → retourne -1.
05
⚙️
// TP 5 — PWM TIMER — SERVO MOTOR SG90

Servo Motor SG90 — Contrôle par PWM

Générer un signal PWM 50Hz pour positionner le servo de 0° à 180°.

SIMULATEUR SERVO SG90
90°
PWM Pulse : 1500 µs · CCR : 3000
0° ◀────────── 90° ──────────▶ 180°
01
Théorie PWM Servo + Configuration TIM2
50 Hz · Pulse 1ms–2ms → 0°–180°
CCR = Pulse_µs × (ARR+1) / Période_µs
ARR=19999 · Période=20ms=20000µs · Ex: 90° → 1500µs → CCR=1500×20000/20000=1500

Angle 0°

Pulse : 1000 µs
CCR : 1000

Angle 90°

Pulse : 1500 µs
CCR : 1500

Angle 180°

Pulse : 2000 µs
CCR : 2000

  1. CubeMX → TimersTIM2 → Channel1 : PWM Generation CH1 (PA0)
  2. Prescaler = 99 → TIM2 clock = 1 MHz
  3. Counter Period (ARR) = 19999 → Période = 20 ms = 50 Hz ✓
  4. Pulse = 1500 (90° par défaut)
02
Code HAL — Servo SG90
C — TP5 : Servo Motor SG90
/* TP5 — Servo SG90 sur PA0 (TIM2_CH1) · 50Hz PWM */
#include "main.h"

TIM_HandleTypeDef htim2;

/* ── Convertir angle (0–180°) en valeur CCR ── */

uint32_t angle_to_ccr(uint8_t angle) {
    /* Interpolation linéaire : 0° → 500µs, 180° → 2500µs */
    if (angle > 180) angle = 180;
    return (uint32_t)(500 + ((float)angle / 180.0f) * 2000);
}

void servo_set_angle(uint8_t angle) {
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, angle_to_ccr(angle));
}

int main(void) {
    HAL_Init(); SystemClock_Config();
    MX_GPIO_Init(); MX_TIM2_Init();

    /* Démarrer le PWM */
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

    while (1) {

        /* Balayage 0° → 180° → 0° (sweep) */
        for (uint8_t a = 0; a <= 180; a += 5) {
            servo_set_angle(a);
            HAL_Delay(30);
        }
        for (int16_t a = 180; a >= 0; a -= 5) {
            servo_set_angle((uint8_t)a);
            HAL_Delay(30);
        }

        /* Positions fixes */
        servo_set_angle(0);    HAL_Delay(1000);
        servo_set_angle(90);   HAL_Delay(1000);
        servo_set_angle(180);  HAL_Delay(1000);
    }
}
CÂBLAGE SG90
Fil Rouge → 5V · Fil Marron/Noir → GND · Fil Orange/Jaune → PA0 (signal PWM). Alimenter le servo sur 5V externe si possible (fort courant au démarrage).
06
🌡️
// TP 6 — 1-WIRE · TIMER µs — DHT11

Capteur DHT11 — Température & Humidité

Protocole 1-Wire, trame 40 bits, timer µs précis pour décoder température et humidité.

SIMULATEUR DHT11
25
°C · Température
60
% · Humidité
OFF
LED Alarme (T > 30°C)
Température : 25°C
Humidité : 60%
Trame 40 bits : 0001100100111100 00011001 00111100 00110101
01
Protocole DHT11 — Séquence 1-Wire
Trame 40 bits : 16b Humidité + 16b Température + 8b Checksum
DHT11 DATA 18ms STM→LOW 80µs DHT LOW 80µs DHT HIGH 50µs 26µs BIT '0' 50µs 70µs BIT '1' ...×40 bits Humidité 16 bits Température 16 bits Checksum 8 bits = 40 bits total
FIG. 6 — Protocole DHT11 : start 18ms, réponse 80µs, 40 bits (bit0=26µs, bit1=70µs)
RÉSISTANCE PULL-UP OBLIGATOIRE
La ligne DATA nécessite une résistance pull-up de 4.7 kΩ entre DATA et VCC (3.3V ou 5V). Sans elle, la communication échoue systématiquement.
02
Code HAL — Driver DHT11 complet
PA5=DATA · TIM1 @ 1MHz · GPIO bidirectionnel
C — TP6 : DHT11 Driver complet
/* TP6 — DHT11 sur PA5 · TIM1 @ 1MHz · PC13 = LED alarme */
#include "main.h"
#include <stdio.h>

TIM_HandleTypeDef htim1;
UART_HandleTypeDef huart1;
#define DHT_PORT  GPIOA
#define DHT_PIN   GPIO_PIN_5
#define TEMP_ALARM 30   /* °C — modifier le seuil ici */

void delay_us(uint16_t us){
    __HAL_TIM_SET_COUNTER(&htim1,0);
    while(__HAL_TIM_GET_COUNTER(&htim1)<us);
}

/* ── Basculer PA5 en Output ── */
void dht_set_output(void){
    GPIO_InitTypeDef g={0};
    g.Pin=DHT_PIN; g.Mode=GPIO_MODE_OUTPUT_PP;
    g.Pull=GPIO_NOPULL; g.Speed=GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(DHT_PORT,&g);
}
/* ── Basculer PA5 en Input ── */
void dht_set_input(void){
    GPIO_InitTypeDef g={0};
    g.Pin=DHT_PIN; g.Mode=GPIO_MODE_INPUT;
    g.Pull=GPIO_PULLUP;
    HAL_GPIO_Init(DHT_PORT,&g);
}

/* ── Structure résultat DHT11 ── */
typedef struct {
    uint8_t temperature;
    uint8_t humidity;
    uint8_t valid;
} DHT11_Data;


DHT11_Data dht11_read(void) {
    DHT11_Data result = {0,0,0};
    uint8_t data[5]={0};

    /* STEP 1 : Signal de démarrage (18 ms LOW) */
    dht_set_output();
    HAL_GPIO_WritePin(DHT_PORT,DHT_PIN,GPIO_PIN_RESET);
    HAL_Delay(18);
    HAL_GPIO_WritePin(DHT_PORT,DHT_PIN,GPIO_PIN_SET);
    delay_us(30);
    dht_set_input();

    /* STEP 2 : Attendre réponse DHT11 (80µs LOW + 80µs HIGH) */
    uint32_t t=HAL_GetTick()+2;
    while(!HAL_GPIO_ReadPin(DHT_PORT,DHT_PIN) && HAL_GetTick()<t);
    while(HAL_GPIO_ReadPin(DHT_PORT,DHT_PIN) && HAL_GetTick()<t+2);

    /* STEP 3 : Lire 40 bits */
    for (int i=0;i<40;i++) {
        while(!HAL_GPIO_ReadPin(DHT_PORT,DHT_PIN)); /* attendre HIGH */
        delay_us(40);  /* échantillonner après 40µs */
        if(HAL_GPIO_ReadPin(DHT_PORT,DHT_PIN))
            data[i/8] |= (1 << (7-(i%8)));  /* BIT = 1 */
        while(HAL_GPIO_ReadPin(DHT_PORT,DHT_PIN)); /* attendre LOW */
    }

    /* STEP 4 : Vérifier le checksum */
    if(data[4]==((data[0]+data[1]+data[2]+data[3])&0xFF)){
        result.humidity    = data[0];
        result.temperature = data[2];
        result.valid       = 1;
    }
    return result;
}

int main(void) {
    HAL_Init(); SystemClock_Config();
    MX_GPIO_Init(); MX_TIM1_Init(); MX_USART1_UART_Init();
    HAL_TIM_Base_Start(&htim1);

    while(1){
        DHT11_Data d = dht11_read();
        if(d.valid){
            printf("T=%d°C H=%d%% %s\r\n",
                d.temperature, d.humidity,
                d.temperature>TEMP_ALARM?"[ALARME!]":"[OK]");
            /* LED alarme */
            HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,
                d.temperature>TEMP_ALARM?GPIO_PIN_RESET:GPIO_PIN_SET);
        }
        HAL_Delay(2000); /* DHT11 : lecture max 1×/2s */
    }
}
07
🏗️
// TP 7 — SYSTÈME INTÉGRÉ COMPLET

Station de Surveillance — DHT11 + Ultrason + Servo + LEDs

Combiner tous les TP précédents : mesure distance, température, alarmes et servo de direction.

SYS
Architecture du Système
Machine à états — 3 modes de fonctionnement
HC-SR04 Distance DHT11 Temp/Hum BOUTON Mode Select BLACK PILL STM32F411CEU6 Machine à États NORMAL / ALERT / SCAN SERVO SG90 Scan 0-180° LED VERTE État normal LED ROUGE Alarme Temp UART Debug Terminal PC
FIG. 7 — Architecture système intégré : 3 capteurs → STM32F411 → 3 actionneurs + UART

🟢 MODE NORMAL

  • Lecture DHT11 toutes les 2s
  • Distance HC-SR04 toutes 100ms
  • LED verte allumée
  • Servo en position 90°

🔴 MODE ALERTE

  • Temp > 30°C → LED rouge
  • Distance < 20cm → Alarme
  • Servo se déplace à 0°
  • UART : message d'alerte

🔵 MODE SCAN

  • Bouton → change de mode
  • Servo balaye 0→180°
  • Mesure distance à chaque angle
  • Cartographie ultrasonique
01
Tableau des Broches — Système Complet
BrocheSignalComposantModePériphérique HAL
PA0PWMServo SG90TIM2_CH1htim2
PA5DATADHT11GPIO_IO (bidirectionnel)GPIO
PA6TRIGHC-SR04GPIO_OutputGPIO
PA7ECHOHC-SR04GPIO_InputGPIO
PB0BTNBouton modeGPIO_EXTI0NVIC
PB4LED VERTELED état normalGPIO_OutputGPIO
PC13LED ROUGELED alarmeGPIO_OutputGPIO
PA9UART1_TXDebug PCUSART1huart1
TIM1Base 1MHzDHT11+HC-SR04Timer Basehtim1
02
Code Principal — Machine à États
C — TP7 : Système Intégré Complet
/* TP7 — Système intégré : DHT11 + HC-SR04 + Servo + LEDs */
#include "main.h"
#include <stdio.h>

/* ─── Seuils personnalisables ─── */

#define TEMP_ALARM_C    30    /* °C */
#define DIST_ALARM_CM   20    /* cm */

/* ─── Machine à états ─── */
typedef enum {
    MODE_NORMAL = 0,
    MODE_ALERT  = 1,
    MODE_SCAN   = 2
} SystemMode;

volatile SystemMode  sys_mode   = MODE_NORMAL;
volatile uint8_t     btn_pressed = 0;
volatile uint32_t    last_btn    = 0;

/* ─── Fonctions capteurs (voir TP4 et TP6) ─── */
extern float       hcsr04_read_cm(void);
extern DHT11_Data  dht11_read(void);
extern void        servo_set_angle(uint8_t angle);

int main(void) {
    HAL_Init(); SystemClock_Config();
    MX_GPIO_Init(); MX_TIM1_Init(); MX_TIM2_Init();
    MX_USART1_UART_Init();
    HAL_TIM_Base_Start(&htim1);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

    uint32_t t_dht   = 0, t_dist = 0;
    DHT11_Data dht   = {0};
    float      dist   = 0;

    while (1) {

        /* ── Changer de mode via bouton ── */
        if (btn_pressed) {
            btn_pressed = 0;
            sys_mode = (SystemMode)((sys_mode + 1) % 3);
            printf("Mode: %d\r\n", sys_mode);
        }

        /* ── Lecture DHT11 toutes les 2 secondes ── */
        if (HAL_GetTick() - t_dht > 2000) {
            t_dht = HAL_GetTick();
            dht   = dht11_read();
        }

        /* ── Lecture distance toutes les 200ms ── */
        if (HAL_GetTick() - t_dist > 200) {
            t_dist = HAL_GetTick();
            dist   = hcsr04_read_cm();
        }

        /* ── Logique selon le mode ── */
        switch (sys_mode) {

            case MODE_NORMAL:
                servo_set_angle(90);
                HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_SET);   /* LED verte */
                HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET); /* LED rouge OFF */
                break;

            case MODE_ALERT:
                /* Alarme température */
                if (dht.valid && dht.temperature > TEMP_ALARM_C) {
                    HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
                    servo_set_angle(0);  /* Servo en butée */
                    printf("ALERTE TEMP! T=%d°C\r\n", dht.temperature);
                }
                /* Alarme distance */
                if (dist > 0 && dist < DIST_ALARM_CM) {
                    HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);  /* Clignotement */
                    printf("ALERTE DIST! D=%.1fcm\r\n", dist);
                }
                break;

            case MODE_SCAN:
                /* Balayage servo + cartographie distance */
                for (uint8_t a=0;a<=180;a+=10) {
                    servo_set_angle(a);
                    HAL_Delay(200);
                    float d = hcsr04_read_cm();
                    printf("SCAN angle=%d dist=%.1f\r\n", a, d);
                }
                break;

            default: break;
        }

        /* ── Affichage UART régulier ── */
        if (dht.valid)
            printf("T=%d°C H=%d%% D=%.1fcm Mode=%d\r\n",
                dht.temperature, dht.humidity, dist, sys_mode);

        HAL_Delay(50);
    }
}

/* ── IRQ Bouton ── */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if(GPIO_Pin==GPIO_PIN_0 && HAL_GetTick()-last_btn>50) {
        last_btn=HAL_GetTick();
        btn_pressed=1;
    }
}
SYSTÈME OPÉRATIONNEL
Appuyer sur BTN (PB0) pour cycler : NORMAL → ALERTE → SCAN → NORMAL
Terminal UART à 115200 baud pour voir toutes les mesures en temps réel.
EXTENSIONS POSSIBLES
→ Ajouter un écran OLED 0.96" (I2C) pour afficher T°, humidité, distance
→ Sauvegarder les mesures en Flash interne (HAL_FLASH)
→ Envoyer les données via Bluetooth (HC-05) ou WiFi (ESP8266 AT commands)