Les supports .odt restent récupérables via le tag avant-quarto. Les figures ne déclarent plus que leur identifiant et leur fonction de dessin (légende et emplacement dans le .qmd). Génération SVG reproductible (sans date, identifiants stables) pour des diffs git propres. README : mode d'emploi Quarto et schémas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
146 lines
6.4 KiB
Python
146 lines
6.4 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", pwm_rapport_cyclique),
|
|
Figure("m03-servo-50hz", servo_50hz),
|
|
Figure("m03-montage-variateur", montage_variateur),
|
|
Figure("m03-adc-quantification", adc_quantification),
|
|
]
|