📡 Microcontrôleurs & IoT

🧠 TP 3 — Module 5

Machines à États (FSM)

Switch-Case · State Pattern · Lambdas C++11

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

📝 Changelog — V0.1.2

  • Niveau 2 State Pattern corrigé : led.off() ajouté dans stateOff() (bug LED restant allumée à 100% après FULL→OFF). setBrightness déplacé dans transitionTo (onEnter) — plus écrit à chaque tick.
  • (V0.1.1) Niveau 3 réécrit avec FSM.h, BlinkFSM, analyse RAM lambdas.
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

❌ Le problème du code séquentiel

// ❌ Code bloquant avec delay()
void loop() {
    digitalWrite(MOTOR_UP, HIGH);
    delay(5000);                  // Figé 5 secondes !
    digitalWrite(MOTOR_UP, LOW);
    delay(1000);
    // Impossible de détecter un obstacle pendant delay() !
}

Conséquence : si un obstacle surgit pendant delay(5000), le moteur continue.
Objectif : un programme réactif qui peut répondre à n'importe quel événement à tout moment.

La solution : les Machines à États Finis (FSM).

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

📐 Théorie des Automates Finis (FSM)

Trois composants d'un automate :

Composant Définition Exemple (lampe)
États Condition actuelle du système OFF, ECO, FULL
Événements Déclencheurs de changement bouton_cliqué
Transitions Règle A → B sur événement E OFF → ECO si clic

Propriété fondamentale : le système n'est dans qu'un seul état à la fois.

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

🔦 Scénario : la Lampe Intelligente

Un bouton, trois états, trois comportements distincts :

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

🔍 Décomposition de l'automate

Appui bouton ──▶  OFF  ──▶  ECO (50%)  ──▶  FULL (100%)
                   ▲                              │
                   └──────────────────────────────┘
                                Appui bouton

