Ich muss zugeben, dass ich nicht der Fachmann bin, aber zur Problemlösung ist sicherlich noch dein CPU-Takt interessant!
Hallo zusammen,
ich habe für eine Hydraulik ein digitalen PID Regler gebaut. Differenzsignal geht auf einen AD, dann in den µC und am Ende wird die Stellgröße mit einem DAC ausgespuckt. Soweit so gut. Im Prinzip läuft das Ding auch recht gut.
Abtastfrequenz beträgt 10kHz. Mein Code sieht so aus (ein paar Dinge rausgemacht, die damit nichts zu tun haben):
So, nun zu dem Problem:Code:#include <avr/io.h> #include <avr/interrupt.h> //#include <avr/eeprom.h> #include <pid.h> #include <adc.h> #include <dac.h> double Kp = 0; double Ki = 0; double Kd = 0; double y = 0; double e = 0; double e_alt = 0; double e_sum = 0; #define Ta 0.0001 //unsigned char Kp_eeprom EEMEM; //unsigned char Ki_eeprom EEMEM; //unsigned char Kd_eeprom EEMEM; void init_pid(void) { // die gespeicherten Parameter laden und Regler initialisieren //Kp = (double) eeprom_read_byte(&Kp_eeprom); //Ki = (double) eeprom_read_byte(&Ki_eeprom); //Kd = (double) eeprom_read_byte(&Kd_eeprom); // PID Timer TCCR0 |= (1 << WGM01) | (1 << CS01) | (1 << CS00); //OCR0 = 250 ergibt 1kHz OCR0 = 25; // ergibt 10kHz } void pid_set_parameters(unsigned char p, unsigned char i, unsigned char d) { // Den aktuellen Variablen die neuen Parameter zuweisen Kp = (double) p; Ki = (double) i; Kd = (double) d; // Die neuen Parameter nicht-flüchtig ablegen, damit sie beim nächsten mal geladen werden können //eeprom_write_byte(&Kp_eeprom, (unsigned char) Kp); //eeprom_write_byte(&Ki_eeprom, (unsigned char) Ki); //eeprom_write_byte(&Kd_eeprom, (unsigned char) Kd); } void pid_start(void) { // Regler starten TIMSK |= (1 << OCIE0); } void pid_stop(void) { // Regler stoppen TIMSK &= ~(1 << OCIE0); } void pid_calculate(void) { // e = w-x ; Differenz soll-ist e = (double) adc_read(); // ADC Wert transformieren if (e >= 2048) { e -= 4096; } // WindUp vermeiden if (y < 4095) { e_sum += e; } // PID Stellgrößen Berechnung y = -Kp*0.1*e; y -= Ki*e_sum; y -= Kd*(e-e_alt); 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); } ISR(TIMER0_COMP_vect) { // mit 10kHz Abtastrate PID Algorithmus aufrufen, also alle 100µs pid_calculate(); }
Ich möchte während der Regler seinen Dienst tut die Regelparameter on the fly ändern.
Angesprochen wird der Regler über Rx/Tx und der Fleury Lib für UART, interruptgesteuerter Empfang.
Wenn der Regler noch nicht gestartet wurde, kann ich wunderbar die Parameter ändern. Starte ich diesen jedoch einmal, kann ich ihn weder anhalten noch Parameter ändern.
Sprich er reagiert über UART nicht mehr.
Wo liegt hier das Problem? Ist die Abtastfrequenz zu hoch und die UART Interrupts kommen nicht "durch", weil ständig der Timer Interrupt zuschläge oder was ist hier los?
Abtastfrequenz muss bei 10kHz bleiben, da ich die Hydraulik sonst nicht stabil bekomme.
Viele Grüße,
hacker
Ich würde ja gern die Welt verändern..., doch Gott gibt mir den Quellcode nicht!
Ich muss zugeben, dass ich nicht der Fachmann bin, aber zur Problemlösung ist sicherlich noch dein CPU-Takt interessant!
www.subms.de
Aktuell: Flaschcraft Funkboard - Informationssammlung
Für so zeitkritische Sachen würde ich auf nem AVR nicht mit floats arbeiten, sondern auf Festkommaarithmetik umstellen. Wenn es an der Rechenleistung liegt, dann dürfte das schon helfen.
Hi,
also ganz wichtig wäre erst mal:
Controller ?
Taktrate ?
Wie schnell und wie oft wird der ADC Wert bestimmt?
Wird der ADC Wert gefiltert?
bzw. Was passiert in adc_read() ?
Wird der Algorithmus wirklich in double gerechnet oder macht der Compiler daraus in wirklichkeit ne Fload? (Bin ich mir nicht sicher?)
Eine Festkommarechnung ist hier aber sicher vorzuziehen.
Was benutzt du für einen DAC? (allgemeine Frage)
gruß ch
Was steht eigentlich in "Main" ? Scheinbar lauft nur noch der Timer Interrupt, und wird auch nur "PID calculate" ausgefuhrt.
Ok, Festkommaarithmetik werde ich mal versuchen.
Controller ist Mega32 und läuft mit 16MHz. Der ADC wird einmal in der PID Berechnungs-Funktion geholt, also alle 100µs. Die reine Wandlung des externen ADCs beträgt ca. 1,5µs + die paar Befehle zum Daten abrufen (die Daten kommen parallel).
Nein, der ADC - Wert wird nicht gefiltert. In adc_read() passiert also nichts zeitaufwendiges. Es wird rein der ADC ausgelesen, mehr nicht.
Als DAC benutzte ich einen AD767.
In der Endlosschleife Main läuft rein gar nichts.
Kann ich mittels AVR Studio irgendwie rausbekommen, wie lange die PID Berechnung dauert?
Was passiert eigentlich, wenn eine ISR momentan ausgeführt wird und während dessen trifft ein neuer Interrupt ein? Verpufft dieser, oder wird der gemerkt, bis die akutelle ISR vorbei ist?
/*schluck*
Laut Simulator soll die Berechnung 350µs dauern...kommt mir arg lang vor.
/*holla*
Mit Shorts brauchts nur ganze 7,6µs!
Ich würde ja gern die Welt verändern..., doch Gott gibt mir den Quellcode nicht!
Ich hab grad keine Zahlen für den AVR, aber bei einem XC161 (16bit, 40MHz) braucht eine Float-Multiplikation etwa 6,4µs. Ganz grob geschätzt würde ich beim AVR mit dem fünffachen rechnen.
Du machst fünf Multiplikationen, macht also etwa 160µs. Daher dürfte der AVR die ganze Zeit im Timer-Interrupt hängen.
Wenn der Timer-Int aktiv ist und der serielle Interrupt tritt ein, wird dieser erst mal gespeichert. Wenn der Timer nun fertig ist, führt der AVR einen Befehl aus main aus. Dann bearbeitet er den nächsten anstehenden Interrupt. Inzwischen ist aber auch der Timer_interrupt nochmal aufgetreten, und wurde ebenfalls gespeichert. Nun wird nach der Priorität entschieden, wer zuerst drankommt. Und das ist der Timer...
Ich siehe keinen Grund warum etwas anderes als die function "PID_calculate" ausgefuhrt werden soll. Da stehts nicht in main, und nur diese ISR(TIMER0_COMP_vect) ist programmiert. Du muss auch einen function für diese serielle comm. programmieren.
Wenn die Ausführungszeit der Timer ISR länger ist als der Abstand der ISRs, dann kommt eine ISR von der USRT nie zur ausführung, denn die Timer interrupts haben die größere Priorität.
Da hilft also nichts als die ISR zu beschleunigen, indem man von den Floats weg kommt.
Die oben genannten 350 µs sind für Floats nicht besonders lang.
Man könnte ja auch einfach sagen "ich will die Regelfunktion mit maximaler Frequenz ausgeführt haben", und einfach die Funktion in die (im Moment leere) Main-Schleife packen (und auf den Timer-Interrupt verzichten). Dann hast du auch keine Probleme mehr mit Interrupt gesteuertem UART-Empfang nebenher.
MfG
Stefan
Lesezeichen