PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : digitaler PID Regler



hacker
08.04.2010, 19:22
Hallo zusammen,

leider hat mich das Thema PID Regler eingeholt. Ich musste feststellen, dass dieser doch nicht mit dem Status "fertig" versehen werden kann.

Folgende ältere Threads dazu:

https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=50223&highlight=digitaler+pid+regler

https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=52476&highlight=digitaler+pid+regler


Der Regler regelt mit einer Abtastzeit von 100µs (10 kHz).

Hier der Code:


#include <avr/io.h>
#include <avr/interrupt.h>

#include <pid.h>
#include <adc.h>
#include <dac.h>

volatile unsigned long Kp = 0;
volatile unsigned long Ki = 0;
volatile unsigned long Kd = 0;
volatile short y = 0;
volatile unsigned short y_p_pos = 0;
volatile unsigned short y_p_neg = 0;
volatile unsigned short y_i_pos = 0;
volatile unsigned short y_i_neg = 0;
volatile unsigned short y_d_pos = 0;
volatile unsigned short y_d_neg = 0;
volatile short e = 0;
volatile short e_alt = 0;
volatile long e_sum = 0;

void init_pid(void)
{
// PID Timer
TCCR0 |= (1 << WGM01) | (1 << CS01) | (1 << CS00);
//OCR0 = 250 ergibt 1kHz
OCR0 = 25; // ergibt 10kHz
}

void pid_set_parameters(unsigned long p, unsigned long i, unsigned long d)
{
// Den aktuellen Variablen die neuen Parameter zuweisen
pid_stop();
Kp = p;
Ki = i;
Kd = d;
pid_start();
}

void pid_start(void)
{
// Regler starten
TIMSK |= (1 << OCIE0);
}

void pid_stop(void)
{
// Regler stoppen
TIMSK &= ~(1 << OCIE0);
}

ISR(TIMER0_COMP_vect)
{
// mit 10kHz Abtastrate PID Algorithmus aufrufen, also alle 100µs

// e = w-x ; Differenz soll-ist
e = (short)adc_read();

// ADC Wert transformieren
if (e >= 2048)
{
e -= 4096;
}
// WindUp vermeiden
if ((y < 4095) & (e_sum < 1000000000) & (e_sum > - 1000000000))
{
e_sum += e;
}
// PID Stellgrößen Berechnung

// P-Anteil
if (-e >= 0)
{
if (-Kp*e <= 2095616)
{
y_p_pos = (unsigned short)((-Kp*e + 512) >> 10);
}else
{
y_p_pos = 2047;
}
y_p_neg = 0;
}
if (-e < 0)
{
if (Kp*e <= 2096640)
{
y_p_neg = (unsigned short)((Kp*e + 512) >> 10);
}else
{
y_p_neg = 2048;
}
y_p_pos = 0;
}

// I-Anteil
if (-e_sum >= 0)
{
if (-Ki*e_sum <= 34334572544)
{
y_i_pos = (unsigned short)((-Ki*e_sum + 8388608) >> 24);
}else
{
y_i_pos = 2047;
}
y_i_neg = 0;
}
if (-e_sum < 0)
{
if (Ki*e_sum <= 34351349760)
{
y_i_neg = (unsigned short)((Ki*e_sum + 8388608) >> 24);
}else
{
y_i_neg = 2048;
}
y_i_pos = 0;
}

// D-Anteil
if (-(e-e_alt) >= 0)
{
if (-Kd*(e-e_alt) <= 2095616)
{
y_d_pos = (unsigned short)((-Kd*(e-e_alt) + 512) >> 10);
}else
{
y_d_pos = 2047;
}
y_d_neg = 0;
}
if (-(e-e_alt) < 0)
{
if (Kd*(e-e_alt) <= 2096640)
{
y_d_neg = (unsigned short)((Kd*(e-e_alt) + 512) >> 10);
}else
{
y_d_neg = 2048;
}
y_d_pos = 0;
}

y = y_p_pos - y_p_neg + y_i_pos - y_i_neg + y_d_pos - y_d_neg;

e_alt = e;
// Stellgröße beschränken
if (y > 2047){y = 2047;}
if (y < -2048){y = -2048;}
// Stellgröße für den DAC transformieren
y += 2048;
// Stellgröße für das Ventil ausgeben
dac_write((unsigned short)y);
}


Die großen Zahlen kommen daher, weil die Regelparameter mit 1024 hoch skaliert sind. Deswegen wird bei der Berechnung wieder um 10 geshiftet (/1024). Mit den "+512" binde ich elegant die nötige Rundung ein. Da bei einer neg. Zahl die Shift Operation teilweise undefiniert ist (ist doch so, oder?), prüfe ich vorher und shifte die pos. Zahl und mache sie dann wieder negativ.

Die Zeit ist ziemlich kritisch, daher verwende ich die Shift-Operationen. Bei einer Skalierung mit 1000 hab ich die Ausführungszeit nicht < 100µs bekommen.

So viel zur Code Erklärung.