Règles métier :

  • En état OFF → LED éteinte, aucun courant
  • En état ECO → PWM 128/255 (~50% luminosité, économie d'énergie)
  • En état FULL → PWM 255/255 (luminosité maximale)
  • La transition se fait toujours dans le même sens (cycle)
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

⭐ Niveau 1 : Switch-Case Classique

enum State { OFF, ECO, FULL };
State currentState = OFF;

Led lampe(26);
Button btn(12);

void loop() {
    if (btn.wasClicked()) {         // Un seul clic = un seul événement
        switch (currentState) {
            case OFF:
                currentState = ECO;
                lampe.setBrightness(128);
                break;
            case ECO:
                currentState = FULL;
                lampe.setBrightness(255);
                break;
            case FULL:
                currentState = OFF;
                lampe.off();
                break;
        }
    }
}
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

✅ Analyse Niveau 1 : Switch-Case

Forces :

  • Très simple à lire et comprendre
  • Performance maximale (proche du langage machine)
  • Aucune allocation mémoire dynamique
  • Idéal pour ≤ 4-5 états simples

Faiblesses :

  • Devient un "code spaghetti" dès 8-10 états
  • Logique métier mélangée avec la gestion matérielle
  • Les onEnter / onExit doivent être gérés manuellement
  • Difficile de tester unitairement

Recommandation : Pour les automates simples ou les ressources très contraintes (Uno).

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

⭐⭐ Niveau 2 : State Pattern (POO)

class LampFSM {
  private:
    Led&    led;
    Button& btn;
    void (LampFSM::*currentTick)();

    // Ticks : UNIQUEMENT la logique de transition
    void stateOff()  { if (btn.wasClicked()) transitionTo(&LampFSM::stateEco);  }
    void stateEco()  { if (btn.wasClicked()) transitionTo(&LampFSM::stateFull); }
    void stateFull() { if (btn.wasClicked()) transitionTo(&LampFSM::stateOff);  }

    void transitionTo(void (LampFSM::*next)()) {
        currentTick = next;
        // onEnter : action exécutée UNE SEULE FOIS à l'entrée
        if (next == &LampFSM::stateOff)  led.off();
        if (next == &LampFSM::stateEco)  led.setBrightness(128);
        if (next == &LampFSM::stateFull) led.setBrightness(255);
    }

  public:
    LampFSM(Led& l, Button& b) : led(l), btn(b), currentTick(&LampFSM::stateOff) {
        led.off(); // action onEnter de l'état initial
    }
    void update() { (this->*currentTick)(); }
};
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

✅ Analyse Niveau 2 : State Pattern

Forces :

  • Code très modulaire : ajouter un état = ajouter une méthode
  • Encapsulation parfaite de l'automate
  • Bon compromis RAM / lisibilité
  • Testable (on peut injecter des mocks de Led et Button)

Faiblesses :

  • Syntaxe des pointeurs de méthodes membres complexe :
    void (LampFSM::*ptr)() → difficile au premier abord
  • Les onEnter / onExit restent verbeux à coder
  • Structure de classe plutôt rigide

Recommandation : Pour les projets professionnels structurés sur ESP32.

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

⭐⭐⭐ Niveau 3 : Bibliothèque FSM dédiée (lib/FSM)

#include <FSM.h>   // lib/FSM/FSM.h — disponible dans res/lib/

enum State { OFF, ECO, FULL };

FSM    fsm;
Led    lampe(26);
Button btn(12);

void setup() {
    fsm.ADD_STATE(OFF).ADD_STATE(ECO).ADD_STATE(FULL);

    fsm.addTransition(OFF,  ECO,  []() { return btn.wasClicked(); })
       .addTransition(ECO,  FULL, []() { return btn.wasClicked(); })
       .addTransition(FULL, OFF,  []() { return btn.wasClicked(); });

    fsm.onEnter(OFF,  []() { lampe.off();              })
       .onEnter(ECO,  []() { lampe.setBrightness(128); })
       .onEnter(FULL, []() { lampe.setBrightness(255); });

    fsm.setDebugTransition(true);  // [T] OFF -> ECO sur Serial
    fsm.start(OFF);
}

void loop() { fsm.update(); }   // ← toute la mécanique est ici
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

🧩 L'API FSM.h — référence rapide

Méthode Rôle
fsm.ADD_STATE(S) Enregistre l'état S avec son nom string (#S)
fsm.addTransition(from, to, [](){...}) Règle : si lambda retourne true, on transite
fsm.onEnter(s, [](){...}) Callback exécuté une fois à l'entrée de l'état
fsm.onExit(s, [](){...}) Callback exécuté une fois à la sortie de l'état
fsm.timeInState() Millisecondes écoulées dans l'état courant
fsm.update() À appeler dans loop() — évalue toutes les transitions
fsm.setDebugTransition(true) Log Serial : [T] IDLE -> DOWN

Toutes les méthodes retournent FSM& → enchaînement fluent par .

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

📡 timeInState() — transitions temporelles

// Rester en STAMPING pendant exactement 1 seconde, puis monter
fsm.addTransition(STAMPING, UP, []() {
    return fsm.timeInState() >= 1000;
});

// Appui long 1.5 s sur START → passer en MAINTENANCE
Timer startTimer;
fsm.onEnter(START_PRESSED_IN, []() { startTimer.start(1500); })
   .addTransition(START_PRESSED_IN, MAINTENANCE, []() {
       return startTimer.elapsed() >= 1500;
   })
   .addTransition(START_PRESSED_IN, IDLE, []() {
       return digitalRead(START_BTN) == HIGH; // relâché avant 1.5 s
   });

Pas de delay() : update() tourne à chaque passage dans loop().
Réactivité < 1 ms (selon vitesse de la boucle).

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

💡 BlinkFSM — automate imbriqué non-bloquant

BlinkFSM est une LED clignotante implémentée avec… une FSM interne !

#include <BlinkFSM.h>  // lib/BlinkFSM/BlinkFSM.h

BlinkFSM stampBlinker(LED_STAMP, 100);  // pin 27, période 100 ms
BlinkFSM runBlinker  (LED_RUN,   500);  // pin LED_RUN, période 500 ms

// Brancher sur les callbacks de la FSM principale :
fsm.onEnter(STAMPING, []() { stampBlinker.start(); })
   .onExit (STAMPING, []() { stampBlinker.stop();  })
   .onEnter(IDLE,     []() { runBlinker.stop();    })
   .onExit (IDLE,     []() { runBlinker.start();   });

void loop() {
    fsm.update();
    stampBlinker.update();   // ← doit être appelé à chaque loop()
    runBlinker.update();
}

Pendant l'état STAMPING, la LED clignote à 10 Hz.
Zéro delay(), zéro blocage, le moteur descend en même temps.

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

⚠️ Lambdas & RAM sur MCU — ce qu'il faut savoir

std::function intègre une Small Buffer Optimization (SBO) :

sizeof(std::function<bool()>) ≈ 32 octets sur ESP32 (GCC xtensa)
┌───────────────────────────────────────────────────────┐
│  SBO inline buffer (~16 B)  │  vtable / fp overhead   │
└───────────────────────────────────────────────────────┘
Lambda Taille capture Stockage heap ?
[]() { return digitalRead(X)==LOW; } 0 B ❌ SBO
[this]() { return fsm.timeInState()>=T; } 4 B ❌ SBO
[&a, &b, &c, &d]() 16 B ❌ SBO (limite)
[bigObj1, bigObj2]() par valeur > 16 B ✅ malloc heap

Coût RAM réel de FSM.h (10 états, 12 transitions) :

  • std::vector<Transition> ≈ 12 × 40 B = 480 B
  • std::map enter/exit ≈ 20 × 40 B = 800 B (arbre rouge-noir)
  • Total ≈ 1.3 KB — acceptable sur ESP32 (520 KB RAM)

❌ ATmega328 (Arduino Uno) : 2 KB RAM total → utiliser Niveau 1 ou 2.
✅ ESP32 / STM32 : RAM largement suffisante.

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

✅ Analyse Niveau 3 : Bibliothèque FSM

Forces :

  • Zéro code de plomberie : plus de switch, plus de variables currentState
  • loop() ne fait que fsm.update() — toute la logique est dans setup()
  • Debug intégré : setDebugTransition(true) → log Serial automatique
  • Extensible : ajouter un état = 3 appels de méthodes
  • BlinkFSM : sous-automates réutilisables

Faiblesses :

  • Heap utilisé : std::map + std::vector + wrappers std::function ≈ 1-3 KB
  • Sur MCU SRAM ≤ 8 KB, préférer le Niveau 2 (State Pattern)

Recommandation : ESP32, STM32F4+, RP2040. ❌ Déconseillé sur Arduino Uno.

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

📊 Comparatif des 3 approches

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

📋 Tableau de synthèse

Critère Switch-Case State Pattern Lambdas
Complexité ⭐ Faible ⭐⭐ Moyenne ⭐⭐⭐ Haute
Maintenance Difficile Facile Très facile
RAM consommée Négligeable Faible ~1-3 KB (ESP32 ✅)
Testabilité Faible Bonne Bonne
Usage idéal ≤ 4 états Projets structurés Lib FSM réutilisable
Compatibilité Tous MCU ESP32, STM32 ESP32, STM32F4, RP2040
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

🏋️ Exercice FSM : Lampe Intelligente

Implémentez les 3 niveaux pour la lampe à 3 états (OFF → ECO → FULL).

Tâches :

  1. Niveau 1 : enum + switch/case → anti-rebond 300 ms sur le bouton
  2. Niveau 2 : encapsuler dans LampFSM avec pointeur de méthode membre
  3. Niveau 3 : utiliser la bibliothèque FSM.h (lib/FSM/) :
    • fsm.ADD_STATE(...), addTransition(...), onEnter(...), fsm.start(OFF)
    • Ajouter une BlinkFSM sur la LED en état ECO (clignotement lent)

Bonus :

  • Ajouter un 4e état BLINK (clignotement rapide) entre FULL et OFF
  • fsm.setDebugTransition(true) → vérifier le log Serial à chaque transition
  • Mesurer avec fsm.timeInState() la durée passée dans chaque état
Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

🎯 Synthèse du TP 3

Concept Retenir
enum State Nommer les états avec des constantes lisibles
switch/case Approche simple, efficace, limitée en scalabilité
Pointeur méthode void (Class::*ptr)() — approche modulaire
Lambda [&](){} Fonction anonyme capturant l'environnement
FSM.h (lib) API fluent : ADD_STATE, addTransition, onEnter/Exit, update()
BlinkFSM Sous-automate de clignotement non-bloquant
SBO std::function Captures ≤ 16 B → inline, pas de malloc heap

Prochain module → Capteurs, Signaux et Réseau

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico
📡 Microcontrôleurs & IoT

🧑‍💻 Questions ?

TP 3 — Machines à États (FSM)
Réda BOUREBABA & Sébastien Antonico

Microcontrôleurs & IoT - V0.1.2 - 12/03/2026 04:02 - Réda BOUREBABA r.bourebaba@ynov.com & Sébastien Antonico