Hallo Testpiloten
20 Zeilen Code und der Copter hält die Höhe? Ein Problem von dem man eigentlich erwartet das es sich einfach lösen läßt. Doch kurzzeitiger Erfolg und Rückschritt liegen wohl immer dicht beianander. Heute denke ich es klappt, morgen lehrt mich dieses dämliche Stück Programm auf dem ATMega das die Wirklichkeit ganz anders aussieht.
Zwischenschritt war eine persönliche Weiterbildung in Exxcel um einen Regler per Tabellenkalkulation nachzubilden. Geht ganz wunderbar, man erhält auch gleich schöne Linien. Diese zeigen aber auch, das so ein Regler ganz schöne Zicken machen kann. Zwischen regeln und aufschaukelnder Ozzilator geht alles.
Doch zum Kern des Problems, der PID Regler. Ich hol einfach mal ein wenig aus, damit wir die Schwachstellen im jetzigen im Originalcode erkennen können.
Der P-Anteil
PidP = DeltaAlt * Anpassungfaktor;
ist problemlos, er wird 40 mal die Sekunde aktualisiert.
Der I-Anteil ist schon schwieriger
errorAltitudeI += DeltaAlt * Anpassungfaktor;
PidI = errorAltitudeI;
Der I-Anteil wird mit zum Motorsteller gegeben, der generiert ein Signal für den Motor, dieser ändert die Drehzahl, beschleunigt die Luftschraube mit...... und das dauert seine Zeit.... in der Zwischenzeit wird die PID Routine mehrfach aufgerufen..... errorAltitudeI weiter erhöht (oder erniedrigt) .... Bis der Motor die Steig/Sinkleistung gebracht hat, hat sich errorAltitudeI meist so weit aufgebaut das der Copter weit über die Mittellinie schiesst..... Dann der Weg in die andere Richtung... (Es wird immer ein gewisses Überschieße geben müssen, sonst klappt es nicht.... aber nicht zu stark)
Die Verzögerung bis die Luftschraube ihre Wirkung zeigt ist das Problem.
Dann gehen wir zum D-Anteil
DeltaAltOld = DeltaAlt;
DeltaAlt = AltHold - EstAlt;
velocity = DeltaAlt - DeltaAltOld;
PidD = velocity * Anpassungsfaktor;
Rechnen tun wir mit Integer. Die Reglerroutine wird 40 mal die Sekunde aufgerufen. Ein Wert von 1 entspricht jezt schon einer Sink/Steiggeschwindigkeit von 40cm/sec. Bei einem Wert von 5 kommt der Copter mit 2m/sec vom Himmel gesaust.
Verstanden habe ich den D-Anteil als Bremse, das heißt es soll schnellen Bewegungen entgegenwirken. Feinfühligkeit ist mit diesen mächtigen Steig/Sinkleistungen die hinter diesen kleinen Zahlen stehen nicht mehr gegeben. Eher schon ein Fall für Grobmotoriker.
Ausweg aus diesem Dilemma ist eine Anpassung der Aufrufrate der Pid-Reglers. Mit 4 Aufrufen / Sekunde zeigt mein Copter ein Regelverhalten welches ich nachvollziehen konnte, keine wilden Orgien mehr am Gassteller. Ob einer Verringerung auf drei oder gar zwei Aufrufe / Sekunde noch eine Verbesserung bringt habe ich noch nicht probiert, wäre dann vermutlich immer noch besser bzw schneller als ich selber am Steuerknüppel. Eine Unteruchung darauf hin ist aber Sinnvoll.
Ein Versuch außerhalb des Messbereichs des US-Sensors hat mich wegen des Pendelns um ca 4m nicht begeistert, doch zum einen war es böig, zum anderen weder der D-Wert optimiert. Routinen die den WindUp des I-Reglers verhinden sollen habe ich erst mal wieder weggelassen, evtl machen sie doch Sinn. Ebenfalls bin ich noch nicht dazu gekommen den Baro gegen Umwelteinflüße besser zu kapseln. Trotzdem hat sich die Regelung im BaroMode nachhaltig verbessert.
Wer probieren will, ich habe meine Routine unten einkopiert. Den Baromittelwert macht eine Routine die ich Bei Alex Mos geklaut habe, wie ich finde super smart programmiert. Den US-SensorCode (auch Mos) soll rausschmeissen wer ihn nicht braucht. Die EstAlt wird noch 40 mal / Sekunde aufgerufen, das ist bestimmt kein Fehler. Ich hoffe das ich beim Bereinigen des Codes nicht zuviel gelöscht habe, das sind aber eh nur Klammern und Semikolions.
Gruß Deacon Blues
typedef struct avg_var16 {
int32_t buf; // internal bufer to store non-rounded average value
int16_t res; // result (rounded to int)
} t_avg_var16;
/* n=(1..16) */
void average16(struct avg_var16 *avg, int16_t cur, int8_t n) {
avg->buf+= cur - avg->res;
avg->res = avg->buf >> n;
}
#define UPDATE_INTERVAL 25000 // 25000 = 40hz update rate (20hz LPF on acc)
#define UPDATE_INTERVAL2 250000 // 250000 = 4hz Rate für den Regler
#define INIT_DELAY 4000000 // 4 sec initialization delay
void getEstimatedAltitude(){
static uint32_t deadLine = INIT_DELAY,deadLine2 = INIT_DELAY;
static t_avg_var16 baroSonarDiff = {0,0};
static t_avg_var16 avgAlt = {0,0};
static int32_t BaroAltGround; // baro for 'ground level'
static int32_t DeltaAlt, DeltaAltOld; // Regelabweichung alt für PID-D
static int32_t velocity; // Geschwindigkeit Höhenänderung
static float alt = 0; // cm
int32_t sensorAlt; // Höhe gemischt aus Sonar und Baro
int16_t PidP = 0, PidI = 0, PidD;
static uint8_t blinker = 0;
static uint8_t blinkerI = 0;
if (currentTime < deadLine) return;
deadLine = currentTime + UPDATE_INTERVAL;
average16(&avgAlt, BaroAlt, 5); // / 4 = 16 = 0,375 sec 5 = 32 = 0,75 sec 6 = 64 = 1,5 sek // Mittelwert der Höhe
BaroAltMw = avgAlt.res;
#ifdef SONAR
if (SonarErrors == 0)
{alt = SonarAlt; BaroAltGround = BaroAltMw - alt;}
#else BaroAltGround = BaroAltMw; alt = 0;
#endif
sensorAlt = BaroAltMw - BaroAltGround;
#ifdef SONAR
sonarUpdate(); // get sonar data and trigger next measure
if(SONAR_USED) { // Get difference between sonar and baro and slightly average it in time
average16(&baroSonarDiff, constrain(SonarAlt - sensorAlt, -32000, 32000), 6);
}
// Check if sonar is not crazy: its value compared to baro should not go outside window +/-3m
if(abs(baroSonarDiff.res) < 300) { //
00
sensorAlt += baroSonarDiff.res;
// Sonar gives precise values, use it
if(SonarErrors == 0) {
sensorAlt = SonarAlt;
// Sonar gives some errors: use cross-section of SONAR and BARO altitudes to softly switch to baro
} else if(SONAR_USED) { //(SonarErrors < SONAR_ERROR_MAX)
sensorAlt = (SonarAlt * (SONAR_ERROR_MAX - SonarErrors) + sensorAlt * SonarErrors)/SONAR_ERROR_MAX;
}
} else {
// Sonar is crasy, so use baro only + sonar value on the end of limits
sensorAlt += constrain(baroSonarDiff.res, -300, 300);
}
#endif
EstAlt = sensorAlt;
if (currentTime < deadLine2) return;
deadLine2 = currentTime + UPDATE_INTERVAL2;
// Dieser Teil der Routine wird nur 4 mal in der Sekunde ausgeführt
DeltaAltOld = DeltaAlt;
DeltaAlt = AltHold - EstAlt; // ist positiv bei zu niedrig
velocity = DeltaAlt - DeltaAltOld; // ist positiv bei sinken, negativ beim Steigen
//D
PidD = conf.D8[PIDALT] * (velocity) / 16; // ist positiv bei sinken // soll der schnellen Bewegung engegenwirken
BaroPID = PidD;
//P
PidP = conf.P8[PIDALT] * constrain(DeltaAlt,-100,100)/100;
BaroPID += PidP;
BaroPID = constrain(BaroPID,-150,+150);
//I
errorAltitudeI += DeltaAlt * conf.I8[PIDALT] / 16;
errorAltitudeI = constrain(errorAltitudeI,-3000,3000); // war 30000 erhöht auf 3750 max range dann +/- 75
PidI = errorAltitudeI / 75; //500 I in range +/-40
BaroPID += PidI;
//AnzeigeLeds zum Anzeigen der Regelabweichung
if (blinkerI == 4) {blinkerI = 0; if (blinker == 1) blinker = 0; else blinker = 1;} blinkerI++;
if ((EstAlt - AltHold) > 0) { digitalWrite(LEDup,LOW);digitalWrite(LEDmid,LOW);digitalWrite(LEDdown,blinker);}
if ((EstAlt - AltHold) > 30) { digitalWrite(LEDup,LOW);digitalWrite(LEDmid,LOW);digitalWrite(LEDdown,HIGH);}
if ((EstAlt - AltHold) < 0) { digitalWrite(LEDup,blinker);digitalWrite(LEDmid,LOW);digitalWrite(LEDdown,LOW);}
if ((EstAlt - AltHold) < -30) { digitalWrite(LEDup,HIGH);digitalWrite(LEDmid,LOW);digitalWrite(LEDdown,LOW);}
if (abs(EstAlt - AltHold) < 15) { digitalWrite(LEDmid,HIGH);}
if (abs(EstAlt - AltHold) < 5) { digitalWrite(LEDmid,blinker);digitalWrite(LEDup,blinker);digitalWrite(LEDdown,blinker);}
debug1 = DeltaAlt;
debug2 = PidP;
debug3 = PidI;
debug4 = PidD;
}