Tutorial · Principiante · 18 min di lettura

Come regolare un controllore PID: guida pratica

Regola un PID passo dopo passo: imposta il proporzionale, aggiungi la derivata per fermare l'overshoot, poi l'integrale ed evita il windup.

Un controllore PID trasforma l’errore tra il punto in cui un sistema si trova e quello in cui vuoi che sia in una correzione. Ha tre termini, proporzionale, integrale e derivata, e la regolazione consiste nel scegliere quanto deve agire ciascuno. Il metodo affidabile è procedere un termine alla volta, in un ordine fisso, osservando la risposta dopo ogni modifica. Puoi seguire il procedimento nel Simulatore del controllore PID.

Inizia dal guadagno proporzionale

Imposta a zero i guadagni integrale e derivata. Aumenta il guadagno proporzionale (Kp) finché il sistema non risponde rapidamente a una variazione del riferimento. Se è troppo piccolo, reagisce lentamente; se è troppo grande, overshoot e oscillazioni. Fermati quando la risposta è rapida ma oscilla soltanto leggermente: questo è il punto di lavoro da cui aggiungere gli altri termini.

Una regola utile: P ti dà velocità. Reagisce all’errore in questo istante, e basta.

Quattro risposte a gradino dello stesso sistema con diversi guadagni PID, rappresentate come posizione in funzione del tempo su quattro secondi, con una linea orizzontale tratteggiata come obiettivo a uno. Un basso guadagno proporzionale sale lentamente e si assesta molto sotto l'obiettivo. Un guadagno proporzionale elevato supera ampiamente l'obiettivo e oscilla attraverso diverse oscillazioni di grande ampiezza prima di assestarsi. Un moderato guadagno proporzionale da solo supera una volta l'obiettivo, poi si assesta leggermente sotto di esso. La traccia combinata di termini proporzionale, integrale e derivata sale rapidamente, supera l'obiettivo di poco e si assesta esattamente sul valore di riferimento.
Un sistema, quattro regolazioni. Ogni fallimento ha una forma propria: un divario permanente, un'oscillazione sostenuta o una curva che si ferma poco prima. Scarica SVG

Aggiungi la derivata per domare l’overshoot

Ora aumenta il guadagno derivativo (Kd). L’azione derivativa risponde alla rapidità con cui l’errore cambia e quindi frena mentre il sistema si avvicina al riferimento. È ciò che elimina l’overshoot e l’oscillazione lasciati dal controllo puramente proporzionale.

Aggiungi solo la quantità di derivata necessaria a smorzare l’oscillazione. Troppa rende il controllore nervoso, perché il termine derivativo amplifica qualsiasi rumore nella misura. Se il segnale è rumoroso, filtralo prima della derivata oppure affidati maggiormente ai termini proporzionale e integrale.

Aggiungi l’integrale per ultimo

Proporzionale e derivata insieme possono comunque lasciare un piccolo errore costante: il sistema si ferma poco prima del riferimento. Il termine integrale (Ki) lo elimina: accumula l’errore nel tempo e spinge finché lo scarto non scompare.

Aumenta lentamente il guadagno integrale e solo se rimane un errore a regime riproducibile. L’integrale è il termine più lento e quello più facile da esagerare, quindi una piccola quantità fa molta strada.

Attenzione al windup integrale

Il windup integrale è il guasto classico del PID. Quando l’attuatore satura, per esempio un motore già alla velocità massima o una valvola completamente aperta, il sistema non può reagire, ma il termine integrale continua ad accumulare errore. Quando l’errore cambia finalmente segno, tutta quell’azione accumulata si scarica in una sola volta e il sistema supera abbondantemente il riferimento.

Evitalo limitando l’integrale, smettendo di sommare valori mentre l’uscita è satura, oppure tramite ricalcolo a posteriori, riducendo l’integrale usando la differenza fra uscita richiesta ed erogata. Un semplice limite:

float candidateIntegral = integral + error * dt;
float raw = kp * error + ki * candidateIntegral + kd * derivative;
float output = constrain(raw, outputMin, outputMax);
if (raw == output) integral = candidateIntegral; // only accumulate when not saturated

Mantieni onesto il ritmo del ciclo

