MakerSpace Amiens · Microcontrôleurs

Communiquer et structurer

CM3 : bus série, game loop et machine à états

Alban Petit

Au programme

  1. Pourquoi des bus
  2. UART, I2C, SPI
  3. Choisir son bus
  4. La game loop
  5. La machine à états
2UniLaSalle Amiens

Ce que vous saurez faire

À la fin de ce cours, vous saurez :

  • dire pourquoi un bus série existe, et distinguer UART, I2C et SPI
  • choisir le bus adapté à un périphérique, et le câbler correctement
  • écrire une game loop qui ne bloque jamais
  • reconnaître une situation où une machine à états s’impose
  • l’implémenter avec un enum et un switch

Les deux premiers font dialoguer la puce avec le monde ; les trois derniers gardent votre code lisible.

Les bus de communication Machine à états finis

3UniLaSalle Amiens

Pourquoi des bus

Faire dialoguer les composants

Un fil par information ?

Un écran couleur 240×240 reçoit plus de 100 000 pixels par image. Un capteur renvoie une température sur 16 bits. Une carte SD, des blocs de 512 octets.

Câbler un fil par bit serait impraticable : plus aucune broche disponible, une carte illisible, et des perturbations entre pistes.

Un bus série envoie les bits les uns après les autres, sur deux à quatre fils. On échange de la largeur contre du temps, et la puce est assez rapide pour que ça ne se voie pas.

Les bus de communication

5UniLaSalle Amiens

Trois bus, trois compromis

  • UART : le plus simple. Deux fils, un seul interlocuteur, pas d’horloge partagée
  • I2C : deux fils, plusieurs composants distingués par une adresse
  • SPI : quatre fils, une horloge, le plus rapide des trois

Aucun n’est meilleur dans l’absolu : chacun échange de la vitesse contre des fils, ou des fils contre de la complexité. Le choix se fait par le besoin.

Les bus de communication

6UniLaSalle Amiens

UART

Deux fils, point à point

Le plus simple des trois

flowchart LR
  subgraph ESP["ESP32-S3"]
    ETX["TX"]
    ERX["RX"]
  end
  subgraph PER["Périphérique"]
    PRX["RX"]
    PTX["TX"]
  end
  ETX -->|données| PRX
  PTX -->|données| ERX

Pas d’horloge partagée : c’est un bus asynchrone. La vitesse est fixée des deux côtés à l’avance, c’est le baud rate, en général 115 200 bauds.

Les bus de communication

8UniLaSalle Amiens
  Le croisement est obligatoire

Le TX d’un appareil se branche sur le RX de l’autre, et réciproquement. Un câblage TX vers TX ne transmet rien, et c’est silencieux : aucun message d’erreur ne vous préviendra, vous verrez seulement un module qui « ne répond pas ».

Les bus de communication

9UniLaSalle Amiens

Anatomie d’une trame

Sans horloge partagée, comment le récepteur sait-il quand lire ? La ligne est au repos à HIGH, et chaque octet est encadré :

  • un bit de start (passage à LOW) annonce le début
  • les bits de données, du poids faible au poids fort
  • un bit de parité facultatif, pour détecter une erreur
  • un ou deux bits de stop (retour à HIGH)

D’où la notation 8N1. Format ou vitesse qui diffèrent : c’est le charabia du moniteur série.

Les bus de communication

10UniLaSalle Amiens

Une trame, en vrai

Copie d'écran d'oscilloscope montrant une trame série, les curseurs mesurant la durée d'un bit

Les curseurs mesurent 103,6 µs par bit, soit 9600 bauds. Copie d’écran : Haji akhundov, via Wikimedia Commons, sous CC BY-SA 3.0.

Les bus de communication

11UniLaSalle Amiens

Là où vous l’utilisez déjà

Serial.begin(115200);                 // UART0, via l'USB
Serial.printf("x=%d y=%d\n", x, y);

Le moniteur série est un UART. C’est aussi le bus des modules GPS, Bluetooth ou GSM, et le moyen le plus simple de faire dialoguer deux microcontrôleurs.

Sur la carte, une puce USB-série (CP2102 ou CH340) fait la traduction entre l’UART de l’ESP32 et le port USB du PC. C’est elle qui a besoin d’un driver sous Windows.

Le port série sous VSCode Vérification de la toolchain

