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