Tutti e tre i termini dipendono da un passo temporale coerente. Su un microcontrollore, una lunga delay() nel ciclo aggiunge tempo morto che rende inutile il termine derivativo, per quanto elevato lo imposti, e distorce l’integrale. Esegui il ciclo rapidamente e a intervalli regolari e passa il tempo effettivamente trascorso nel calcolo.

Avvicinarti rapidamente: il punto di partenza Ziegler–Nichols

Aumentare P finché non trovi l’oscillazione non è soltanto una verifica: fornisce un numero da cui calcolare l’intera regolazione. Imposta I e D a zero, aumenta P finché l’uscita non oscilla in modo stabile, senza crescere né diminuire, e registra due valori:

  • Ku — il guadagno proporzionale in cui ciò accade, il guadagno ultimo
  • Tu — il periodo dell’oscillazione, in secondi

Poi:

Controllore Kp Ki Kd
Solo P 0.5 Ku — —
PI 0.45 Ku 0.54 Ku / Tu —
PID (classico) 0.6 Ku 1.2 Ku / Tu 0.075 Ku Tu
PID (senza overshoot) 0.2 Ku 0.4 Ku / Tu 0.066 Ku Tu

Sono un punto di partenza, non una risposta. La riga classica è deliberamente aggressiva e in genere produce un overshoot del 20–40%, accettabile per un motore e inaccettabile per un braccio robotico vicino a un tavolo. L’ultima riga scambia la velocità di risposta con il raggiungimento del riferimento senza overshoot.

Il valore del metodo è che sostituisce tre incognite con due misurazioni. Venti minuti per trovare Ku e Tu ti avvicinano più di un pomeriggio di tentativi, dopo di che puoi regolare con fiducia a partire da una base sensata.

Deriva la misura, non l’errore

Questa è una modifica di una riga che risolve il problema più comune relativo al termine D.

Quando il riferimento cambia a gradino, anche l’errore cambia a gradino. La derivata di un gradino è brevemente enorme, quindi il controllore comanda una correzione violenta per un motivo che non ha nulla a che fare con la fisica: non si è mosso nulla, hai solo chiesto qualcosa di diverso. Su un robot, questo è uno strappo brusco a ogni variazione dell’obiettivo.

// Derivative kick: the setpoint step goes straight into the output.
float derivative = (error - lastError) / dt;

// Derivative on measurement: the setpoint contributes nothing at all.
float derivative = -(measurement - lastMeasurement) / dt;

Il segno cambia perché l’errore è setpoint − measurement, quindi derivare la misura equivale a negare. Quando il riferimento è costante, le due forme sono matematicamente identiche; quando cambia, soltanto la seconda si comporta bene.

Usa la derivata sulla misura, a meno che tu non abbia un motivo preciso per non farlo. Quasi non esiste un caso in cui il gradino derivativo sia desiderato.

Filtra la derivata o D amplificherà il rumore

La derivazione amplifica il contenuto ad alta frequenza e il rumore del sensore è ad alta frequenza per definizione. Un segnale con fluttuazioni di ±2 conteggi, campionato ogni 10 ms, produce una stima della derivata che oscilla di ±200 conteggi al secondo e D la trasforma in comandi del motore.

Il risultato abituale è che le persone concludono che D è inutile e lo impostano a zero. Il problema reale è a monte:

// A first-order filter on the derivative term only.
// alpha = dt / (tau + dt); tau is the filter time constant in seconds.
const float tau = 0.05f;                       // 50 ms
const float alpha = dt / (tau + dt);
dFiltered += alpha * (dRaw - dFiltered);

La scelta di tau è un compromesso: se è troppo piccola, il rumore sopravvive; se è troppo grande, D agisce su informazioni già obsolete, che sono esattamente ciò che D dovrebbe evitare. Inizia da tre a cinque volte il periodo del ciclo e aumenta solo finché il nervosismo non scompare.

Un controllore completo

Tutto quanto sopra, in un unico punto:

struct Pid {
  float kp, ki, kd;
  float outMin, outMax;
  float tau;                 // derivative filter time constant

  float integral = 0;
  float lastMeasurement = 0;
  float dFiltered = 0;
  bool  primed = false;
};