12UniLaSalle Amiens

I2C

Deux fils, plusieurs composants

Un bus, des adresses

Deux fils partagés : SDA (données) et SCL (horloge). Chaque composant a une adresse sur 7 bits.

  1. Start : le maître prend la main sur le bus
  2. Adresse + R/W : il désigne le composant, lecture ou écriture
  3. ACK : l’esclave répond ; un silence = personne à cette adresse
  4. Données : les octets circulent, chacun confirmé par un ACK
  5. Stop : le maître libère le bus

Les bus de communication

14UniLaSalle Amiens

Les pull-ups ne sont pas optionnelles

SDA et SCL sont en open-drain, comme au CM2 : les composants ne peuvent que tirer la ligne vers LOW, jamais vers HIGH. C’est précisément ce qui permet à plusieurs d’entre eux de partager le fil sans court-circuit.

Il faut donc deux résistances de pull-up (~4,7 kΩ) vers le 3,3 V. Sans elles, la ligne ne remonte jamais et rien ne communique.

La plupart des modules du commerce les intègrent déjà. Si vous en chaînez plusieurs, une seule paire suffit sur tout le bus : inutile de les cumuler.

Les bus de communication GPIO et monde numérique

15UniLaSalle Amiens

Qui est là ? Le scanner I2C

On ne connaît pas toujours l’adresse. On les balaie toutes :

for (byte adr = 1; adr < 127; adr++) {
  Wire.beginTransmission(adr);
  if (Wire.endTransmission() == 0) {
    Serial.printf("Trouvé : 0x%02X\n", adr);
  }
}

Absent du scan ? Le problème est le câblage ou les pull-ups.

Les bus de communication

16UniLaSalle Amiens

Vitesses standard

Mode Vitesse
Standard 100 kHz
Fast 400 kHz
Fast+ 1 MHz

Largement suffisant pour des capteurs ou un petit écran OLED monochrome. Bien trop lent pour un écran TFT couleur : à 400 kHz, une image de 240×240 pixels demanderait plus de deux secondes.

Les bus de communication

17UniLaSalle Amiens

SPI

Quatre fils, plein débit

Le bus de l’écran

Fil Rôle
MOSI Données du maître vers l’esclave
MISO Données de l’esclave vers le maître
SCK Horloge, générée par le maître
CS Chip Select : un fil par esclave

Transmission synchrone (l’horloge cadence chaque bit, donc pas de baud rate à accorder) et full-duplex (les deux sens en même temps).

Les bus de communication

19UniLaSalle Amiens

Un maître, plusieurs esclaves

graph LR
  M["ESP32-S3"] -->|"MOSI + SCK"| E1["Écran TFT"]
  M -->|"MOSI + SCK"| E2["Carte SD"]
  E1 -->|"MISO"| M
  E2 -->|"MISO"| M
  M -->|"CS1"| E1
  M -->|"CS2"| E2

Les trois premiers fils sont partagés ; seul le CS distingue les esclaves. Un seul CS actif à la fois, les autres composants se mettent en haute impédance.

De quelques MHz à 80 MHz sur ESP32-S3 : c’est ce qui rend un écran TFT fluide.

Les bus de communication

20UniLaSalle Amiens

Le câblage de l’écran

Signal Broche Rôle
CS GPIO10 Sélection de l’écran sur le bus
DC GPIO11 Bascule commande / donnée
RST GPIO12 Reset matériel de l’écran
SCLK GPIO13 Horloge SPI
MOSI GPIO14 Données CPU vers écran
BLK GPIO15 ou 3,3 V Rétroéclairage

MISO n’est pas câblé : un bus full-duplex utilisé en écriture seule.

Écran TFT SPI ST7789

21UniLaSalle Amiens

En pratique, vous n’écrirez pas le SPI

lib_deps =
    bodmer/TFT_eSPI@^2.5.0
build_flags =
    -DUSER_SETUP_LOADED=1
    -include User_Setup.h
TFT_eSPI tft = TFT_eSPI();
tft.init();
tft.fillScreen(TFT_BLACK);
  Le point de blocage classique

Un User_Setup.h mal configuré donne un écran noir, pas une erreur de compilation. Si l’écran reste éteint alors que tout compile, c’est là qu’il faut regarder en premier : le pilote déclaré et les numéros de broches.

Écran SPI et game loop

