regolatore PID termine integrale causando estrema instabilità
-
29-09-2019 - |
Domanda
Ho un regolatore PID in esecuzione su un robot che è stato progettato per rendere la sterzata del robot su una bussola. La correzione PID viene ricalcolata / un'aliquota di 20Hz.
Sebbene il regolatore PID funziona bene in modalità PD (IE, con il termine integrale zero'd out) anche la minima quantità di integrale forzerà l'uscita instabile in modo tale che l'attuatore di sterzo viene spostato a sinistra oa estrema destra.
Codice:
private static void DoPID(object o)
{
// Bring the LED up to signify frame start
BoardLED.Write(true);
// Get IMU heading
float currentHeading = (float)RazorIMU.Yaw;
// We just got the IMU heading, so we need to calculate the time from the last correction to the heading read
// *immediately*. The units don't so much matter, but we are converting Ticks to milliseconds
int deltaTime = (int)((LastCorrectionTime - DateTime.Now.Ticks) / 10000);
// Calculate error
// (let's just assume CurrentHeading really is the current GPS heading, OK?)
float error = (TargetHeading - currentHeading);
LCD.Lines[0].Text = "Heading: "+ currentHeading.ToString("F2");
// We calculated the error, but we need to make sure the error is set so that we will be correcting in the
// direction of least work. For example, if we are flying a heading of 2 degrees and the error is a few degrees
// to the left of that ( IE, somewhere around 360) there will be a large error and the rover will try to turn all
// the way around to correct, when it could just turn to the right a few degrees.
// In short, we are adjusting for the fact that a compass heading wraps around in a circle instead of continuing
// infinity on a line
if (error < -180)
error = error + 360;
else if (error > 180)
error = error - 360;
// Add the error calculated in this frame to the running total
SteadyError = SteadyError + (error * deltaTime);
// We need to allow for a certain amount of tolerance.
// If the abs(error) is less than the set amount, we will
// set error to 0, effectively telling the equation that the
// rover is perfectly on course.
if (MyAbs(error) < AllowError)
error = 0;
LCD.Lines[2].Text = "Error: " + error.ToString("F2");
// Calculate proportional term
float proportional = Kp * error;
// Calculate integral term
float integral = Ki * (SteadyError * deltaTime);
// Calculate derivative term
float derivative = Kd * ((error - PrevError) / deltaTime);
// Add them all together to get the correction delta
// Set the steering servo to the correction
Steering.Degree = 90 + proportional + integral + derivative;
// We have applied the correction, so we need to *immediately* record the
// absolute time for generation of deltaTime in the next frame
LastCorrectionTime = DateTime.Now.Ticks;
// At this point, the current PID frame is finished
// ------------------------------------------------------------
// Now, we need to setup for the next PID frame and close out
// The "current" error is now the previous error
// (Remember, we are done with the current frame, so in
// relative terms, the previous frame IS the "current" frame)
PrevError = error;
// Done
BoardLED.Write(false);
}
Qualcuno ha qualche idea del perché questo sta accadendo o come risolvere il problema?
Soluzione
Sembra che si sta applicando la vostra base di tempo per le integrali tre volte. L'errore è già l'errore accumulato dopo l'ultimo campione in modo yo non hanno bisogno di tempo delta volte moltiplicarlo. Quindi mi sento di modificare il codice di seguito.
SteadyError += error ;
SteadyError è l'integrale o somma di errori.
Quindi l'integrale dovrebbe essere solo SteadyError * Ki
float integral = Ki * SteadyError;
Modifica:
Ho passato con il codice di nuovo e ci sono diversi altri elementi che vorrei fissare, oltre alla correzione di cui sopra.
1) Se non si desidera delta time in millisecondi. In un sistema di campionamento normale termine delta sarebbe uno ma si è messa in un valore come 50 per il tasso 20Hz questo ha l'effetto di aumentare Ki per questo fattore e diminuendo Kd di un fattore 50 pure. Se siete preoccupati per il jitter, allora avete bisogno di convertire il tempo delta ad un tempo di campionamento relativo. Vorrei usare invece la formula.
float deltaTime = (LastCorrectionTime - DateTime.Now.Ticks) / 500000.0
la 500.000,0 è il numero di zecche attesi per campione, che per 20Hz è 50ms.
2) Tenere il termine integrale all'interno di un intervallo.
if ( SteadyError > MaxSteadyError ) SteadyError = MaxSteadyError;
if ( SteadyError < MinSteadyError ) SteadyError = MinSteadyError;
3) Modificare il seguente codice in modo che quando l'errore è di circa -180 non si ottiene un passo in errore con un piccolo cambiamento.
if (error < -270) error += 360;
if (error > 270) error -= 360;
4) Verificare Steering.Degree sta ricevendo la corretta risoluzione e segno.
5) Infine anni probabilmente può solo cadere tempo delta tutti insieme e calcolare il termine differenziale modo seguente.
float derivative = Kd * (error - PrevError);
Con tutto che il codice diventa.
private static void DoPID(object o)
{
// Bring the LED up to signify frame start
BoardLED.Write(true);
// Get IMU heading
float currentHeading = (float)RazorIMU.Yaw;
// Calculate error
// (let's just assume CurrentHeading really is the current GPS heading, OK?)
float error = (TargetHeading - currentHeading);
LCD.Lines[0].Text = "Heading: "+ currentHeading.ToString("F2");
// We calculated the error, but we need to make sure the error is set
// so that we will be correcting in the
// direction of least work. For example, if we are flying a heading
// of 2 degrees and the error is a few degrees
// to the left of that ( IE, somewhere around 360) there will be a
// large error and the rover will try to turn all
// the way around to correct, when it could just turn to the right
// a few degrees.
// In short, we are adjusting for the fact that a compass heading wraps
// around in a circle instead of continuing infinity on a line
if (error < -270) error += 360;
if (error > 270) error -= 360;
// Add the error calculated in this frame to the running total
SteadyError += error;
if ( SteadyError > MaxSteadyError ) SteadyError = MaxSteadyError;
if ( SteadyError < MinSteadyError ) SteadyError = MinSteadyError;
LCD.Lines[2].Text = "Error: " + error.ToString("F2");
// Calculate proportional term
float proportional = Kp * error;
// Calculate integral term
float integral = Ki * SteadyError ;
// Calculate derivative term
float derivative = Kd * (error - PrevError) ;
// Add them all together to get the correction delta
// Set the steering servo to the correction
Steering.Degree = 90 + proportional + integral + derivative;
// At this point, the current PID frame is finished
// ------------------------------------------------------------
// Now, we need to setup for the next PID frame and close out
// The "current" error is now the previous error
// (Remember, we are done with the current frame, so in
// relative terms, the previous frame IS the "current" frame)
PrevError = error;
// Done
BoardLED.Write(false);
}
Altri suggerimenti
Stai inizializzazione SteadyError
(nome bizzarro ... perché non "integratore")? Se contiene un valore casuale in fase di start-up che potrebbe non tornare quasi a zero (1e100 + 1 == 1e100
).
Si potrebbe essere affetti da integratore windup , che normalmente dovrebbe andare via, ma non se ci vuole più tempo a diminuire di quanto non faccia per il vostro veicolo per completare una rotazione completa (e avvolgimento di nuovo l'integratore). La soluzione banale è imporre limiti sull'integratore, se ci sono soluzioni più avanzate (PDF, 879 kB) se il sistema richiede.
non Ki
hanno il segno corretto?
Vorrei con forza scoraggiare l'uso di galleggianti per i parametri PID a causa della loro precisione arbitraria. Utilizzare i numeri interi (punto forse fisso). Si dovrà imporre il controllo dei limiti, ma sarà molto più sano rispetto all'utilizzo di carri allegorici.
Il termine integrale è già accumulato nel corso del tempo, moltiplicando per tempo delta renderà accumulano ad un tasso di tempo-squared. Infatti dal momento che è già SteadyError erroneamente calcolato moltiplicando errore tempo delta, che è il tempo-Cubed!
In SteadyError, se si sta cercando di compensare un aggiornamento aperiodico, sarebbe meglio per risolvere l'aperiodicity. Tuttavia, il calcolo è viziata in ogni caso. Avete calcolato in unità di errore / ora, mentre si vuole unità di errore solo. Il modo arithmentiaclly corretta per compensare per la temporizzazione del jitter, se proprio necessario sarebbe:
SteadyError += (error * 50.0f/deltaTime);
se tempo delta rimane in millisecondi e la frequenza di aggiornamento nominale è 20Hz. Tuttavia tempo delta sarebbe meglio calcolato come un galleggiante o non convertito in millisecondi affatto se è temporizzazione jitter si sta tentando di rilevare; si sta inutilmente scartando precisione. In entrambi i casi ciò che è necessario è quello di modificare il valore di errore per il rapporto del tempo nominale di tempo effettivo.
Una buona lettura è PID senza un dottorato di ricerca
Non sono sicuro perché il codice non funziona, ma sono quasi positivo, non è possibile verificare per vedere il motivo per cui, neanche. Si potrebbe iniettare un servizio timer in modo da poter prendere in giro fuori e vedere cosa succede:
public interace ITimer
{
long GetCurrentTicks()
}
public class Timer : ITimer
{
public long GetCurrentTicks()
{
return DateTime.Now.Ticks;
}
}
public class TestTimer : ITimer
{
private bool firstCall = true;
private long last;
private int counter = 1000000000;
public long GetCurrentTicks()
{
if (firstCall)
last = counter * 10000;
else
last += 3500; //ticks; not sure what a good value is here
//set up for next call;
firstCall = !firstCall;
counter++;
return last;
}
}
Poi, sostituire entrambe le chiamate al DateTime.Now.Ticks
con GetCurrentTicks()
, e si può fare un passo attraverso il codice e vedere quali sono i valori sembrano.