Hallo zusammen
Ich versuche mit dem Atmega128 mit 16MHz einen PID Regler für eine
Drehzahlregelung zu programmieren, leider bekomme ich nur instabile
Regelgrößen raus.
Für die Strecke habe ich Ks=1,06 Tg=2,15s und Tu=0,26s.
Mit den Einstellregel für PID nach Chien, Hrones und Reswick
habe ich folgende Reglerparameter berechnet:
Kp=7,4 Tn=2,9s Tv=0,12s Ta habe 0,01 gewählt, da mein Algorithmus durch
einen Timer alle 10ms aufgerufen wird.
Sollte doch eigentlich funktionieren.
#include <avr/interrupt.h>
#include "DAC.h"
#include "ADC.h"
#include "Regleralgorithmus.h"
volatile double Kp=7.4;
volatile double Tn=2.9;
volatile double Tv=0.13;
volatile double Ta=0.01;
volatile double I;
volatile double D;
volatile int e;
volatile int esum;
volatile int ealt;
volatile int x;
volatile int y;
volatile int w;
int main (void)
{
timer1_init();
sei();
DACInit();
I=Kp/Tn;
D=Kp*Tv;
while (1)
{
w = ReadAdcChannel(1)-512; // MUX-Bits auf 0b0001 -> Channel 1
// Istwert einlesen
x = ReadAdcChannel(0)-512; // MUX-Bits auf 0b0000 -> Channel 0
// Sollwert einlesen
//y0=w/1.06;
SetDAC(DACA, y+512);
}
return 0;
}
************************************************************************
********************
Header Datei von Regleralgorithmus
************************************************************************
********************
#include <avr/interrupt.h>
#include "DAC.h"
#include "Regleralgorithmus.h"
#define DURCHLAEUFE 156;
// Alle 10ms soll ein Overflow stattfinden
void timer1_init()
{
TCNT0 = 256-DURCHLAEUFE; // TCNT0 = Register in dem man
den Startwert festlegt.
/* 10-Bit Vorzaehler,wir wollten durch 1024 teilen, also CSO0=1 und
CSO2=1 setzen: 16MHz/1024=15625Hz */
TCCR0 = (1 << CS02)|(1 << CS00); // Timer Takt auf 16MHz/1024
/* Interrupt bei Overflow aktivieren
TIMSK = (1<< TOIE0); // Deshalb TOIE0 (Timer/Counter0 Overflow
Interrupt Enable) = 1
/* Alle Interrupts erlauben mit der Funktion sei () */
sei ();
}
ISR (TIMER0_OVF_vect)
{
// Regeldifferenz bilden
e = w - x; // aktuelle
Regelabweichung bestimmen
if ((y < 511)&&(y > -512)) // bei Übersteuertem
stellglied Integration einfrieren
{ // (Anti-Windup)
esum = esum + e; // Summe der
Regelabweichung aktualisieren
}
y = (Kp*e)+(I*Ta*esum)+(D*(e-ealt)/Ta); //Reglergleichung
ealt = e; //Regelabweichung für
nächste Abtastung merken
if (y > 511) // Stellgröße auf
-511...+512 begrenzen (10 bit DAC)
{
y = 511;
}
if (y < -512)
{
y = -512;
}
// Neu laden, damit Timer nicht bei 0 beginnt
TCNT0 = 256-DURCHLAEUFE;
}
Wobei, du hast da ein "#include "Regleralgorithmus.h" stehen. Ich glaube
du hast dich nur verschrieben und doch die Regleralgorithmus.c-Datei
gepostet und nicht den Header, oder?
@Karl heinz Buchegger
Ich muss mich noch mal entschuldigen die ist nicht auskommentiert. Ich
habe wahrscheinlich bei posten irgenwie das */ gelöscht. Aber bei mir im
AVR Studio ist es drin.
Was mir auffällt (wenn ich das richtig gesehen hab), du aktualisierst in
der main ständig die werte w und x und benutzt die aber gleichzeitig in
der ISR. Stichwort atomare Zugriffe. Int ist 2 Bytes. Es kann also
passieren, dass deine ISR einen Wert liest während in der main gerade
die Variable geschrieben wird und dann bekommt du unter Umständen Müll.
um die Schreibzugriffe gehören deswegen ein cli() und sei().
Hallo
erstmal danke.
>>Wie ist die Signatur von ReadAdcChannel(1) Liefert die einen int oder einen
unsigned int?
Einen int Wert
>>um die Schreibzugriffe gehören deswegen ein cli() und sei().
Was meinst du damit ??
Gast 2009 schrieb:
>>>um die Schreibzugriffe gehören deswegen ein cli() und sei().>> Was meinst du damit ??
Wenn ein int geschrieben wird werden nacheinander 2 Bytes kopiert. Wenn
nun nach dem ersten Bytes zufällig dein Interrupt zuschlägt, hast du ein
int in dem das eine Byte zum alten Wert und das andere Byte zum neuen
Wert gehört. Und das ist natürlich völliger Datenmüll. Also: Interrupt
während dem schreiben sperren. Und das geht mit cli() und sei().
Hallo
ich habe meine Interrupt Routine mal mit cli und sei erweitert,
allerdings ohne Erfolg.
ISR (TIMER0_OVF_vect)
{
cli();
// Regeldifferenz bilden
e = w - x; // aktuelle
Regelabweichung bestimmen
if ((y < 511)&&(y > -512)) // bei Übersteuertem
stellglied Integration einfrieren
{ // (Anti-Windup)
esum = esum + e; // Summe der
Regelabweichung aktualisieren
}
y = (Kp*e)+(I*Ta*esum)+(D*(e-ealt)/Ta); //Reglergleichung
ealt = e; //Regelabweichung für
nächste Abtastung merken
if (y > 511) // Stellgröße auf
-511...+512 begrenzen (10 bit DAC)
{
y = 511;
}
if (y < -512)
{
y = -512;
}
// Neu laden, damit Timer nicht bei 0 beginnt
TCNT0 = 256-DURCHLAEUFE;
sei();
}
Mich wundern nur, das der reine P-Regler relativ gut funktionert. Liegt
es nicht vielleicht an Ta=???
Hier der P-Regler Code:
#include <avr/interrupt.h>
#include "DAC.h"
#include "ADC.h"
#include "Regleralgorithmus.h"
volatile double Kp=7.4;
volatile int e;
volatile int x;
volatile int y;
volatile double y0;
volatile int w;
int main (void)
{
timer1_init();
sei();
DACInit();
while (1)
{
w = ReadAdcChannel(1)-512; // MUX-Bits auf 0b0001 -> Channel 1
// Istwert einlesen
x = ReadAdcChannel(0)-512; // MUX-Bits auf 0b0000 -> Channel 0
// Sollwert einlesen
y0=w/1.06;
SetDAC(DACA, y+512);
}
return 0;
}
************************************************************************
********************
c-Datei von Regleralgorithmus
************************************************************************
********************
#include <avr/interrupt.h>
#include "ADC.h"
#include "Regleralgorithmus.h"
#define DURCHLAEUFE 156;
void timer1_init()
{
TCNT0 = 256-DURCHLAEUFE; // TCNT0 = Register in dem man
den Startwert festlegt.
/* 10-Bit Vorzaehler,wir wollten durch 1024 teilen, also CSO0=1 und
CSO2=1 setzen: 16MHz/1024=15625Hz */
TCCR0 = (1 << CS02)|(1 << CS00); // Timer Takt auf 16MHz/1024
/* Interrupt bei Overflow aktivieren*/
TIMSK = (1<< TOIE0); // Deshalb TOIE0 (Timer/Counter0 Overflow
Interrupt Enable) = 1
/* Alle Interrupts erlauben mit der Funktion sei () */
sei ();
}
ISR (TIMER0_OVF_vect)
{
cli();
// Regeldifferenz bilden
e = w - x; // aktuelle
Regelabweichung bestimmen
y = (Kp*e)+y0; //Reglergleichung
//Regelabweichung für nächste Abtastung
merken
if (y > 511) // Stellgröße auf
-511...+512 begrenzen (10 bit DAC)
{
y = 511;
}
if (y < -512)
{
y = -512;
}
// Neu laden, damit Timer nicht bei 0 beginnt
TCNT0 = 256-DURCHLAEUFE;
sei();
}
> ich habe meine Interrupt Routine mal mit cli und sei erweitert,> allerdings ohne Erfolg.
Das war auch nicht zu erwarten. :-)
Dieser Fehler ist nur ein Beifang.
> Mich wundern nur, das der reine P-Regler relativ gut funktionert. Liegt> es nicht vielleicht an Ta=???
Bist du sicher das deine Regelparameter auch stimmen? Wie hast
du die bestimmt? Ausserdem wuerde ich die eher nur als erste
Naeherung ansehen.
Drehst du nur einen Motor oder haengt da noch irgendwas dran?
Gibt es z.B ein Getriebe mit Spiel?
Schaff dir eine Moeglichkeit waehrend dein Regler arbeitet Parameter
auszugeben. Vielleicht RS232? Oder eine andere PWM?
Dann schaust du dir da mal deine einzelnen Regelparameter an. Vielleicht
hast du irgendwo einen Sprung weil dir ein Register ueberlaeuft.
> volatile double Kp=7.4;
Hast du Grund zu der Annahme das dein Controller schnell genug ist
das du mit solchen Zahlen arbeiten kannst? Schalte mal am Anfang deiner
IRQ-Routine einen Port auf High und am Ende auf Low und kuck mal ob du
am Oszi siehst was du erwartet.
Wo kommt deine Drehzahlinformation her? Tastest du dort schnell genug
hab?
Olaf
Olaf schrieb:
>> Mich wundern nur, das der reine P-Regler relativ gut funktionert. Liegt>>> es nicht vielleicht an Ta=???>> Bist du sicher das deine Regelparameter auch stimmen? Wie hast>> du die bestimmt? Ausserdem wuerde ich die eher nur als erste>> Naeherung ansehen.
Reglerparameter sollten stimmen, ich habe die wie oben schon
geschrieben, mit den Einstellregel für PID nach Chien, Hrones und
Reswick gerechnet.
>> Drehst du nur einen Motor oder haengt da noch irgendwas dran?>> Gibt es z.B ein Getriebe mit Spiel?
Nein, ich drehe nur den Motor.
>> Schaff dir eine Moeglichkeit waehrend dein Regler arbeitet Parameter>> auszugeben. Vielleicht RS232? Oder eine andere PWM?
Habe ich schon keine unauffälligkeiten.
>
Hehe,
>> Habe ich schon keine unauffälligkeiten.
d.h. Du siehst also nur Unfug ;-)
Also
1. Bitte mal alle! Dateien anhängen in *.c, *.h Form.
Makefile ? .... etc.
2. Du scheinst ISR Abläufe + main-Programmfluss nicht
verstanden zu haben.
Bitte dazu mal ISR lesen, wofür genau braucht man cli(), sei()
Wann braucht man diese und wie verhindere ich
falsche Werte in Variablen bei ISRs, wie oben beschrieben ?
Gruß Sven
Gast 2009 schrieb:
>> du die bestimmt? Ausserdem wuerde ich die eher nur als erste>>>> Naeherung ansehen.>> Reglerparameter sollten stimmen, ich habe die wie oben schon> geschrieben, mit den Einstellregel für PID nach Chien, Hrones und> Reswick gerechnet.
Ganz ehrlich.
Auf die würde ich keinen Deut geben.
Gast 2009 schrieb:
> ich habe meine Interrupt Routine mal mit cli und sei erweitert,>> allerdings ohne Erfolg.
Hallo,
cli in der Interruptroutinen ist immer unwirksam, während der Routine
sind die automatisch disabled.
Ich denke, es war main gemeint....
Gruß,
Michael
Dito. Das beste Verständnis für einen PID Regler bekommt man, wenn man
die Parameter einfach mal etwas herumprobiert und dabei so
grundsätzliche Sachen im Kopf hat, dass bei höherem D Anteil der P
Anteil ebenfalls angehoben werden kann.
Den I Anteil kann man Anfangs auch ganz ignorieren und wenn der PD
Regler gut funktioniert, kann man hier mit herumprobieren.
Diesbezüglich verweise ich auf den Roboternetz-Artikel zu dem Thema. Der
ist nicht vollständig (wäre auch völlig Overkill), dafür aber leicht
verständlich und ein paar wichtige Sachen stehen drin.
EDIT: Auch interessant:
http://de.wikipedia.org/wiki/Faustformelverfahren_%28Automatisierungstechnik%29#Empirische_Dimensionierung
Ich glaube wenn ich jetzt alle Dateien dran hänge bringt das nur noch
mehr durch einander. Kann ich dir das nicht lieber als Email schicken.
Wenn du nichts dagegen hast.
Meine Email: uc_forum@web.de
Danke
Ich glaube nicht, dass er ein persönliches Anliegen an den Dateien hat,
sondern eher, dass er möchte, dass auch andere Forenbenutzer die Dateien
einsehen können um dir zu helfen.
Wie so oft: Die Strecke ist nicht linear! Stellgrößenbegrenzung
beachten, Mit P-Regler anfangen, Sprungantworten aufnehmen!!
Einstellregeln vergessen, die taugen für viele Fälle NICHT.
micha54 schrieb:
> Hallo,>> cli in der Interruptroutinen ist immer unwirksam, während der Routine>> sind die automatisch disabled.>> Ich denke, es war main gemeint....
Was meinst du mit ich denke main ist gemeint.
> Ganz ehrlich.> Auf die würde ich keinen Deut geben.
Hehe, mich hat der unerschuettliche Glaube an ein paar Formeln
auch ueberrascht. :-)
> Wie so oft: Die Strecke ist nicht linear!
Ah! Guter Einwand. Garnicht dran gedacht. Ich wuerde evenutell
empfehlen den Strom zu regeln, oder den Ausgang zu linearisieren.
Olaf
Gast 2009 schrieb:
> micha54 schrieb:>> Hallo,>>>> cli in der Interruptroutinen ist immer unwirksam, während der Routine>>>> sind die automatisch disabled.>>>> Ich denke, es war main gemeint....>> Was meinst du mit ich denke main ist gemeint.
Du greifst auch in main auf die beiden Variablen zu.
Dort sind die Zugriffe aber nicht atomar ausgeführt (mit cli() und
sei()) geschützt, sodass dir kein Interrupt dazwischen knallen kann,
wenn das LowByte schon geschrieben ist, das High-Byte aber noch nicht)
Du hast hier zb stehen
x = ReadAdcChannel(0)-512; // MUX-Bits auf 0b0000 -> Channel 0
Der Compiler implementiert das irgendwie so
call ReadAdcChannel // returnwert in einem Registerpaar
lade Adresse von x
schreibe HighByte vom Ergebnis nach Adresse von x
schreibe LowByte vom Ergebnis nach (Adresse von x)+1
Das ist alles soweit unproblematisch, bis auf den einen Fall. Nämlich
dann, wenn ein Interrupt genau zwischen Schritt 3 und Schritt 4 kommt.
IN dem Fall ist das HighByte schon nach x abgespeichert worden. Aber das
LowByte noch nicht! Das kümmert aber den Interrupt nicht. Der kommt
trotzdem. Und als Folge davon rechnet deine ISR mit einem Wert für x,
der nicht stimmt, weil das Ergebnis vom ADC erst zur Hälfte
abgespeichert wurde.
Also musst du das abspeichern atomar machen, sodass da kein Interrupt
reinknallen kann
tmp = ReadAdcChannel(0)-512;
cli();
x = tmp;
sei();
jetzt ist das eigentliche Abspeichern 'atomar'. Kein Interrupt kann es
unterbrechen.
Hallo zusammen
Viele Danke für eure Beiträge !!!
ich habs mit ein bisschen rumprobieren hinbekommen den Motor zu regeln,
allerdings nur durch rumprobieren und nicht wirklich logisch.
Ich rufen den Algorithmus jede 1ms durch einen Timer Interrupt auf.
Mein ADC Wandlung benötigt 0,1ms
Jetzt meine alles entscheidende Frage :-), Ta muss doch gleich 1ms
(0,001) sein wie der Overflow vom Timer?? Oder irre ich da.
Also ist die Overflow Zeit meine Abtastzeit oder ist doch die ADC Zeit
meine Abtastzeit ??? Sorry aber ich bleib bei dieser Frage echt hängen.
Mich wundert nur das es mit 1ms klappt, denn in der Literatur findet man
bei Wahl der Abtastzeit, eine Tabelle die sagt.
0,2*Tu < Ta < 0,5 Tg und mit einem Tu=0,26s und Tg=2,15s, ist mein
Ta=1ms kleiner als 0,2*Tu ?
Gast 2009 schrieb:
> Also ist die Overflow Zeit meine Abtastzeit oder ist doch die ADC Zeit> meine Abtastzeit ??? Sorry aber ich bleib bei dieser Frage echt hängen.
Die Abtastzeit ist in diesem Zusammenhang das Zeitintervall welches von
einem Feststellen des Istwertes bis zum nächsten Feststellen des
Istwertes vergeht.
Wie lang dein ADC zur Wandlung braucht interessiert doch keine
allgemeine Formel!
Interessant ist die Zykluszeit! Wie lange dauert es, bis der Regler mit
einem Zyklus durch ist und der nächste beginnt
* Istwert feststellen
* Reglergleichung auswerten
* Stellglied einstellen
* und wieder alles von vorne
Bei dir ist die Zykluszeit daher offensichtlich die Zeit zwischen 2
Interrupts.
> ich habs mit ein bisschen rumprobieren hinbekommen den Motor zu> regeln, allerdings nur durch rumprobieren und nicht wirklich logisch.
Das wirst du auch logisch nicht hinkriegen.
Dazu gibt es viel zu viele Unbekannte im Regelkreis.
> ich habs mit ein bisschen rumprobieren hinbekommen den Motor zu regeln,> allerdings nur durch rumprobieren und nicht wirklich logisch.
Naja, ich glaube das ich sicher noch Diskussionsfaehig. :)
> Ta muss doch gleich 1ms> (0,001) sein wie der Overflow vom Timer?? Oder irre ich da.
Dein Abtastzeit hat nichts mit der Wandlungszeit deines AD-Wandlers
zutun. Dein AD-Wandler muss nur schneller sein wie deine Abtastzeit.
Die Abtastzeit wird letztlich bestimmt durch die Dynamik deines Systems.
Wenn du also z.B einen kleinen Motor mit sehr wenig Masse hast dann kann
er sehr schnell seine Drehzahl aendern. Hast du dagegen einen dicken
Klotz mit grosser Schwungmasse so reagiert der langsamer. Es ist dir
dann moeglich auch langsamer abzutasten. Allerdings koenntest du auch
einfach immer sehr schnell abtasten. Das kostet dich dann halt unoetige
Rechenzeit.
Ausserdem haengt das natuerlich davon ab was du mit deiner Regelung
beabsichtigst. Also wie soll dein Regler auf einen Stoergroessensprung
reagieren, wie schnell soll er sich anpassen wenn du die Solldrehzahl
aenderst.
Allerdings aendern sich mit einer Aenderung an der Abtastzeit auch deine
ganzen Regelparameter. Darum legt man die zuerst fest und aendert sie
dann nie mehr.
Olaf
hi
danke für deine schnelle Antwort, hast du denn auch eine Erklärung warum
es mit 1ms klappt, obwohl man in der Literatur halt diese Tabelle
findet in der bei Wahl der Abtastzeit steht.
0,2*Tu < Ta < 0,5 Tg und mit einem Tu=0,26s und Tg=2,15s, ist mein
Ta=1ms kleiner als 0,2*Tu ?
kann es sein das meine Abtastzeit so klein ist dass, man sie
vernachlässigen kann???
Quelle:http://www.rn-wissen.de/index.php/Regelungstechnik
"Ist die Abtastzeit sehr viel kleiner als die dominierende Zeitkonstante
der Strecke, dann spricht man von quasi-kontinuierlichem Verhalten, da
sich der digitale Regler ähnlich wie ein analoger Regler verhält. Der
digitale Regler kann dann wie ein analoger dimensioniert werden"
Gast 2009 schrieb:
> "Ist die Abtastzeit sehr viel kleiner als die> dominierende Zeitkonstante
**************************
Das ist der alles bestimmende Paramater.
Wenn deine dominierende Zeitkonstante 1 Stunde ist (weil zb am Motor
eine Schungmasse mit ein paar 100 Tonnen Masse hängt und der Motor seine
Drehzahl daher in 1 Stunde maximal um 2 U/min verändern kann, dann
reicht es auch wenn du alle halbe Stunde die Drehzahl feststellst und
nachregelst um die Drehzahl auf 5 U/min konstant zu halten.
Daher kann das hier
0,2*Tu < Ta < 0,5 Tg und mit einem Tu=0,26s und Tg=2,15s
nur eine Faustformel sein.
Es gibt keine universell einsetzbaren Parameter. Jede Reglerstrecke ist
anders. Ein anderer Motor ist durchzugsstärker und beschleunigt besser.
Im nächsten Reglerkreis ist die zu drehende Masse größer und der Motor
beschleunigt langsamer. Ein Motor hat sein Drehmomentmaximum bei
niedrigen Drehzahlen, ein anderer hat es bei höheren Drehzahlen, ....
All das erfordert eine andere Abstimmung der Regelparamter. Kennst du
diese (und nich viel mehr) Werte deines Regelkreises?