22UniLaSalle Amiens

Choisir son bus

Le tableau de bord

Critère UART I2C SPI
Fils 2 2 4 et plus
Vitesse ~1 Mbit/s 100 k à 1 Mbit/s 10 à 80 Mbit/s
Plusieurs composants non oui, par adresse oui, un CS chacun
Full-duplex non non oui
Horloge partagée non oui oui
Complexité faible moyenne moyenne
Usage type debug, modules capteurs, OLED écrans, carte SD

Les bus de communication

24UniLaSalle Amiens

L’arbre de décision

flowchart LR
  B{"Besoin de<br/>vitesse ?"} -->|Oui| C["SPI<br/>écran, carte SD"]
  B -->|Non| D{"Plusieurs<br/>composants ?"}
  D -->|Oui| E["I2C<br/>capteurs, OLED"]
  D -->|Non| F["UART<br/>debug, GPS, BT"]

Notre projet : UART pour le moniteur série, SPI pour l’écran. ↓ pour la page.

Les bus de communication

25UniLaSalle Amiens

Le concept en ligne

25.1UniLaSalle Amiens

La game loop

Ne jamais bloquer

Lire, mettre à jour, redessiner


unsigned long dernierUpdate = 0;
const unsigned long INTERVALLE = 16;   // ~60 images par seconde

void loop() {
  unsigned long maintenant = millis();
  if (maintenant - dernierUpdate >= INTERVALLE) {
    dernierUpdate = maintenant;
    lireEntrees();
    mettreAJourJeu();
    redessiner();
  }
}

Cette charpente ne changera plus : le Pong puis le Snake enrichiront ces trois fonctions.

Écran SPI et game loop

27UniLaSalle Amiens

Pourquoi 16 ms

16 ms

par image, soit 60 images par seconde. En dessous, l’œil voit saccader.

4 M

cycles CPU disponibles dans ces 16 ms, à 240 MHz. De quoi faire beaucoup.

Si une image prend plus de 16 ms à calculer, le jeu ralentit au lieu de sauter des images. Premier suspect en cas de lenteur : un delay() oublié, ou un redessin complet de l’écran au lieu des seules zones modifiées.

Écran SPI et game loop

28UniLaSalle Amiens

La machine à états

Sortir du plat de spaghettis

Le code qui s’emmêle

Un jeu a plusieurs moments de vie : un menu d’accueil, une partie en cours, un écran de fin. Gérés à la main, ils donnent ceci :

bool enPartie = false;
bool gameOver = false;
bool menuActif = true;
// et ça empire à chaque nouvelle phase

Que veut dire enPartie && gameOver ? Rien. Et pourtant rien ne l’empêche.

Machine à états finis

30UniLaSalle Amiens

Une FSM, c’est trois choses

  • un ensemble fini d’états, des transitions, un seul état actif
stateDiagram-v2
  [*] --> MENU
  MENU --> PARTIE : bouton START
  PARTIE --> GAME_OVER : vie == 0
  GAME_OVER --> MENU : bouton RETRY

Machine à états finis

31UniLaSalle Amiens

La même chose, en tableau

État \ Événement START vie == 0 RETRY
MENU PARTIE - -
PARTIE - GAME_OVER -
GAME_OVER - - MENU

Une case « - » veut dire « événement ignoré dans cet état ». Le tableau se traduit ligne à ligne en switch/case, et révèle les cas oubliés.

Le dessiner avant de coder prend cinq minutes, et en fait gagner deux heures.

Machine à états finis

32UniLaSalle Amiens

enum et switch

enum EtatJeu { MENU, PARTIE, GAME_OVER };
EtatJeu etat = MENU;                      // l'état courant, un seul

void loop() {
  lireEntrees();
  switch (etat) {
    // un case par état
  }
}

Un enum nomme les états, une variable retient celui qui est actif. Le switch fait le reste.

Machine à états finis

33UniLaSalle Amiens

Un case par état


case MENU:
  afficherMenu();
  if (boutonStart()) { initialiserPartie(); etat = PARTIE; }
  break;
case PARTIE:
  mettreAJourJeu();
  afficherJeu();
  if (vieJoueur == 0) etat = GAME_OVER;
  break;
case GAME_OVER:
  afficherGameOver();
  if (boutonRetry()) etat = MENU;

