Controllo PID: l'aggiunta di un ritardo prima del ciclo successivo è una buona idea?
Sto implementando il controllo PID in c ++ per far girare un robot con azionamento differenziale di un numero preciso di gradi, ma ho molti problemi.
Uscita anticipata dal loop di controllo a causa del tempo di esecuzione del loop veloce
Se il robot misura il suo errore inferiore a 0,5 gradi, esce dal ciclo di controllo e considera la svolta "finita" (lo 0,5 è un valore casuale che potrei cambiare ad un certo punto). Sembra che il loop di controllo funzioni così rapidamente che il robot può girare a velocità molto elevata, superare il setpoint ed uscire dal loop / tagliare le potenze del motore, perché era al setpoint per un breve istante. So che questo è l'intero scopo del controllo PID, per raggiungere con precisione il setpoint senza superare il limite, ma questo problema rende molto difficile regolare le costanti PID. Ad esempio, cerco di trovare un valore di kp tale che ci sia un'oscillazione costante, ma non c'è mai alcuna oscillazione perché il robot pensa di aver "finito" una volta superato il setpoint. Per risolvere questo problema,Ho implementato un sistema in cui il robot deve essere al setpoint per un certo periodo di tempo prima di uscire, e questo è stato efficace, consentendo l'oscillazione, ma il problema dell'uscita anticipata dal loop sembra un problema insolito e la mia soluzione potrebbe non essere corretto.
Il termine D non ha effetto a causa del tempo di esecuzione veloce
Una volta che il robot oscillava in modo controllato usando solo P, ho provato ad aggiungere D per evitare il superamento. Tuttavia, questo non ha avuto effetto per la maggior parte del tempo, perché il ciclo di controllo è in esecuzione così rapidamente che 19 cicli su 20, il tasso di variazione dell'errore è 0: il robot non si è mosso o non si è mosso abbastanza per esso da misurare in quel tempo. Ho stampato la variazione di errore e il termine derivativo di ogni ciclo per confermarlo e ho potuto vedere che questi sarebbero entrambi 0 per circa 20 cicli di loop prima di prendere un valore ragionevole e poi di nuovo a 0 per altri 20 cicli. Come ho detto, penso che ciò sia dovuto al fatto che i cicli di loop sono così veloci che il robot non si è letteralmente spostato abbastanza per qualsiasi tipo di cambiamento evidente in errore.Questo era un grosso problema perché significava che il termine D non aveva essenzialmente alcun effetto sul movimento del robot perché era quasi sempre 0. Per risolvere questo problema, ho provato a utilizzare l'ultimo valore diverso da zero della derivata al posto di qualsiasi valore 0, ma questo non funzionava bene e il robot oscillava in modo casuale se l'ultima derivata non rappresentava l'attuale tasso di variazione dell'errore.
Nota: sto anche usando un piccolo feedforward per il coefficiente di attrito statico e lo chiamo feedforward "f"
Devo aggiungere un ritardo?
Mi sono reso conto che penso che la fonte di entrambi questi problemi sia il ciclo in esecuzione molto molto rapidamente, quindi qualcosa a cui pensavo era l'aggiunta di un'istruzione wait alla fine del ciclo. Tuttavia, sembra una cattiva soluzione generale per rallentare intenzionalmente un ciclo. E 'questa una buona idea?
turnHeading(double finalAngle, double kp, double ki, double kd, double f){
std::clock_t timer;
timer = std::clock();
double pastTime = 0;
double currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
const double initialHeading = getHeading();
finalAngle = angleWrapDeg(finalAngle);
const double initialAngleDiff = initialHeading - finalAngle;
double error = angleDiff(getHeading(), finalAngle);
double pastError = error;
double firstTimeAtSetpoint = 0;
double timeAtSetPoint = 0;
bool atSetpoint = false;
double integral = 0;
double derivative = 0;
double lastNonZeroD = 0;
while (timeAtSetPoint < .05)
{
updatePos(encoderL.read(), encoderR.read());
error = angleDiff(getHeading(), finalAngle);
currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
double dt = currentTime - pastTime;
double proportional = error / fabs(initialAngleDiff);
integral += dt * ((error + pastError) / 2.0);
double derivative = (error - pastError) / dt;
//FAILED METHOD OF USING LAST NON-0 VALUE OF DERIVATIVE
// if(epsilonEquals(derivative, 0))
// {
// derivative = lastNonZeroD;
// }
// else
// {
// lastNonZeroD = derivative;
// }
double power = kp * proportional + ki * integral + kd * derivative;
if (power > 0)
{
setMotorPowers(-power - f, power + f);
}
else
{
setMotorPowers(-power + f, power - f);
}
if (fabs(error) < 2)
{
if (!atSetpoint)
{
atSetpoint = true;
firstTimeAtSetpoint = currentTime;
}
else //at setpoint
{
timeAtSetPoint = currentTime - firstTimeAtSetpoint;
}
}
else //no longer at setpoint
{
atSetpoint = false;
timeAtSetPoint = 0;
}
pastTime = currentTime;
pastError = error;
}
setMotorPowers(0, 0);
}
turnHeading(90, .37, 0, .00004, .12);
Risposte
Non disinnestare il controller.
Lo scopo di un controller non è solo quello di guidare il sistema al setpoint desiderato in base a una risposta dinamica predeterminata, ma anche di contrastare potenziali fattori esterni che possono impedire questo compito. Pensa a un disturbo che porterà il sistema lontano dal setpoint una volta raggiunto. Pertanto, il controller deve essere sempre operativo (a meno che non sia necessario modificare l'attività stessa e di conseguenza cambiare anche il controller).
A tal fine bisognerebbe sicuramente sommare la parte integrale, che è responsabile del raggiungimento di un errore di stato stazionario nullo in presenza di grandezze non modellate e disturbi esterni.
Questa è una risorsa piuttosto correlata: https://robotics.stackexchange.com/a/19198/6941.
Tralascia il derivato.
Il 95% dei controller PID nelle industrie sono controller PI (vedere "Feedback Systems" Astrom, Murray) poiché la parte D può svolgere un ruolo significativo solo con processi lenti (come quelli che si occupano di regolazione della temperatura e del livello del serbatoio). Questo sicuramente non è il tuo caso. Le risorse in qualche modo correlate alle difficoltà dei termini derivati sono:
- https://robotics.stackexchange.com/a/21556/6941.
- https://robotics.stackexchange.com/a/21555/6941.
Una volta che il robot oscillava in modo controllato usando solo P, ho provato ad aggiungere D per evitare il superamento.
Bene, sembra che tu stia seguendo le prescrizioni di Ziegler-Nichols per mettere a punto il tuo controller. Ci sono tabelle a cui devi attenersi che forniscono stime per i guadagni. Tuttavia, questi sono euristici ed è probabile che non funzionerà nel tuo caso.
Basta rilasciare la parte D e concentrarsi sul controller PI. Esiste anche un'altra variante ZN che non si basa su oscillazioni appositamente generate:https://robotics.stackexchange.com/a/21616/6941.
Mai e poi mai aggiungere ritardi al sistema.
I ritardi sono cattivi da affrontare in un ciclo di controllo e le bestie peggiori contro cui gli ingegneri devono combattere ( guarda questo 🎥 ) poiché riducono significativamente il margine di fase spingendo il sistema generale verso l'instabilità.
Se ritieni che il tuo loop sia troppo veloce, applica l'input shaping al setpoint (con ad esempio la traiettoria del minimo jerk ) per appianare l'inizio del passo brusco. Il sistema a circuito chiuso reagirà in modo più garbato. Un'altra possibilità è che il tuo guadagno P sia troppo alto: basta eseguire la stonatura del controller.
A questo proposito, se inserisci il termine integrale I, dovrai ragionare sul tempo di campionamento $T_s$ anche.
Sarebbe molto meglio determinare una frequenza di campionamento fissa per il controller. Una regola pratica molto approssimativa è che qualunque sia il tempo di assestamento necessario per uscire dal loop una volta che funziona in regime lineare , l'intervallo di campionamento dovrebbe essere compreso tra 10 volte e 100 volte inferiore al tempo di assestamento. In altre parole, la frequenza di campionamento dovrebbe essere da 10 a 100 volte più veloce della larghezza di banda del loop desiderata.
Come piccolo bonus aggiuntivo, questo significa che puoi calcolare dt al di fuori del ciclo
Di preferenza, la frequenza di campionamento dovrebbe essere dettata dall'hardware, ad esempio da un timer hardware, e ancora meglio il campionamento della posizione dovrebbe essere fatto dall'hardware, attivato da un timer. Ciò riduce notevolmente i vincoli in tempo reale sul software.
Se stai utilizzando una frequenza di campionamento ragionevole e per lo più non stai registrando modifiche nel tuo codificatore, il tuo codificatore non ha passaggi sufficienti.
Non sono d'accordo con @Ugo_Pattachini, in quanto se il loop coinvolge un motore e il tuo controller è abbastanza pulito, alcune azioni differenziali possono essere utili. Ma ci sono buone probabilità che abbia bisogno di essere limitato alla banda (cioè, hai bisogno di un controller lead-lag), e se stai facendo l'ottimizzazione PID da posto ci sono buone probabilità che tu non abbia gli strumenti per impostare correttamente il bandlimiting.
Tuttavia, se non è possibile applicare tempi molto stretti sul campionamento del codificatore, non provare nemmeno a utilizzare il controllo derivativo. Tempi di campionamento irregolari più un po 'di velocità sul motore produrranno rumore extra e i controller derivati tendono ad amplificare il rumore. Se non è possibile predisporre un segnale pulito su cui controllare, il controllo derivativo non sarà adatto.