===== TP – Asservissement en distance du mBot : Correcteur PID ===== ==== Objectifs ==== * Comprendre la logique d’un asservissement en boucle fermée * Utiliser le capteur ultrason pour mesurer une distance réelle * Étudier séparément les rôles des termes P, I et D * Analyser la stabilité, le dépassement, le bruit et l’erreur statique * Interpréter les données fournies dans le moniteur série ==== Prérequis ==== Lirairie mBot: [[bibliotheque_arduino_mbot]] ==== Activité===== {{ https://www.lextronic.fr/41997-large_default/robot-mbot-explorer-bluetooth-p1050015.jpg }} Forcer le mBot à suivre une cible à 20 cm de distance. ==== 1) Principe général de l’asservissement ==== Le robot doit se stabiliser à **20 cm** de l’obstacle. L’erreur est définie par : e = D_{mes} - D_{cons} * si e > 0 → le robot est trop loin → avancer * si e < 0 → le robot est trop près → reculer La commande est donnée par un correcteur PID : v = K_p e + K_i \int e\, dt + K_d \frac{de}{dt} ===== Rôle des trois termes ===== * **P** : corrige proportionnellement à l’erreur * **I** : supprime l’erreur statique mais peut provoquer des oscillations * **D** : réduit le dépassement, amortit le mouvement ===== Filtrage du dérivé ===== Le capteur ultrason étant bruyant, on utilise un dérivé filtré : d_{filt} = \alpha\, d_{filt} + (1 - \alpha)\, \frac{e - e_{prev}}{dt} ===== Anti-windup (limitation de l’intégrale) ===== I = \mathrm{clip}(I + e\,dt,\; -I_{max},\; I_{max}) Reset si inversion du signe de l’erreur : e \cdot e_{prev} < 0 \Rightarrow I = 0 ===== Saturation moteurs ===== v = \begin{cases} V_{max} & \text{si } v > V_{max} \\ -V_{max} & \text{si } v < -V_{max} \\ v & \text{sinon} \end{cases} ===== Zone de vitesse minimale ===== |v| < V_{min} \Rightarrow v = \begin{cases} V_{min} & \text{si } v > 0 \\ -V_{min} & \text{si } v < 0 \end{cases} ---- ==== 2) Travail demandé ==== ===== Partie A — Analyse conceptuelle ===== * Expliquer la signification physique de l’erreur e. * Pourquoi un système en boucle fermée est-il nécessaire ? * Donner le rôle séparé des termes P, I, D. * Pourquoi la dérivée doit-elle être filtrée ? * Quel est l’intérêt du mécanisme anti-windup ? ---- ===== Partie B — Expérimentations ===== ==== Étape 1 : Action P seule ==== * Mettre Ki = 0 et Kd = 0 * Faire varier Kp (2, 4, 6, 8…) **Questions :** * Une erreur statique persiste-t-elle ? * Y a-t-il un dépassement ? * Le robot oscille-t-il si Kp est trop grand ? ---- ==== Étape 2 : Action PI ==== * Garder un Kp raisonnable (ex : 4 ou 6) * Ajouter Ki (0.1 → 0.2) **Questions :** * L’erreur statique disparaît-elle ? * Le robot devient-il plus oscillant ? * Que provoque un Ki trop élevé ? ---- ==== Étape 3 : Action PID ==== * Ajouter un terme dérivé Kd (0.5 ou 1.0) **Questions :** * Le dépassement diminue-t-il ? * La stabilité augmente-t-elle ? * Le dérivé amplifie-t-il le bruit ? * Le filtre (α) améliore-t-il les tremblements du robot ? ---- ===== Partie C — Analyse des données série ===== Les données typiques affichées : D = distance mesurée e = erreur v = commande moteur I = intégrale d = dérivée filtrée **Questions :** * La commande v se rapproche-t-elle de 0 une fois stabilisé ? * L’intégrale atteint-elle les limites Imax ? * Le dérivé filtré est-il stable ou bruité ? * Observe-t-on un dépassement (overshoot) dans la distance D ? ---- ==== 3) Synthèse finale ==== Rédiger un paragraphe répondant aux questions suivantes : * Pourquoi P seul ne suffit-il pas ? * En quoi I améliore la précision mais peut dégrader la stabilité ? * Quel est le rôle du dérivé dans l’amortissement ? * Quel triplet (Kp, Ki, Kd) est optimal pour VOTRE robot ? (justifier) * Pourquoi d’un robot à l’autre les réglages diffèrent-ils ? ---- ==== 4) Bonus facultatif ==== * Ajouter une consigne variable via le port série * Tracer les courbes D, e, v, I dans un tableur * Tester 10 cm, 20 cm et 30 cm pour analyser la robustesse #include "mCoreLite.h" mCoreLite bot; // ----- CONSIGNE ----- float Dcons = 20.0; // distance cible (cm) // ----- PARAMÈTRES PID ----- float Kp = 6.0; // proportionnel float Ki = 0.15; // intégral float Kd = 0; // dérivé (amortissement) // ----- MEMOIRES PID ----- float integral = 0.0; float e_prev = 0.0; unsigned long lastTime = 0; // ----- SECURITE INTEGRALE (ANTI-WINDUP) ----- float Imax = 50.0; // limite de l'intégrale // ----- VITESSE ----- int Vmin = 60; // évite les sifflements int Vmax = 200; // saturation // ----- FILTRE DERIVÉ ----- // (évite les tremblements dus au bruit US) float alpha = 0.7; // 0 = pas de filtre, 1 = filtre fort float d_filtered = 0.0; void setup() { Serial.begin(115200); bot.botSetup(); lastTime = millis(); } void loop() { // ----- 1) MESURE ----- float Dmes = bot.distanceCM(); // ----- 2) ERREUR ----- float e = Dmes - Dcons; // ----- 3) TEMPS ----- unsigned long now = millis(); float dt = (now - lastTime) / 1000.0; if (dt <= 0) dt = 0.001; // sécurité lastTime = now; // ----- 4) ZONE MORTE ----- if (fabs(e) < 1.0) { integral = 0; // détendre l'intégrale bot.motors(0,0); debug(Dmes, e, 0, integral, 0); return; } // ----- 5) INTÉGRALE + ANTI-WINDUP ----- integral += e * dt; if (integral > Imax) integral = Imax; if (integral < -Imax) integral = -Imax; // Réinitialisation si inversion de signe de l'erreur if (e * e_prev < 0) integral = 0; // ----- 6) DÉRIVÉE (/!\ capteur bruyant) ----- float derivative = (e - e_prev) / dt; e_prev = e; // Filtre passe-bas sur le dérivé d_filtered = alpha * d_filtered + (1 - alpha) * derivative; // ----- 7) SORTIE PID ----- float v = Kp * e + Ki * integral + Kd * d_filtered; // ----- 8) SATURATION ----- if (v > Vmax) v = Vmax; if (v < -Vmax) v = -Vmax; // ----- 9) VITESSE MINIMALE ----- if (fabs(v) > 0 && fabs(v) < Vmin) { v = (v > 0) ? Vmin : -Vmin; } // ----- 10) ACTION ----- // v > 0 → avancer (cible trop loin) // v < 0 → reculer (cible trop proche) bot.motors(-v, v); // ----- 11) DEBUG ----- debug(Dmes, e, v, integral, d_filtered); } // ================== DEBUG ====================== void debug(float D, float e, float v, float I, float d) { Serial.print("D="); Serial.print(D); Serial.print(" e="); Serial.print(e); Serial.print(" v="); Serial.print(v); Serial.print(" I="); Serial.print(I); Serial.print(" d="); Serial.println(d); }