Un seul état par case : ce qu’il affiche, et vers où il bascule.

Machine à états finis

34UniLaSalle Amiens
  Deux sens du mot « état »

Dans la même séance, le mot état désigne deux choses : les données du jeu (position de la balle, score, ce sont des variables) et la phase du jeu (MENU, PARTIE, GAME_OVER, c’est la FSM). Gardez la distinction en tête pour ne pas les mélanger.

Machine à états finis

35UniLaSalle Amiens

Faire une chose une seule fois

Réinitialiser le score ou effacer l’écran ne doit pas se répéter 60 fois par seconde. On l’exécute au moment de la transition, pas dans le case :

void allerVers(EtatJeu nouvel) {
  etat = nouvel;
  entreeEtat = millis();                        // date d'entrée
  if (nouvel == PARTIE)    initialiserPartie();
  if (nouvel == GAME_OVER) afficherGameOver();
}

C’est l’action d’entrée (onEnter). Toutes les transitions passent par là.

Machine à états finis

36UniLaSalle Amiens

Un état qui bascule tout seul

L’écran « GAME OVER » revient au menu après trois secondes, sans bloquer quoi que ce soit :

case GAME_OVER:
  if (millis() - entreeEtat > 3000) {
    allerVers(MENU);
  }
  break;

FSM et millis() se combinent naturellement : c’est la transition temporisée. La date d’entrée dans l’état, mémorisée par allerVers(), sert de référence.

Machine à états finis Du matériel au logiciel

37UniLaSalle Amiens

L’exemple canonique : le feu tricolore

stateDiagram-v2
  [*] --> VERT
  VERT --> ORANGE : 5 s
  ORANGE --> ROUGE : 2 s
  ROUGE --> VERT : 5 s

Trois états, des transitions purement temporisées, aucun bouton.

Machine à états finis

38UniLaSalle Amiens

Le feu tricolore, en entier

void loop() {
  unsigned long ec = millis() - entreeEtat;

  switch (etat) {
    case VERT:   if (ec > 5000) allerVers(ORANGE); break;
    case ORANGE: if (ec > 2000) allerVers(ROUGE);  break;
    case ROUGE:  if (ec > 5000) allerVers(VERT);   break;
  }
}

Aucun delay() : le programme pourrait aussi lire un bouton piéton.

Machine à états finis

39UniLaSalle Amiens

Ce que la FSM vous fait gagner

Approche Lisibilité Extensibilité Bugs typiques
Booléens faible difficile états contradictoires
FSM enum élevée un case de plus aucun

Au projet, cette FSM s’étendra aux états réseau du Snake multijoueur.

Machine à états finis

40UniLaSalle Amiens

Activité : la FSM de votre Pong

En binôme10 minSur papier

Dessinez la machine à états de votre jeu

  1. Quels sont les états ? (MENU, PARTIE, PAUSE ?, GAME_OVER)
  2. Quels événements déclenchent chaque transition ?
  3. Quel est l’état initial ?
  4. Remplissez la table : y a-t-il des cases oubliées ?

Comparez avec le binôme voisin : les différences sont instructives.

Machine à états finis

41UniLaSalle Amiens

À retenir

  • Un bus série envoie les bits l’un après l’autre
  • UART : 2 fils, croisement TX/RX, même baud des deux côtés
  • I2C : 2 fils, adressage, pull-ups ~4,7 kΩ obligatoires
  • SPI : 4 fils, un CS par esclave, le seul rapide pour un TFT
  • La game loop cadence à 16 ms avec millis(), sans jamais bloquer
  • Une FSM = états + transitions + un seul état actif
  • Action d’entrée à la transition, jamais dans le case

Les bus de communication Machine à états finis

42UniLaSalle Amiens

Où tout cela vous mène

Le projetEn binôme

  • Un Snake multijoueur : la console d’un côté, un navigateur de l’autre
  • Une mécanique neuve, les mêmes briques : GPIO, ADC, SPI, FSM
  • La FSM étendue aux états réseau : connexion, attente, partie, déconnexion
  • Un PCB conçu sous KiCad et soudé, pour remplacer la breadboard

Tout ce que vous venez de voir se retrouve là. Le projet n’ajoute que le réseau.

L’atelier Microcontrôleurs Collisions, score et machine à états

43UniLaSalle Amiens
UniLaSalle Amiens, Demain commence ici