float pidUpdate(Pid &c, float setpoint, float measurement, float dt) {
  if (dt <= 0) return 0;

  const float error = setpoint - measurement;

  // Derivative on measurement, low-pass filtered.
  if (!c.primed) { c.lastMeasurement = measurement; c.primed = true; }
  const float dRaw = -(measurement - c.lastMeasurement) / dt;
  const float alpha = dt / (c.tau + dt);
  c.dFiltered += alpha * (dRaw - c.dFiltered);
  c.lastMeasurement = measurement;

  // Provisional integral, committed only if the output does not saturate.
  const float candidate = c.integral + error * dt;
  const float raw = c.kp * error + c.ki * candidate + c.kd * c.dFiltered;
  const float out = constrain(raw, c.outMin, c.outMax);
  if (raw == out) c.integral = candidate;      // conditional integration

  return out;
}

Tre elementi segnano la differenza fra un controllore che funziona su un robot e uno che funziona soltanto in un libro di testo: la derivata applicata alla misura, una derivata filtrata e l’integrazione condizionale, che impedisce all’integrale di andare in windup mentre l’attuatore è saturato.

Diagnosticare dalla forma della risposta

Puoi assegnare un nome a quasi ogni errore di regolazione osservando soltanto la curva di risposta:

Cosa osservi Cosa significa Cosa modificare
Sale lentamente e non arriva mai P troppo bassa, assente I Aumenta P; aggiungi I se rimane uno scarto
Si assesta sempre sotto l’obiettivo Nessun termine integrale Aggiungi I lentamente
Supera una volta, poi si assesta P leggermente alta, assente D Aggiungi D
Supera ripetutamente con oscillazioni decrescenti P troppo alta Abbassa P oppure aggiungi D
Oscilla per sempre con ampiezza costante P al guadagno ultimo Hai trovato Ku — dimezzalo
L’oscillazione cresce P superiore a Ku Riducila immediatamente
Uscita rapida, irregolare e nervosa D agisce sul rumore Filtra la derivata o il sensore
Dà uno strappo brusco a ogni variazione del riferimento Gradino derivativo Applica la derivata alla misura
Va molto oltre dopo un movimento ampio Windup integrale Integrazione condizionale o limite
Ieri funzionava, oggi è lento Il sistema è cambiato, di solito la batteria I guadagni sono proprietà del sistema, non del codice

L’ultima riga merita attenzione perché non è un bug. I guadagni descrivono uno specifico sistema, e il sistema comprende carica della batteria, massa del telaio, superficie e temperatura del riduttore. Una regolazione perfetta con batterie nuove diventa lenta con batterie scariche, perché lo stesso ciclo di lavoro produce ora meno coppia. Le risposte professionali sono un ciclo di velocità sottostante, così che il controllore comandi la velocità e non il ciclo di lavoro, oppure la pianificazione dei guadagni, con più regolazioni selezionate in base alle condizioni operative.

Ritmo del ciclo, quantificato

Sia I sia D sono calcolati in funzione del tempo trascorso, quindi un ciclo che viene eseguito ogni volta che capita di terminare li calcola entrambi usando un tempo falso.

Comportamento del ciclo Effetto sul controllore
Intervallo fisso Tutto funziona come previsto
Variabile, ma dt misurato e passato al controllore Corretto anch’esso: questa è la risposta pratica
Variabile, dt assunto costante I accumula in modo errato; D diventa rumore
Contiene una delay() Tempo morto: nessun valore di D lo compensa

Misura dt con micros() e passalo al controllore. Non costa nulla, sopravvive a un ciclo la cui durazione cambia man mano che aggiungi funzionalità ed elimina un’intera classe di misteri dalla regolazione.

Se puoi anche rendere il ciclo rapido, fallo: un controllore che campiona a 20 Hz agisce su informazioni vecchie fino a 50 ms e nessun guadagno corregge informazioni obsolete. Rimuovere le chiamate a delay() porta in genere un primo robot da 20 Hz a diverse centinaia, e l’inseguimento migliora senza toccare alcun guadagno.

Provalo tu

Apri il Simulatore del controllore PID, imposta a zero i guadagni I e D e segui quest’ordine. Osserva come overshoot, tempo di assestamento ed errore a regime rispondano a un solo termine alla volta. Una volta che la forma ha senso qui, la stessa intuizione si trasferisce a un motore reale, a un riscaldatore o al ciclo di sterzo del Simulatore del robot seguilinea.

Esplora il grafo

Fa parte di queste costruzioni

Progetti e percorsi di apprendimento che includono questo tutorial.

Per approfondire

Fonti