Séance 29 · Partie E · Robotique
Régulation PID
et suivi de ligne.
Tenir une vitesse malgré la pente, suivre une ligne sans zigzaguer, garder un drone à l’horizontale : un même algorithme de dix lignes, inventé pour les navires en 1922, fait tout cela. C’est le PID, et il est dans chaque robot, chaque four, chaque régulateur de vitesse.
Durée : 2 séances · Objectifs : boucle fermée, les trois termes P, I, D et ce que chacun corrige, réglage (Ziegler-Nichols et méthode manuelle), saturation et anti-windup, PID de vitesse sur encodeur, PID de suivi de ligne, PID en cascade.
Ce que vous saurez faire à la fin
- Expliquer avec les mains ce que font P, I et D, et le montrer en simulation.
- Régler un PID méthodiquement sur un vrai robot.
- Asservir la vitesse des roues, puis le suivi de ligne, puis les deux ensemble.
- Reconnaître les pièges : saturation, bruit sur D, retard.
Matériel : le robot des séances 27–28 avec encodeurs et capteurs de ligne. Les simulations tournent dans la page.
01 / Boucle fermée
Le principe : mesurer, comparer, corriger
consigne ──▶(+)── erreur ──▶ [contrôleur] ── commande ──▶ [système] ──┬──▶ sortie
(−) │
▲ │
└──────────────────── mesure ◀───────────[capteur]◀──────┘
erreur = consigne − mesure
commande = f(erreur) ← le PID est un choix de fBoucle ouverte (séance 27) : « PWM 150 donne 20 cm/s » — vrai sur table, faux en pente, faux à batterie faible.
Boucle fermée : « je veux 20 cm/s ; je mesure 17 ; je pousse plus ». Le contrôleur ne connaît pas la pente : il voit l’erreur et la combat.
Robustesse : la boucle fermée corrige ce qu’on n’a pas prévu. C’est la raison de son omniprésence.
Vous en avez déjà fait
Séance 15 (chariot P et PD), séance 23 (luminosité), séance 27 (aller droit avec les encodeurs), séance 28 (suiveur de ligne proportionnel). Cette séance donne le cadre général et la méthode de réglage.
01 / Boucle fermée
Le banc d’essai : un moteur simulé
Le modèle
Un moteur réel ressemble à ce premier ordre : sa vitesse rejoint la valeur d’équilibre avec une constante de temps τ (séance 21, la charge d’un condensateur suit la même loi). Zone morte, bruit de mesure (encodeur grossier), retard de mesure (60 ms : le temps de compter les ticks et de filtrer) et perturbation (pente à t = 3 s) sont là pour rendre le problème réaliste — c’est le retard qui rend l’oscillation possible (séance 26). Tout ce qui suit s’exécute sur ce banc ; les cellules partagent leurs définitions, exécutez-les dans l’ordre.
02 / P, I, D
P : proportionnel à l’erreur
L’erreur statique
Avec P seul, la commande est nulle quand l’erreur est nulle : or il faut une PWM non nulle pour tenir 20 cm/s. Le système s’équilibre donc avec une erreur (d’autant plus petite que kp est grand) : c’est l’erreur statique. Augmenter kp la réduit mais amplifie le bruit et finit par faire osciller (à cause de l’inertie τ : la commande arrive « en retard »). Et la pente ajoute une erreur permanente que P ne peut pas annuler.
02 / P, I, D
I : intégrale, la mémoire de l’erreur
Ce que fait I
L’intégrale accumule l’erreur : tant qu’il reste une erreur, la commande continue de monter. À l’équilibre, l’erreur est forcément nulle — et l’intégrale a mémorisé la PWM nécessaire pour tenir la consigne, pente comprise. Trop de ki : l’intégrale a « trop d’élan » et dépasse (comme un thermostat qui chauffe trop). C’est le terme qui rend le robot exact ; c’est aussi celui qui pose le plus de problèmes (saturation, plus loin).
02 / P, I, D
D : dérivée, l’anticipation
Ce que fait D
La dérivée mesure la vitesse de variation : si la mesure fonce vers la consigne, D freine avant le dépassement. C’est l’amortisseur. Mais dériver une mesure bruitée amplifie le bruit (la commande devient hachée : mauvais pour les moteurs) : on filtre D, ou on le calcule sur plusieurs échantillons. Beaucoup de robots réels se contentent d’un PI ; D est indispensable pour les systèmes « rapides » (équilibre d’un drone, d’un pendule inversé).
03 / Régler
Méthode manuelle et Ziegler-Nichols
La méthode manuelle, sur le vrai robot
- ki = kd = 0. Monter kp jusqu’à une réponse rapide qui oscille un peu, puis réduire de moitié.
- Monter ki jusqu’à ce que l’erreur statique disparaisse en ~1 s sans trop de dépassement.
- Si ça dépasse ou oscille : un peu de kd, filtré.
- Tester avec des perturbations (pousser le robot, pente, batterie faible). Recommencer.
Ziegler-Nichols (1942) donne un point de départ agressif (dépassement de ~25 %) ; on affine ensuite. Chaque réglage est propre à un système : celui du robot A ne va pas au robot B. Notez vos gains dans un fichier de configuration, pas dans le code.
03 / Régler
Le piège majeur : saturation et windup
Windup
Pendant les 2 s de consigne inatteignable, la PWM est saturée à 255 mais l’intégrale continue de grossir (« s’enrouler »). Quand la consigne redescend, il faut des secondes pour la « dérouler » : le robot reste à fond bien après. Correction : ne pas intégrer quand la commande est saturée. C’est le bug PID le plus fréquent — un robot qui « ne s’arrête plus » après avoir été bloqué. Toujours : borner la commande et l’intégrale.
04 / Sur le robot
PID de vitesse par roue (firmware)
# MicroPython — pid.py : un PID réutilisable, avec tout ce qui précède
import time
class PID:
def __init__(self, kp, ki, kd, u_min=-1.0, u_max=1.0, filtre=0.7):
self.kp, self.ki, self.kd = kp, ki, kd
self.u_min, self.u_max, self.filtre = u_min, u_max, filtre
self.integrale = 0.0; self.prec = None; self.df = 0.0
def reset(self): self.integrale = 0.0; self.prec = None; self.df = 0.0
def calculer(self, consigne, mesure, dt):
e = consigne - mesure
d = 0.0 if self.prec is None else -(mesure - self.prec) / dt
self.prec = mesure; self.df = self.filtre * self.df + (1 - self.filtre) * d
u_brut = self.kp * e + self.ki * self.integrale + self.kd * self.df
u = max(self.u_min, min(self.u_max, u_brut))
if u == u_brut or (e > 0) != (u_brut > 0): self.integrale += e * dt
return u
# Dans la classe Robot (séance 27) : asservir chaque roue en cm/s
class RobotAsservi(Robot):
def __init__(self):
super().__init__()
self.pid_g = PID(0.02, 0.15, 0.0005); self.pid_d = PID(0.02, 0.15, 0.0005)
self.v_g_cible = self.v_d_cible = 0.0; self.t_prec = time.ticks_ms()
def vitesses(self, v_g, v_d): # cm/s
self.v_g_cible, self.v_d_cible = v_g, v_d
def maj(self):
now = time.ticks_ms(); dt = max(1, time.ticks_diff(now, self.t_prec)) / 1000; self.t_prec = now
tg = self.enc_g.lire_et_raz() * self.sens_g; td = self.enc_d.lire_et_raz() * self.sens_d
v_g, v_d = tg * self.CM_PAR_TICK / dt, td * self.CM_PAR_TICK / dt # mesures (bruitées : 1 cm/tick)
self.g.vitesse(self.pid_g.calculer(self.v_g_cible, v_g, dt))
self.d.vitesse(self.pid_d.calculer(self.v_d_cible, v_d, dt))
self.g.maj(); self.d.maj()
self._odometrie(tg, td) # extrait de Robot.majCadence et résolution
À 50 Hz avec 1 cm par tick, une roue à 20 cm/s donne 0,4 tick par cycle : la mesure de vitesse est très bruitée (0, 1 ou 2 ticks). Solutions : cadence du PID à 10–20 Hz (plus de ticks par cycle), ou mesurer la durée entre ticks avec ticks_us dans l’ISR, ou encodeurs plus fins. Les gains ci-dessus sont des ordres de grandeur pour une commande normalisée −1…1 : à régler avec la méthode manuelle et le tableau de bord (séance 26) qui trace consigne et mesure.
04 / Sur le robot
PID de suivi de ligne, puis cascade
Cascade
Le PID de ligne produit une rotation w ; il suppose que le robot tourne exactement de w. Or les roues ont leur propre dynamique. D’où la cascade : le PID de ligne (lent, 20 Hz) donne des consignes de vitesses v ± wL/2 aux PID de roues (rapides, 50 Hz) qui les réalisent. Chaque boucle est plus simple à régler, et la boucle interne absorbe pente et batterie. On règle toujours l’interne d’abord. Les drones ont trois niveaux : position → vitesse → attitude → vitesse angulaire des moteurs.
05 / Défis
Défi ★ — Régler sur le banc, puis sur le robot
Consigne
1. Sur le banc simulé : trouver, avec la méthode manuelle, un PID qui atteint 20 cm/s en moins de 0,5 s, avec moins de 10 % de dépassement, et qui absorbe la pente en moins de 1 s. Le vérifier avec un « score » automatique : temps de montée, dépassement, erreur RMS après la pente.
2. Sur le robot : implémenter RobotAsservi, tracer consigne/mesure avec le tableau de bord, régler. Pousser le robot avec la main : il doit résister.
Correction (score)
def score(ctrl):
t, v, u = essai(ctrl)
montee = t[np.argmax(v > 18)] if (v > 18).any() else np.inf
depassement = max(0, v[:150].max() - 20) / 20 * 100
apres_pente = np.sqrt(np.mean((v[t > 4] - 20) ** 2))
return montee, depassement, apres_pente
for nom, c in (("PI 8/80", PI(8, 80)), ("PID 12/100/0.4", PID_antiwindup(12, 100, 0.4)), ("PID 20/150/0.6", PID_antiwindup(20, 150, 0.6))):
m, d, a = score(c)
print(f"{nom:16} montée {m:.2f} s dépassement {d:4.1f} % RMS après pente {a:.2f}")Un score chiffré permet de comparer des réglages sans se fier à l’œil, et même de lancer une recherche automatique (grille ou optimisation) : c’est ce que font les outils d’auto-réglage industriels.
05 / Défis
Défi ★★ — Aller à un point, précisément
Consigne
Le robot doit rejoindre (x, y) donné, en partant de n’importe où, et s’y arrêter à ±2 cm avec le bon cap. Deux PID : un sur la distance (→ v, saturé à v_max, ralentissant à l’approche) et un sur l’erreur de cap vers la cible (→ w). Puis un troisième, une fois arrivé, pour aligner le cap final. Simuler avec le modèle de la séance 14 (odométrie parfaite), puis avec 3 % de bruit d’odométrie. Mesurer l’erreur d’arrivée sur 50 essais.
Direction
def aller_a(cible_x, cible_y, cap_final, dt=0.05):
pid_d, pid_cap = PIDsim(0.8, 0.0, 0.1, u_max=25), PIDsim(3.0, 0.0, 0.2, u_max=3.0) # version Python de la classe PID
x, y, cap = 0.0, 0.0, 0.0; phase = "aller"
for _ in range(600):
dx, dy = cible_x - x, cible_y - y; dist = math.hypot(dx, dy)
if phase == "aller":
err_cap = (math.atan2(dy, dx) - cap + math.pi) % (2 * math.pi) - math.pi
v = pid_d.calculer(0, -dist, dt) * max(0.0, math.cos(err_cap)) # ne pas avancer si on pointe à l'opposé
w = pid_cap.calculer(0, -err_cap, dt)
if dist < 2: phase = "aligner"
else:
err_cap = (cap_final - cap + math.pi) % (2 * math.pi) - math.pi
v, w = 0.0, pid_cap.calculer(0, -err_cap, dt)
if abs(err_cap) < math.radians(3): break
x += v * math.cos(cap) * dt; y += v * math.sin(cap) * dt; cap += w * dt
return math.hypot(cible_x - x, cible_y - y), math.degrees(abs(cap_final - cap))Le facteur cos(err_cap) empêche d’avancer tant que le robot ne regarde pas vers la cible : c’est une petite machine à états implicite. Avec du bruit d’odométrie, l’erreur d’arrivée reflète la dérive (séance 27) : ~3–5 cm pour un trajet d’un mètre. Pour faire mieux, il faut un repère externe (balise, ligne au sol, caméra).
05 / Défis
Défi ★★★ — Esprit prépa : le pendule inversé
Consigne
Le pendule inversé (un bâton en équilibre sur un chariot ; un gyropode ; une fusée à l’atterrissage) est instable : sans commande, il tombe. Équations (petits angles) : θ'' ≈ (g/l)·θ − (1/l)·a, où a est l’accélération du chariot que l’on commande.
1. Simuler (Euler, séance 15) avec un PID sur θ : trouver des gains qui le maintiennent debout depuis θ₀ = 10°, avec une accélération bornée à ±10 m/s².
2. Ajouter un bruit de mesure de 0,5° et un retard de mesure de 40 ms. Le PID tient-il ? Quel gain kd minimal ?
3. Montrer par simulation qu’un contrôleur P seul ne peut jamais stabiliser ce système (il oscille ou diverge quel que soit kp), et expliquer pourquoi le D est indispensable ici (indice : l’équation est du second ordre sans amortissement).
Direction
Avec P seul, l’équation devient θ'' = (g/l − kp/l)·θ : si kp < g, ça diverge ; si kp > g, c’est un oscillateur harmonique sans amortissement : ça oscille pour toujours (et le moindre retard fait diverger). Le terme D ajoute l’amortissement : θ'' + (kd/l)θ' + ((kp − g)/l)θ = 0, une équation du second ordre stable dès que kd > 0 et kp > g. En prépa, vous étudierez exactement cette équation (oscillateur amorti) et le critère de stabilité par les racines du polynôme caractéristique — puis, plus tard, la commande optimale (LQR) qui calcule les meilleurs gains. Le retard limite kd : trop de D avec un retard, et l’on corrige une pente périmée. C’est le problème que résout un gyropode 400 fois par seconde.
06 / Vérification
Quel terme du PID annule l’erreur due à une pente constante ?
Deux questions supplémentaires
1. Qu’est-ce que le windup ? L’intégrale qui grossit pendant que la commande est saturée, provoquant un dépassement long. Remède : ne pas intégrer en saturation.
2. Pourquoi filtrer le terme D ? Parce que dériver une mesure bruitée amplifie le bruit et hache la commande.
Référence
Les mots à retenir
| Mot | Définition |
|---|---|
| Consigne / mesure / erreur | Ce qu’on veut / ce qu’on a / la différence. |
| P | Commande proportionnelle à l’erreur ; laisse une erreur statique. |
| I | Somme des erreurs ; annule l’erreur statique ; risque de windup. |
| D | Vitesse de variation ; amortit ; sensible au bruit. |
| Saturation | Commande bornée par le matériel. |
| Anti-windup | Stopper l’intégration en saturation. |
| Cascade | PID lent qui donne la consigne d’un PID rapide. |
| Ziegler-Nichols | Réglage à partir du gain critique et de la période d’oscillation. |
Pour continuer
Le robot est précis
Il roule, perçoit, décide, se régule. Il reste à en faire un projet présentable — et à parler de la suite : le lycée, la prépa, l’X, l’ENS.
À faire chez soi
- Régler le PID de vitesse de votre robot et enregistrer la réponse à un échelon dans le journal. Tracer.
- Faire suivre une ligne à trois vitesses ; noter à partir de laquelle le PD décroche ; ajouter la cascade.
- Chercher « LQR pendule inversé » et regarder une vidéo de gyropode ou de fusée réutilisable en atterrissage : c’est le même problème.
← Séance 28SommaireSéance 30 : Projet final et orientation →