Charte commune (style.py), générateur SVG/PNG (generer.py) et 4 figures pour le module PWM & ADC : rapport cyclique, servo 50 Hz, montage du variateur, quantification ADC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
7.0 KiB
Python
154 lines
7.0 KiB
Python
"""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),
|
|
]
|