Illustre les modules 4 à 6 (9 figures)

M4 : trame UART 8N1, câblage série croisé. M5 : bus I2C, trame I2C, bus SPI. M6 : tâches millis(), déroulé d'une ISR, modes de déclenchement, encodeur rotatif.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Artemia
2026-10-18 15:15:43 +02:00
co-authored by Claude Opus 5.5
parent 499d11402b
commit 372c5a3e1e
24 changed files with 16951 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
"""Module 4 — Communication série & débogage."""
from outils import boite, chronogramme, fleche, toile
from style import BLEU, ENCRE, GRIS, ORANGE, VERT, Figure
def trame_uart():
# 'A' = 0x41 = 0b01000001, envoyé bit de poids faible en premier
donnees = [(0x41 >> k) & 1 for k in range(8)]
bits = [1] + [0] + donnees + [1] + [1] # repos, start, D0..D7, stop, repos
points = [(i, b) for i, b in enumerate(bits)]
fig, ax, y = chronogramme([("TX", points)], 12, largeur_cm=16, hauteur_voie_cm=2.2)
ax.set_xticks([])
ax.spines["bottom"].set_visible(False)
noms = ["repos", "start"] + [f"D{k}" for k in range(8)] + ["stop", "repos"]
couleurs = [GRIS, ORANGE] + [BLEU] * 8 + [VERT, GRIS]
for i, (n, c) in enumerate(zip(noms, couleurs)):
ax.text(i + 0.5, 1.35, n, ha="center", fontsize=8, color=c, fontweight="bold")
if 2 <= i <= 9:
ax.text(i + 0.5, -0.35, str(bits[i]), ha="center", fontsize=9, color=ENCRE)
ax.axvline(i, ymin=0.12, ymax=0.83, color="#e5e7eb", lw=0.7, zorder=0)
ax.annotate("", xy=(2, 1.85), xytext=(10, 1.85), arrowprops=dict(arrowstyle="<->", lw=0.9, color=BLEU))
ax.text(6, 1.95, "8 bits de données : 'A' = 0x41, bit de poids faible (D0) en premier",
ha="center", va="bottom", fontsize=8, color=BLEU)
ax.annotate("", xy=(1, -0.75), xytext=(2, -0.75), arrowprops=dict(arrowstyle="<->", lw=0.9))
ax.text(2.15, -0.75, "1 bit = 1 / 115200 s ≈ 8,7 µs", va="center", fontsize=8, color=ENCRE)
ax.text(12, -0.75, "8N1 : 8 bits, pas de parité, 1 bit de stop", ha="right", va="center",
fontsize=8, color=GRIS)
ax.set_ylim(-1.1, 2.5)
fig.tight_layout()
return fig
def cablage_serie():
fig, ax = toile(16, 6.8)
pc = boite(ax, 1.8, 3.1, 3.0, 2.2, "Ordinateur\n\nMoniteur série", couleur=GRIS, taille=8.5)
esp = boite(ax, 8.0, 3.2, 4.0, 4.6, couleur=BLEU, fond="#f5f8fe")
ax.text(8.0, 5.1, "ESP32-S3", ha="center", fontsize=10, color=BLEU, fontweight="bold")
mod = boite(ax, 14.0, 3.0, 3.0, 3.8, couleur=ORANGE)
ax.text(14.0, 4.2, "Module externe\n(GPS, RFID…)", ha="center", va="center", fontsize=8.5, color=ENCRE)
# USB natif
boite(ax, 7.0, 4.2, 1.6, 0.6, "USB", couleur=GRIS, taille=8)
fleche(ax, (3.3, 4.2), (6.2, 4.2), "Serial\n(USB CDC)", double=True, couleur=VERT, taille=8)
# Broches Serial2
for yy, nom in ((3.0, "GPIO18 (TX)"), (2.0, "GPIO17 (RX)")):
ax.text(9.9, yy, nom, ha="right", va="center", fontsize=8.5, color=ENCRE)
for yy, nom in ((3.0, "RX"), (2.0, "TX")):
ax.text(12.7, yy, nom, ha="left", va="center", fontsize=8.5, color=ENCRE)
ax.text(9.9, 1.0, "GND", ha="right", va="center", fontsize=8.5, color=ENCRE)
ax.text(12.7, 1.0, "GND", ha="left", va="center", fontsize=8.5, color=ENCRE)
fleche(ax, (10.0, 3.0), (12.5, 2.0), couleur=BLEU)
fleche(ax, (12.5, 3.0), (10.0, 2.0), couleur=ORANGE)
ax.plot([10.0, 12.5], [1.0, 1.0], color=ENCRE, lw=1.3)
ax.text(11.25, 3.35, "croisé !", ha="center", fontsize=8.5, color=ORANGE, fontweight="bold")
ax.text(11.25, 0.55, "masse commune", ha="center", fontsize=7.5, color=GRIS)
ax.text(8.0, 0.15, "Serial2.begin(9600, SERIAL_8N1, 17, 18);", ha="center", fontsize=7.5,
color=GRIS, family="monospace")
return fig
FIGURES = [
Figure("m04-trame-uart", trame_uart),
Figure("m04-cablage-serie", cablage_serie),
]
+117
View File
@@ -0,0 +1,117 @@
"""Module 5 — Bus I2C et SPI."""
import matplotlib.pyplot as plt
from outils import boite, toile
from style import BLEU, CM, ENCRE, GRIS, ORANGE, VERT, Figure
def bus_i2c():
fig, ax = toile(16, 6.4)
maitre = boite(ax, 1.9, 3.2, 3.0, 2.6, couleur=BLEU)
ax.text(1.9, 3.75, "ESP32-S3", ha="center", fontsize=10, color=BLEU, fontweight="bold")
ax.text(1.9, 2.95, "maître\n(génère SCL)", ha="center", va="center", fontsize=8, color=ENCRE)
y_sda, y_scl = 4.0, 2.4
ax.plot([3.4, 15.6], [y_sda, y_sda], color=BLEU, lw=2)
ax.plot([3.4, 15.6], [y_scl, y_scl], color=ORANGE, lw=2)
ax.text(3.5, y_sda + 0.15, "SDA (GPIO8) — données", fontsize=8.5, color=BLEU, va="bottom")
ax.text(3.5, y_scl + 0.15, "SCL (GPIO9) — horloge", fontsize=8.5, color=ORANGE, va="bottom")
# Résistances de tirage
ax.plot([7.5, 9.5], [5.9, 5.9], color=ENCRE, lw=1.3)
ax.text(8.5, 6.05, "3V3", ha="center", fontsize=8, color=ENCRE)
for x, yb in ((7.8, y_sda), (9.2, y_scl)):
ax.plot([x, x], [5.9, 5.6], color=ENCRE, lw=1.3)
ax.add_patch(plt.Rectangle((x - 0.15, 4.7 if yb == y_sda else 4.2), 0.3, 0.9 if yb == y_sda else 1.4,
fill=False, ec=ENCRE, lw=1.3))
ax.plot([x, x], [4.7 if yb == y_sda else 4.2, yb], color=ENCRE, lw=1.3)
ax.plot(x, yb, "o", color=ENCRE, ms=4)
ax.text(9.5, 5.2, "résistances de tirage\n(souvent déjà sur les modules)", fontsize=7.5, color=GRIS, va="center")
# Esclaves
for x, nom, adr in ((6.0, "Écran OLED\nSSD1306", "0x3C"), (11.5, "Capteur\nBME280", "0x76"),
(14.6, "autre\nmodule", "0x??")):
boite(ax, x, 0.85, 2.7, 1.45, f"{nom}\nadresse {adr}", couleur=VERT, taille=7.2)
for dx, yb, c in ((-0.5, y_sda, BLEU), (0.5, y_scl, ORANGE)):
ax.plot([x + dx, x + dx], [1.5, yb], color=c, lw=1.5)
ax.plot(x + dx, yb, "o", color=c, ms=4)
ax.text(1.9, 1.05, "tous sur les 2 mêmes fils :\nchacun répond\nà son adresse", ha="center", va="center",
fontsize=7.2, color=GRIS, style="italic")
return fig
def trame_i2c():
# Écriture vers l'adresse 0x3C (0111100), R/W = 0, ACK, octet 0xAF, ACK
adresse = [0, 1, 1, 1, 1, 0, 0]
donnee = [1, 0, 1, 0, 1, 1, 1, 1]
bits = adresse + [0] + [0] + donnee + [0]
roles = ["adr"] * 7 + ["rw"] + ["ack"] + ["data"] * 8 + ["ack"]
fig, ax = plt.subplots(figsize=(16 * CM, 5.6 * CM))
t0 = 1.0
scl_x, scl_y, sda_x, sda_y = [0, t0], [1, 1], [0, 0.5], [1, 1]
sda_x += [0.5]
sda_y += [0] # START : SDA descend pendant que SCL est haut
scl_x += [t0]
scl_y += [0]
for k, b in enumerate(bits):
x = t0 + k
scl_x += [x + 0.5, x + 0.5, x + 1, x + 1]
scl_y += [0, 1, 1, 0]
sda_x += [x + 0.15, x + 0.15]
sda_y += [sda_y[-1], b]
fin = t0 + len(bits)
scl_x += [fin + 0.4, fin + 0.4, fin + 1.4]
scl_y += [0, 1, 1]
sda_x += [fin + 0.15, fin + 0.15, fin + 0.8, fin + 0.8, fin + 1.4]
sda_y += [sda_y[-1], 0, 0, 1, 1] # STOP : SDA monte pendant que SCL est haut
ax.plot(scl_x, [v + 2 for v in scl_y], color=ORANGE, lw=1.6)
ax.plot(sda_x, sda_y, color=BLEU, lw=1.6)
couleurs = {"adr": BLEU, "rw": BLEU, "ack": VERT, "data": BLEU}
for k, (b, r) in enumerate(zip(bits, roles)):
x = t0 + k
ax.axvspan(x, x + 1, ymin=0.08, ymax=0.42, color=couleurs[r], alpha=0.07 if r != "ack" else 0.2, lw=0)
ax.text(x + 0.5, -0.35, str(b), ha="center", fontsize=7.5, color=ENCRE)
groupes = [(0, 1, "START", ORANGE), (t0, t0 + 7, "adresse 0x3C (7 bits)", BLEU), (t0 + 7, t0 + 8, "R/W", BLEU),
(t0 + 8, t0 + 9, "ACK", VERT), (t0 + 9, t0 + 17, "donnée (8 bits)", BLEU),
(t0 + 17, t0 + 18, "ACK", VERT), (fin, fin + 1.4, "STOP", ORANGE)]
for a, b, txt, c in groupes:
ax.annotate("", xy=(a + 0.05, 3.45), xytext=(b - 0.05, 3.45),
arrowprops=dict(arrowstyle="|-|", color=c, lw=0.9, mutation_scale=3))
ax.text((a + b) / 2, 3.6, txt, ha="center", va="bottom", fontsize=7.3, color=c, fontweight="bold")
ax.text(-0.3, 2.5, "SCL", ha="right", va="center", fontsize=9, fontweight="bold", color=ORANGE)
ax.text(-0.3, 0.5, "SDA", ha="right", va="center", fontsize=9, fontweight="bold", color=BLEU)
ax.text(fin + 1.4, -0.75, "ACK (en vert) : c'est l'esclave qui tire SDA à 0 pour accuser réception",
ha="right", fontsize=7.5, color=VERT)
ax.set_xlim(-0.4, fin + 1.6)
ax.set_ylim(-0.95, 4.2)
ax.axis("off")
fig.tight_layout()
return fig
def bus_spi():
fig, ax = toile(16, 6.4)
boite(ax, 2.0, 3.2, 3.2, 4.4, couleur=BLEU)
ax.text(2.0, 4.55, "ESP32-S3\nmaître", ha="center", va="center", fontsize=9.5, color=BLEU, fontweight="bold")
lignes = [("SCK", 4.2, ORANGE, "horloge"), ("MOSI", 3.5, BLEU, "maître → esclave"),
("MISO", 2.8, VERT, "esclave → maître")]
for nom, yy, c, role in lignes:
ax.plot([3.6, 15.4], [yy, yy], color=c, lw=2)
ax.text(3.75, yy + 0.08, f"{nom} — {role}", fontsize=8, color=c, va="bottom")
esclaves = [(9.0, "Écran TFT", "CS1"), (13.5, "Carte SD", "CS2")]
for i, (x, nom, cs) in enumerate(esclaves):
boite(ax, x, 0.8, 2.8, 1.1, nom, couleur=GRIS, taille=8.5)
for dx, (_, yy, c, _r) in zip((-0.9, -0.3, 0.3), lignes):
ax.plot([x + dx, x + dx], [1.35, yy], color=c, lw=1.4)
ax.plot(x + dx, yy, "o", color=c, ms=3.5)
ycs = 1.9 - i * 0.45
ax.plot([3.6, x + 0.9, x + 0.9], [ycs, ycs, 1.35], color=ENCRE, lw=1.4, ls="--" if i else "-")
ax.text(3.75, ycs + 0.06, cs, fontsize=7.8,
color=ENCRE, va="bottom")
ax.text(15.4, 5.6, "SCK, MOSI, MISO : partagés\nCS (Chip Select) : une ligne par périphérique,\nle maître met à 0 celle de son interlocuteur",
ha="right", va="center", fontsize=7.8, color=GRIS, style="italic")
return fig
FIGURES = [
Figure("m05-bus-i2c", bus_i2c),
Figure("m05-trame-i2c", trame_i2c),
Figure("m05-bus-spi", bus_spi),
]
+122
View File
@@ -0,0 +1,122 @@
"""Module 6 — Interruptions matérielles & timers."""
import matplotlib.pyplot as plt
import numpy as np
from outils import chronogramme
from style import BLEU, CM, ENCRE, GRIS, ORANGE, ROUGE, VERT, Figure
def taches_millis():
fig, ax = plt.subplots(figsize=(16 * CM, 6.6 * CM))
duree = 5200
# loop() : itérations très rapides
ax.vlines(np.arange(0, duree, 40), 3.1, 3.7, color=GRIS, lw=0.6)
# LED toutes les 300 ms
t = np.arange(0, duree + 300, 300)
niveaux = np.arange(len(t)) % 2
ax.step(t, 2.0 + niveaux * 0.6, where="post", color=BLEU, lw=1.6)
# NeoPixel toutes les 1000 ms
teintes = [ROUGE, VERT, BLEU, "#f5f5f5", ROUGE, VERT]
for k in range(6):
ax.add_patch(plt.Rectangle((k * 1000, 0.95), min(1000, duree - k * 1000), 0.6,
facecolor=teintes[k], edgecolor=GRIS, lw=0.6, alpha=0.85))
# Debug toutes les 5000 ms
for x in (0, 5000):
ax.plot(x, 0.3, marker="v", color=ORANGE, ms=8)
ax.text(x + 60, 0.3, "Serial.print", fontsize=7, color=ORANGE, va="center")
noms = [(3.4, "loop()", GRIS), (2.3, "LED (300 ms)", BLEU), (1.25, "NeoPixel (1 s)", ENCRE),
(0.3, "debug (5 s)", ORANGE)]
for yy, n, c in noms:
ax.text(-80, yy, n, ha="right", va="center", fontsize=8.5, color=c, fontweight="bold")
ax.text(duree, 3.85, "loop() tourne en permanence : à chaque tour, on teste « l'heure est-elle venue ? »",
ha="right", fontsize=7.5, color=GRIS, style="italic")
ax.set_xlim(-50, duree)
ax.set_ylim(-0.1, 4.2)
ax.set_yticks([])
ax.spines["left"].set_visible(False)
ax.set_xticks(range(0, 5001, 1000), [f"{s} s" for s in range(6)])
ax.set_xlabel("millis()")
ax.grid(axis="y", visible=False)
fig.tight_layout()
return fig
def deroule_isr():
fig, ax = plt.subplots(figsize=(16 * CM, 5.0 * CM))
# Signal du bouton
ax.step([0, 3.0, 6.0, 10], [2.6, 1.9, 2.6, 2.6], where="post", color=BLEU, lw=1.6)
ax.text(-0.15, 2.25, "bouton\n(GPIO5)", ha="right", va="center", fontsize=8.5, fontweight="bold", color=BLEU)
ax.annotate("front descendant\n(FALLING)", xy=(3.0, 2.25), xytext=(1.2, 3.25), fontsize=7.5, color=BLEU,
arrowprops=dict(arrowstyle="-|>", color=BLEU, lw=0.8), ha="center")
# Exécution du processeur
y = 0.6
ax.broken_barh([(0, 3.0), (3.35, 6.65)], (y, 0.7), facecolors=BLEU, alpha=0.25, edgecolor=BLEU)
ax.broken_barh([(3.0, 0.35)], (y, 0.7), facecolors=ROUGE, edgecolor=ROUGE)
ax.broken_barh([(5.0, 1.2)], (y, 0.7), facecolors=VERT, alpha=0.55, edgecolor=VERT)
ax.text(-0.15, y + 0.35, "processeur", ha="right", va="center", fontsize=8.5, fontweight="bold", color=ENCRE)
ax.text(1.5, y + 0.35, "loop()", ha="center", va="center", fontsize=8)
ax.text(8.1, y + 0.35, "loop()", ha="center", va="center", fontsize=8)
ax.annotate("ISR très courte :\ninterruptionDetectee = true", xy=(3.17, y), xytext=(3.4, -0.55),
fontsize=7.5, color=ROUGE, arrowprops=dict(arrowstyle="-|>", color=ROUGE, lw=0.8))
ax.text(5.6, y + 0.35, "traitement", ha="center", va="center", fontsize=7.5)
ax.text(5.6, y + 0.95, "if (interruptionDetectee) …", ha="center", fontsize=7.2, color=VERT,
family="monospace")
ax.set_xlim(-1.6, 10)
ax.set_ylim(-0.8, 3.9)
ax.axis("off")
fig.tight_layout()
return fig
def modes_declenchement():
pts = [(0, 0), (1.5, 1), (4, 0), (6, 1), (8.5, 0)]
fig, ax, y = chronogramme([("signal", pts)], 10, largeur_cm=16, hauteur_voie_cm=3.4)
ax.set_xticks([])
ax.spines["bottom"].set_visible(False)
lignes = [("RISING", [1.5, 6], BLEU), ("FALLING", [4, 8.5], ORANGE), ("CHANGE", [1.5, 4, 6, 8.5], VERT)]
for k, (nom, xs, c) in enumerate(lignes):
yy = -0.7 - k * 0.7
ax.text(-0.25, yy, nom, ha="right", va="center", fontsize=8.5, fontweight="bold", color=c)
ax.plot([0, 10], [yy, yy], color="#e5e7eb", lw=0.8)
for x in xs:
ax.plot(x, yy, marker="o", color=c, ms=7)
ax.plot([x, x], [yy, 0], color=c, lw=0.6, ls=":")
ax.set_ylim(-3.0, 1.5)
ax.text(10, -2.8, "● = instant où l'ISR est appelée", ha="right", fontsize=7.5, color=GRIS)
fig.tight_layout()
return fig
def encodeur():
fig, axes = plt.subplots(1, 2, figsize=(16 * CM, 5.2 * CM))
cas = [("Rotation dans un sens", 0.0, "DT = HIGH → compteur++", BLEU),
("Rotation dans l'autre sens", 0.5, "DT = LOW → compteur--", ORANGE)]
t = np.linspace(0, 4, 2000)
for ax, (titre, dephasage, regle, c) in zip(axes, cas):
clk = ((t % 2) < 1).astype(float)
dt = (((t - 0.5 + dephasage * 2) % 2) < 1).astype(float)
ax.plot(t, clk + 1.6, color=BLEU, lw=1.6)
ax.plot(t, dt, color=GRIS, lw=1.6)
ax.text(-0.1, 2.1, "CLK", ha="right", va="center", fontsize=8.5, fontweight="bold", color=BLEU)
ax.text(-0.1, 0.5, "DT", ha="right", va="center", fontsize=8.5, fontweight="bold", color=GRIS)
for x in (1, 3):
ax.plot([x, x], [-0.25, 2.8], color=c, lw=1, ls="--")
etat = dt[np.searchsorted(t, x + 0.01)]
ax.plot(x, etat, "o", color=c, ms=6)
ax.set_title(titre, fontsize=9, loc="left")
ax.text(2, -0.75, regle, ha="center", fontsize=8.5, color=c, fontweight="bold")
ax.set_xlim(-0.6, 4)
ax.set_ylim(-1.0, 2.9)
ax.axis("off")
fig.text(0.5, 0.02, "À chaque front descendant de CLK (pointillés), l'ISR lit l'état de DT. "
"Le sens réel dépend du câblage.", ha="center", fontsize=7.5, color=GRIS, style="italic")
fig.tight_layout(rect=(0, 0.06, 1, 1))
return fig
FIGURES = [
Figure("m06-taches-millis", taches_millis),
Figure("m06-deroule-isr", deroule_isr),
Figure("m06-modes-declenchement", modes_declenchement),
Figure("m06-encodeur", encodeur),
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 48 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 70 KiB