Nun zu den Fragen/Problemen, wieso ich hier poste:

Der Code als reiner P - Regler funktioniert astrein. Was noch nicht so will ist der integrierende und differenzierende Anteil.

Noch kurz als Info: Die Regelstrecke weißt integrierendes Verhalten auf. D.h eigentlich bräuchte ich keinen PID, sondern ein PD Regler. Aber kann ich den P-Faktor nicht so wählen, dass mein System (in dem Fall der Kolben) merklich zu schwingen anfängt (starkes Vibrieren) und mit einem zusätlichen I-Anteil das System wieder träger und demnach ruhiger machen?

Bei meinen Versuchen zeigte sich das Phänomen, dass ich mit einem I-Anteil das System leider nur für kurze Zeit ruhiger bekommen habe. Danach hat die Schwingungsamplitude wieder zugenommen.

Ich vermute ebenfalls ein Code Fehler beim I-Anteil. Ich fürchte, dass es irgendwo ein Variablenüberlauf gibt. Beleg dafür:

Ich testete mit einer bleibenden Soll-Ist Differenz von 5V und einem reinen I-Regler (die Strecke war quasi abgeklemmt). Stellgröße ist max. 10V. I-Faktor war ganz klein, so das ich das Ansteigen der Stellgröße mit einem Multimeter beobachten konnte.
Meine Vorstellung war, dass die Stellgröße mit der Zeit langsam (e_sum wird immer größer) zunimmt, bis sie eben ihr max. erreicht hat bei 10V. Was passierte war aber, dass die Spannung anstieg bis ca. 1,3V und dann wieder bei 0V anfing hochzulaufen bis wieder "resetet" wurde.

Daher denke ich irgendwo läuft eine meiner Variablen über. Und ehrlich gesagt hab ich auch kein Überblick mehr über die 4Byte großen Dinger und den damit verbundenen Multiplikationen.


Nächsten Problem der D-Anteil:

Schon mit einem D-Faktor von 1 fängt der Kolben an wild zu schwingen, richtig unkontrolliert. Muss der Faktor einfach weiter runter, oder sollte man da eine Art "Totband" drum rum legen, dass erst ab einer gewissen größeren Änderung der D-Anteil zu wirken beginnt?

Das Istsignal ist auf analoger Seite tiefpass gefiltert. Die gesampelten Werte im µC werden jedoch knallhart so genommen, wie diese sind. Sollte man da noch ein wenig was machen?



Grundproblem ist eben mit nur einem reinen P-Regler, dass wenn ich eine steile Rampe fahren will (Weg), der Kolben entweder die konstante Geschwindigkeit nicht erreicht (P-Faktor zu klein) oder eben erreicht, aber dann mächtig überschwingt (P-Faktor zu groß). Mächtig überschwingen heißt bei mir ca. 30%.


Viele Fragen, ich weiß...

So, und nun lasst mal euren Gedanken freien lauf und postet. :)


Grüße,
hacker

hacker
08.04.2010, 20:56
Dem Überlauf bin ich mittels Simulator auf die Schliche gekommen. Wenn ich e_sum auf Long Long setzte, schaffe ich meine 10kHz aber leider nicht mehr.

Gibt es sinnvolle Empfehlungen auf welche Größe man die e_sum begrenzen soll? z.B 10x die max. Regelabweichung.

Besserwessi
08.04.2010, 21:26
Die Aufteilung in Positive / Negative Fälle machte den Code ziehmlich unübersichtlich und ganz nebenbei auch langsam.
Soweit ich weiss kriegt gcc die optimierung schon soweit hin, dass man auch ruhig .... % 1024 schreiben kann. Da werden dann schon shifts draus, die auch für negative Zahlen gehen.
Beim AVR sind aber die Shifts auch nicht besonders schnell. Wenn es wirklich schnell sein soll, sollte man ganze Bytes als Nachkomma-anteil haben, und dann mit UNION arbeiten. Ich sehe noch kein Überlaufprobem wenn man jeweils 16 Bits nach der Multiplication wegläßt. Man muß sich dann nur beim Integralgleid ein wenig mit dem Wert zurückhalten, aber so groß sollte das ja ohnehin nicht werden, sonst wäre der Integral anteil einfach zu groß in der Summe.

Das macht dann schon mal den Code nur etwa 1/3 so lang und vermutlich auch 2-3 mal schneller. Eventuelle Fehler lassen sich dann auch besser finden.

Unabhängig davon sollte man die 3 Anteile erstmal mit voller Auflösung zusammenrechenen und dann erst die Rundung machen.


Der Effekt mit dem I-Glied spricht wirklich für ein Überlaufproblem. Wenn man von 32 Bits (Long) noch 24 Bits per shift unterdrückt, kann das auch nichts werden, da bleichen dann ja nur 8 Bit über, und damit wird es nie mehr als die 1,3 V. So viele Nachkommastellen für die Rundung sind nicht nötig. Also statt:
((Ki*e_sum + 8388608) >> 24);
besser
((Ki*(e_sum>>8 ) + (1<<15)) >> 16);
Wenn man nur ganz kleine Werte für Ki braucht, dann auch:
((Ki*e_sum + (1<<15)) >> 16);
mit angepaßtem zahlenwert für Ki.

