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.
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 ultimoTu— 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