Frage

Ich habe einen PID-Regler an einem Roboter ausgeführt wird, der ausgelegt ist, den Roboter Steer auf einen Kompasskurs zu machen. Die PID-Korrektur wird neu berechnet / mit einer Rate von 20 Hz angelegt wird.

Obwohl der PID-Regler arbeitet gut in PD-Modus (das heißt mit dem Integralterm zero'd out) auch nur die geringste Menge an fester wird das Ausgang instabil so Kraft, die der Lenkaktuator entweder nach links geschoben wird, oder Recht extrem.

Code:

        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);
    }

Hat jemand eine Idee, warum dies geschieht oder wie man es beheben?

War es hilfreich?

Lösung

Es sieht aus wie Sie Ihre Zeitbasis zu dem integralen dreimal anwenden. Fehler sind bereits der akkumulierte Fehler seit der letzten Probe so yo braucht nicht zu vermehren DELTA mal es. So würde ich den Code der folgenden ändern.

SteadyError += error ;

SteadyError ist das Integral oder die Summe der Fehler.

So sollte das Integral nur SteadyError * Ki sein

float integral = Ki * SteadyError;

Edit:

Ich habe wieder durch den Code gegangen und es gibt einige andere Elemente, dass ich zusätzlich zu den oben fix beheben würde.

1) Sie haben keine Delta-Zeit in Millisekunden wollen. In einem normalen abgetastete System würde das Delta Begriff sein, aber Sie in einem Wert wie 50 für die 20 Hz-Rate setzen dies den Effekt hat Ki um diesen Faktor der Erhöhung und Verringerung Kd um einen Faktor von 50 als gut. Wenn Sie sich über Jitter besorgt sind, dann müssen Sie Delta-Zeit zu einer relativen Probenzeit konvertieren. Ich würde stattdessen die Formel verwenden.

float deltaTime = (LastCorrectionTime - DateTime.Now.Ticks) / 500000.0

die 500.000,0 ist die Anzahl der erwarteten Ticks pro Probe für 20 Hz, die 50ms ist.

2) Halten Sie den Integralterm in einem Bereich.

if ( SteadyError > MaxSteadyError ) SteadyError = MaxSteadyError;
if ( SteadyError < MinSteadyError ) SteadyError = MinSteadyError;

3) Ändern Sie den folgenden Code so, dass, wenn Fehler um -180 Sie keinen Schritt in Fehler mit einer kleinen Änderung erhalten.

if (error < -270) error += 360;
if (error >  270) error -= 360;

4) Stellen Sie sicher, Steering.Degree die richtige Auflösung und Zeichen empfängt.

5) Schließlich kann yo wahrscheinlich fallen nur DELTA alle zusammen und der Differentialausdruck die folgende Art und Weise berechnet werden.

float derivative = Kd * (error - PrevError);

Mit allen, dass Ihr Code wird.

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);
}

Andere Tipps

Initialisieren Sie SteadyError (bizarre Namen ... warum nicht "Integrator")? Wenn es einige zufällige Wert auf enthält Start-up nie auf nahe Null zurückkehren könnte (1e100 + 1 == 1e100).

Das könnte Sie Leid von Integrator-Windup , die normalerweise weg gehen sollte, aber nicht, wenn es dauert länger zu verringern, als es für Ihr Fahrzeug hat eine volle Umdrehung (wieder und windup den Integrator) in Anspruch nehmen. Die triviale Lösung ist Grenzen für den Integrator zu verhängen, obwohl es ). Sie haben Grenzprüfung zu verhängen, aber es wird als die Verwendung von Schwimmern viel mehr gesund sein.

Ich bin nicht sicher, warum Ihr Code funktioniert nicht, aber ich bin fast sicher, Sie es nicht testen, um zu sehen, warum auch nicht. Sie können einen Timer Service injizieren, so dass Sie es heraus verspotten und sehen, was passiert:

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;
    }
}

Dann ersetzen Sie beide Anrufe zu DateTime.Now.Ticks mit GetCurrentTicks(), und Sie können Schritt durch den Code und sehen, was die Werte aussehen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top