Beim Differenzgleid hat man eventuel ein Rauschproblem: wenn man die Zeit über die man die Steigung berechnet zu kurz hat, kreigt man leicht ein starkes Rauschen rein: Die ADC Werte ändern sich bei aufeinanderfolgenden Werten nur sehr wenig. Für die Differenz hat man dann nur ein paar weniger quantisierte Werte von vielleicht nur - 5 bis +5. Wenn der Aktor sehr schnell realgiert kann das ein Problem werden.

Es kann aber auch gut gehen und man bekommt dann ein überlagertes Dithering für die Ausgabewerte. Die Regelung kann dann trotzdem funktionieren, zum Teil sogar besser als ohne den Effekt zu erwarten wäre, aber die Parameter sind nicht mehr unbedingt das, was man von der Theorie erwartet.

jeffrey
08.04.2010, 21:42
hallo,
heiteres parameter raten ist bei nem regler zwar oft lustig, aber selten zielführend ;-)
ich habe mir den code jetzt nicht angeschaut. aber paar hinweise zum regler. wenn die strecke schon nen i-anteil hat, würde ich dem regler keinen geben, weil dadurch das ganze eher schwingfähig und deswegen anfälliger für schwingungen wird. ohne genaueres wissen über die strecke ist es aber schwierig genauer aussagen über den reglertyp zu machen, wobei wenn der p-regler funktiert, würde ich den verwenden.
sonst halt erst mal ne sprungantwort aufnehmen, falls das möglich ist.
mfg jeffrey

hacker
14.04.2010, 15:03
Hallo,

danke für die Antworten. Momentan hab ich es so hinbekommen, dass der Regler (jedoch ohne I-Anteil) sauber funktioniert. Jedoch gibt es noch folgende zwei Merkwürdigkeiten/Probleme:

Aufgrund des I-Anteil der Strecke wurde oben ja empfohlen keinen zusätzlichen I-Anteil in den Regler mit aufzunehmen. Wenn ich jedoch zwischen zwei verschiedenen P-Faktoren live umschalte, dann macht mein Kolben (Weg-geregelt) ein kleinen Satz. D.h da muss noch eine Regeldifferenz sein, die er nicht ausgeregelt bekommt (Messungen haben das bestätigt). Je höher die Verstärkung desto näher kommt er an das wirkliche Sollsignal heran. Irgendwann erreicht er es und er schwingt um diese Lage.
Dieses Verhalten sagt mir aber, dass ich doch einen zusätlichen I-Anteil brauche oder?
Also I-Faktor hochgedreht und jetzt nähert er sich tatsächlich nächer an. Jedoch wenn ich den Faktor zu groß mache, schießt er über den Sollwert hinaus und bleibt dort auch! Warum kommt der nicht wieder zurück? Eigentlich sollte er doch bei zu hohem I-Anteil zum Schwingen an zu fangen.



Das nächste Problem bestimmt bei der Kraftregelung. Man spannt eine Probe an und fährt z.B eine konstante Kraft (Offset) von 2kN an. Mit P-Anteil hat man noch die bleibende Regelabweichung. Also wieder I - Anteil hinzu. Das selbe Problem wie oben: Ist der Faktor zu klein, erreiche ich den Sollwert nicht wirklich, ist er zu groß schießt er wieder über den Sollwert hinaus und verharrt dort.

So und jetzt kommt das nächste Kuriose: Die Kraft kann mit der Zeit irgendwie nicht gehalten werden. Ich kann zuschauen und ganz langsam nimmt die Kraft ab. Klar, die Probe erweicht, aber dafür ist doch der Regler da, dass er dann eben bisschen mehr dem Aktor gibt. Ergebnis sollte doch sein, dass dann die Probe immer länger und länger wird.


Könnt ihr euch zu oben beschriebenem einen Reim machen?

Grüße,
hacker

hacker
14.04.2010, 15:49
Oh man, ich sollte früher hier im Forum schreiben. Irgendwie bekommt es dann mein persönlicher Fehlerteufel mit der Angst zu tun und schon kommt man selbst auf den Fehler.

Der Code für den I-Anteil war falsch. Man sollte natürlich immer aufsummieren und danach evt. begrenzen. ;-)


Das Problem, dass die Kraft über die Zeit nicht gehalten werden kann, wird jetzt nochmal mit richtigem I-Anteil getestet. Ich werde davon berichten.

Grüße,
hacker

hacker
15.04.2010, 13:01
Es besteht leider immer noch das Problem, das die Kraft über die Zeit nicht exakt gehalten werden kann.

Mit der Zeit nimmt die Offset-Kraft langsam ab.


Wie könnte man das lösen? Im Prinzip müsste der Regler erkennen, dass sich seine Strecke ändert (Probe erweicht) und dann seine Parameter weiter aufdrehen?

Bitte um Hilfe.