diff --git a/.gitignore b/.gitignore index d6984c2..329aee3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ build/ .env secrets.h *.key + +# Environnement Python +.venv/ +__pycache__/ diff --git a/schemas/generer.py b/schemas/generer.py new file mode 100644 index 0000000..9bf2c07 --- /dev/null +++ b/schemas/generer.py @@ -0,0 +1,47 @@ +"""Génère les schémas (SVG + PNG) de tous les modules, ou de ceux passés en argument. + +Usage : python schemas/generer.py [m03 ...] +""" +import importlib +import sys +from pathlib import Path + +import matplotlib.pyplot as plt + +ICI = Path(__file__).resolve().parent +sys.path.insert(0, str(ICI)) + +DOSSIER_SVG = ICI / "svg" +DOSSIER_PNG = ICI / "png" + + +def modules_figures(filtres=()): + """Renvoie {préfixe 'mXX': [Figure, ...]} pour chaque fichier mXX_*.py.""" + resultat = {} + for fichier in sorted(ICI.glob("m[0-9][0-9]_*.py")): + prefixe = fichier.stem[:3] + if filtres and prefixe not in filtres: + continue + resultat[prefixe] = importlib.import_module(fichier.stem).FIGURES + return resultat + + +def enregistrer(fig, id_): + if hasattr(fig, "fig"): # dessin schemdraw (backend matplotlib) + fig = fig.fig + fig.savefig(DOSSIER_SVG / f"{id_}.svg") + fig.savefig(DOSSIER_PNG / f"{id_}.png", dpi=200) + plt.close(fig) + + +def main(): + DOSSIER_SVG.mkdir(exist_ok=True) + DOSSIER_PNG.mkdir(exist_ok=True) + for prefixe, figures in modules_figures(sys.argv[1:]).items(): + for f in figures: + enregistrer(f.dessiner(), f.id) + print(f"{f.id} : OK") + + +if __name__ == "__main__": + main() diff --git a/schemas/m03_pwm_adc.py b/schemas/m03_pwm_adc.py new file mode 100644 index 0000000..7799da0 --- /dev/null +++ b/schemas/m03_pwm_adc.py @@ -0,0 +1,153 @@ +"""Module 3 — Signaux analogiques : PWM & ADC.""" +import numpy as np +import matplotlib.pyplot as plt +import schemdraw +import schemdraw.elements as elm + +from style import BLEU, ORANGE, GRIS, ENCRE, VERT, CM, Figure + +schemdraw.use("matplotlib") + + +def _signal_carre(rapport, periodes=3, points=2000): + t = np.linspace(0, periodes, points, endpoint=False) + return t, np.where((t % 1) < rapport, 3.3, 0.0) + + +def pwm_rapport_cyclique(): + fig, axes = plt.subplots(3, 1, figsize=(16 * CM, 11 * CM), sharex=True) + for ax, rapport in zip(axes, (0.25, 0.50, 0.75)): + t, v = _signal_carre(rapport) + moyenne = 3.3 * rapport + ax.fill_between(t, v, step=None, color=BLEU, alpha=0.12) + ax.plot(t, v, color=BLEU) + ax.axhline(moyenne, color=ORANGE, ls="--", lw=1.8) + ax.text(3.04, moyenne, f"moyenne\n{round(moyenne + 1e-9, 2):.2f} V".replace(".", ","), + color=ORANGE, va="center", fontsize=9, fontweight="bold") + ax.set_ylim(-0.3, 4.1) + ax.set_yticks([0, 3.3], ["0 V", "3,3 V"]) + ax.set_title(f"Rapport cyclique {int(rapport * 100)} % → analogWrite(pin, {round(rapport * 255)})", + loc="left", fontsize=10, pad=16 if rapport == 0.25 else 6) + ax.grid(axis="x") + # Cotes T et t_on sur le premier graphe + ax = axes[0] + ax.annotate("", xy=(1, 3.75), xytext=(2, 3.75), + arrowprops=dict(arrowstyle="<->", color=ENCRE, lw=1)) + ax.text(1.5, 3.85, "période T", ha="center", va="bottom", fontsize=8.5, color=ENCRE) + ax.annotate("", xy=(0, 3.75), xytext=(0.25, 3.75), + arrowprops=dict(arrowstyle="<->", color=ENCRE, lw=1)) + ax.text(0.125, 3.85, "t$_{on}$", ha="center", va="bottom", fontsize=8.5, color=ENCRE) + axes[-1].set_xticks([0, 1, 2, 3], ["0", "T", "2T", "3T"]) + axes[-1].set_xlabel("temps (ex. T = 0,2 ms à 5 kHz)") + fig.tight_layout() + return fig + + +def servo_50hz(): + fig, axes = plt.subplots(3, 1, figsize=(16 * CM, 9.5 * CM), sharex=True) + cas = ((1.0, "0°"), (1.5, "90°"), (2.0, "180°")) + t = np.linspace(0, 40, 4000, endpoint=False) + for ax, (largeur, angle) in zip(axes, cas): + v = np.where((t % 20) < largeur, 3.3, 0.0) + ax.plot(t, v, color=BLEU, lw=1.8) + ax.fill_between(t, v, color=BLEU, alpha=0.12) + ax.set_ylim(-0.3, 4.3) + ax.set_yticks([0, 3.3], ["0 V", "3,3 V"]) + ax.text(22.8, 2.0, f"impulsion {str(largeur).replace('.', ',')} ms → bras à {angle}", + fontsize=9, color=ENCRE, va="center") + ax.grid(axis="x") + ax = axes[0] + ax.annotate("", xy=(0, 3.85), xytext=(20, 3.85), + arrowprops=dict(arrowstyle="<->", color=ORANGE, lw=1.2)) + ax.text(10, 3.95, "période = 20 ms (50 Hz)", ha="center", va="bottom", + fontsize=9, color=ORANGE, fontweight="bold") + axes[-1].set_xticks([0, 5, 10, 15, 20, 25, 30, 35, 40]) + axes[-1].set_xlabel("temps (ms)") + fig.tight_layout() + return fig + + +def montage_variateur(): + d = schemdraw.Drawing(show=False) + d.config(fontsize=11, unit=2.5, color=ENCRE) + ic = elm.Ic( + pins=[ + # schemdraw empile les broches de gauche de bas en haut + elm.IcPin(name="GND", side="left", anchorname="GNDL"), + elm.IcPin(name="GPIO1 (ADC1)", side="left", anchorname="ADC"), + elm.IcPin(name="3V3", side="left", anchorname="V33"), + elm.IcPin(name="GPIO4 (PWM)", side="right", anchorname="PWM", slot="1/3"), + ], + size=(6.5, 4.2), pinfontsize=10, lblsize=13, + ).label("ESP32-S3", loc="top", fontsize=14).color(BLEU) + d += ic + # Potentiomètre 10 kΩ entre 3V3 et GND, curseur vers GPIO1 + haut = ic.V33 + bas = ic.GNDL + d += elm.Line().at(haut).left().length(2.2) + p_haut = d.here + d += elm.Line().at(bas).left().length(2.2) + p_bas = d.here + d += (pot := elm.Potentiometer().at(p_haut).down().toy(p_bas).label("10 kΩ", loc="top").color(VERT)) + d += elm.Wire("-").at(pot.tap).to((ic.ADC[0], pot.tap[1])).color(VERT) + d += elm.Ground().at(p_bas) + # Sortie PWM : résistance 220 Ω puis LED vers GND + d += elm.Line().at(ic.PWM).right().length(1) + d += elm.Resistor().right().label("220 Ω").color(ORANGE) + d += elm.LED().down().label("LED", loc="bottom").color(ORANGE) + d += elm.Ground() + return d.draw(show=False) + + +def adc_quantification(): + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16 * CM, 7.5 * CM), + gridspec_kw={"width_ratios": [1, 1.25]}) + # (a) principe de la quantification, illustré sur 3 bits + v = np.linspace(0, 3.3, 1000) + niveaux = 8 + code = np.minimum(np.floor(v / 3.3 * niveaux), niveaux - 1) + ax1.plot(v, v / 3.3 * (niveaux - 1), color=GRIS, ls=":", lw=1.3, label="tension réelle") + ax1.step(v, code, where="post", color=BLEU, label="valeur numérique") + ax1.set_xlim(0, 3.3) + ax1.set_yticks(range(niveaux)) + ax1.set_xlabel("tension d'entrée (V)") + ax1.set_ylabel("valeur lue") + ax1.set_title("(a) Principe : la tension est\ndécoupée en paliers", loc="left", fontsize=10) + ax1.text(2.35, 0.6, "illustré avec 8 paliers (3 bits)\nl'ESP32-S3 en a 4096 (12 bits)", + fontsize=7.5, color=GRIS, ha="center") + ax1.legend(fontsize=7.5, loc="upper left", frameon=False) + # (b) chaîne de conversion utilisée dans l'exercice + ax2.plot([0, 3.3], [0, 4095], color=BLEU) + ax2.set_xlim(0, 3.3) + ax2.set_ylim(0, 4095) + ax2.set_yticks([0, 1024, 2048, 3072, 4095]) + ax2.set_xlabel("tension d'entrée (V)") + ax2.set_ylabel("analogRead()", color=BLEU) + sec = ax2.secondary_yaxis("right", functions=(lambda x: x * 255 / 4095, lambda y: y * 4095 / 255)) + sec.set_ylabel("map() → PWM", color=ORANGE) + sec.set_yticks([0, 85, 170, 255]) + sec.tick_params(colors=ORANGE) + sec.spines["right"].set_visible(True) + sec.spines["right"].set_color(ORANGE) + ax2.plot([1.65, 1.65, 3.3], [0, 2048, 2048], color=ORANGE, ls="--", lw=1.2) + ax2.plot(1.65, 2048, "o", color=ORANGE) + ax2.text(1.75, 1150, "1,65 V → 2048\nmap() → 127", color=ORANGE, fontsize=8.5, fontweight="bold") + ax2.set_title("(b) Lecture puis mise à l'échelle", loc="left", fontsize=10) + fig.tight_layout() + return fig + + +FIGURES = [ + Figure("m03-pwm-rapport-cyclique", + "Le rapport cyclique fixe la tension moyenne perçue par la charge (LED, moteur)", + "1. Qu'est-ce que le PWM ?", pwm_rapport_cyclique), + Figure("m03-servo-50hz", + "Commande d'un servomoteur : la largeur de l'impulsion (1 à 2 ms) fixe l'angle", + "2.2 Approche explicite : API LEDC (contrôle fin)", servo_50hz, largeur_cm=15), + Figure("m03-montage-variateur", + "Câblage de l'exercice : potentiomètre sur GPIO1 (ADC1), LED sur GPIO4 (PWM)", + "3. Exercice : variateur de luminosité avec potentiomètre", montage_variateur, largeur_cm=14), + Figure("m03-adc-quantification", + "Conversion analogique-numérique puis mise à l'échelle avec map()", + "4. Le convertisseur analogique-numérique (ADC)", adc_quantification), +] diff --git a/schemas/png/m03-adc-quantification.png b/schemas/png/m03-adc-quantification.png new file mode 100644 index 0000000..cf8274b Binary files /dev/null and b/schemas/png/m03-adc-quantification.png differ diff --git a/schemas/png/m03-montage-variateur.png b/schemas/png/m03-montage-variateur.png new file mode 100644 index 0000000..4594067 Binary files /dev/null and b/schemas/png/m03-montage-variateur.png differ diff --git a/schemas/png/m03-pwm-rapport-cyclique.png b/schemas/png/m03-pwm-rapport-cyclique.png new file mode 100644 index 0000000..cebeab8 Binary files /dev/null and b/schemas/png/m03-pwm-rapport-cyclique.png differ diff --git a/schemas/png/m03-servo-50hz.png b/schemas/png/m03-servo-50hz.png new file mode 100644 index 0000000..56f362f Binary files /dev/null and b/schemas/png/m03-servo-50hz.png differ diff --git a/schemas/requirements.txt b/schemas/requirements.txt new file mode 100644 index 0000000..2381c86 --- /dev/null +++ b/schemas/requirements.txt @@ -0,0 +1,3 @@ +schemdraw>=0.23 +matplotlib>=3.11 +lxml>=6.1 diff --git a/schemas/style.py b/schemas/style.py new file mode 100644 index 0000000..e2200bb --- /dev/null +++ b/schemas/style.py @@ -0,0 +1,51 @@ +"""Charte graphique commune à tous les schémas de la formation.""" +from dataclasses import dataclass +from typing import Callable + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 + +# Palette +BLEU = "#1f5fa8" # signal principal +ORANGE = "#e07a1f" # valeur moyenne, mise en évidence +VERT = "#2e8b57" +ROUGE = "#c0392b" +GRIS = "#6b7280" +GRIS_CLAIR = "#e5e7eb" +ENCRE = "#1f2937" # texte + +CM = 1 / 2.54 # conversion cm -> pouces pour figsize + +plt.rcParams.update({ + "font.family": "DejaVu Sans", + "font.size": 10, + "axes.edgecolor": GRIS, + "axes.labelcolor": ENCRE, + "axes.titlesize": 11, + "axes.titleweight": "bold", + "axes.titlecolor": ENCRE, + "axes.spines.top": False, + "axes.spines.right": False, + "axes.grid": True, + "grid.color": GRIS_CLAIR, + "grid.linewidth": 0.8, + "xtick.color": GRIS, + "ytick.color": GRIS, + "lines.linewidth": 2, + "svg.fonttype": "path", # texte vectorisé : rendu identique partout + "savefig.bbox": "tight", + "savefig.pad_inches": 0.1, + "savefig.facecolor": "white", +}) + + +@dataclass +class Figure: + """Déclaration d'une figure à générer puis insérer dans un support.""" + id: str # ex. "m03-pwm-duty" ; le préfixe mXX donne le module + legende: str # texte de la légende (sans « Figure X.Y — ») + ancre: str # texte exact du titre de section concerné + dessiner: Callable # renvoie une figure matplotlib ou un dessin schemdraw + largeur_cm: float = 16.0 diff --git a/schemas/svg/m03-adc-quantification.svg b/schemas/svg/m03-adc-quantification.svg new file mode 100644 index 0000000..e932dcc --- /dev/null +++ b/schemas/svg/m03-adc-quantification.svg @@ -0,0 +1,2635 @@ + + + + + + + + 2026-10-18T14:38:22.443124 + image/svg+xml + + + Matplotlib v3.11.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/svg/m03-montage-variateur.svg b/schemas/svg/m03-montage-variateur.svg new file mode 100644 index 0000000..db7dd37 --- /dev/null +++ b/schemas/svg/m03-montage-variateur.svg @@ -0,0 +1,720 @@ + + + + + + + + 2026-10-18T14:38:22.285543 + image/svg+xml + + + Matplotlib v3.11.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/svg/m03-pwm-rapport-cyclique.svg b/schemas/svg/m03-pwm-rapport-cyclique.svg new file mode 100644 index 0000000..2bb124b --- /dev/null +++ b/schemas/svg/m03-pwm-rapport-cyclique.svg @@ -0,0 +1,14076 @@ + + + + + + + + 2026-10-18T14:38:21.884270 + image/svg+xml + + + Matplotlib v3.11.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/svg/m03-servo-50hz.svg b/schemas/svg/m03-servo-50hz.svg new file mode 100644 index 0000000..c90cb28 --- /dev/null +++ b/schemas/svg/m03-servo-50hz.svg @@ -0,0 +1,25726 @@ + + + + + + + + 2026-10-18T14:38:22.123535 + image/svg+xml + + + Matplotlib v3.11.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +