mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik PID-Regler: Wie schnell ist schnell genug?


Autor: M. K. (sylaina)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Ich hatte die Tage etwas, ich sag mal, langeweile und hab mal mein altes 
Regelungstechnikwissen ausgepackt und auf nem AVR (konkret: Atmega328P) 
einen PID-Regler zusammen gestrickt.
Als Regelstrecke hab ich einen simplen Tiefpass mit 10kOhm und 100µF 
aufgebaut, die von einer 8 Bit PWM gespeist wird.
Der Regler soll die PWM so regeln, dass sich die Ausgangsspannung des 
TPs auf einen bestimmten Wert einstellen soll.

Das funktioniert auch Super, da kann ich nicht klagen (hab ein Bild und 
die Messdaten hier mal angehangen).

Mir ist bewusst, da ich hier beim PID floats benutze, ist das nicht die 
schnellste Implementierung. Ich hab mal mit Timer1 die Counts gezählt, 
die die Funktion pid() benötigt und bekomme hier so rund 2500-2700 
Counts. Da der AVR bei mir mit 8 MHz läuft bedeutet dies, dass die 
Berechnung so rund 325µs dauert.

Die Frage, die sich mir nun noch stellt, und die ich bisher nicht 
ermitteln konnte: Ist das für einen PID-Regler langsam, normal oder 
schnell? Wie schnell sollte eine Berechnung der Werte erfolgen? Wie 
schon gesagt, mir ist klar, dass es auch schneller geht, mir fehlt jetzt 
nur das Gespür dafür, wie schnell meine Implementierung ist.
#include <stdlib.h>
#include <avr/io.h>
#include "uart.h"


#define SETVALUE 2.0

char valueToPrint[6];
uint16_t adcValue;
volatile uint16_t i=0;
volatile float voltage;

typedef struct PID_CONTROLLER{
  float kpFactor;
  float kiFactor;
  float kdFactor;
  float taFactor;
  float lastValue;
  float error;
  float lastError;
  float preLastError;
} pid_controller_t;

#define KP 8000.0 
#define KI 2.0
#define KD 0.4
//Abtastzeit: ADC-Prescaler=64, 13 Zyklen für eine Wandlung und F_CPU = 8 MHz => ca 0.1 ms
#define TA 0.0001

uint8_t pid(float sollWert, float istWert, pid_controller_t* controller){
  controller->error = sollWert - istWert;
  
  float y = controller->lastValue 
        + (controller->kpFactor+controller->kiFactor+controller->kdFactor/controller->taFactor) * controller->error 
        + (-controller->kpFactor-2*controller->kdFactor/controller->taFactor) * controller->lastError 
        + controller->kdFactor/controller->taFactor * controller->preLastError;
  
  controller->preLastError = controller->lastError;
  controller->lastError = controller->error;
  controller->lastValue = y;
  
  if(y < 0) return 0;
  if(y > 0xff) return 0xff;
  
  return (uint8_t)y;
}
uint16_t getADC(uint8_t channel){
    ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
    ADCSRA |= (1 << ADSC);
    while (ADCSRA & (1<<ADSC) ) {
    }
    return ADC;
}
int main(void)
{
  //UART einschalten
  uartSetup(UBRR_VAL);
  sendString("Init done!\r\n");
  
  //ADC auf interne Ref stellen
  ADMUX = (1 << REFS0)|(1 << REFS1);
  //ADC einschalten, PS auf 64
  ADCSRA = (1 << ADEN)|(1 << ADPS2)|(1 << ADPS1);
  //Dummyread
    ADCSRA |= (1 << ADSC);
    while (ADCSRA & (1<<ADSC) ) {
    }
    
  // PWM fuer OCR0B einstellen
  DDRD |= (1 << PD5);
  TCCR0A |= (1 << COM0B1)|(1 << WGM01)| (1 << WGM00);
  TCCR0B |= (1 << CS00);
    
    
  // PID-Kontroller einstellen
  pid_controller_t myController;
  myController.kpFactor = KP;
  myController.kiFactor = KI;
  myController.kdFactor = KD;
  myController.taFactor = TA;
  
    
    for(;;){
        /* insert your main loop code here */
        
        // Spannung messen an Eingang PC5
        
    adcValue = getADC(5);
    
    // Referenzspannung: 1.126V, 100k/10k Spannungsteiler
    voltage = (1.126/1024) * adcValue * 11.0;
    
    OCR0B = pid(SETVALUE, voltage, &myController);
  
    if(i < 1000){
            
      itoa(OCR0B,valueToPrint,10);
      sendString(valueToPrint);
      sendString_p(PSTR(","));
            
      dtostrf(voltage,
          6,
          4,
          valueToPrint);
          TCCR1B &= ~(1 << CS10);
    
      sendString(valueToPrint);
      sendString_p(PSTR("\r\n"));
      i++;
    }
    
  }
    return 0;   /* never reached */
}

Autor: PittyJ (Gast)
Datum:

Bewertung
3 lesenswert
nicht lesenswert
Kommt drauf an, was du damit steuern möchtest.

Ich habe PID-Regler für Heizung und Kühlung. Dort brauchen Messwerte nur 
alle paar Sekunden aufgenommen werden. Die Regelung arbeitet noch 
langsamer.

Da könnte die CPU auch im KHz Bereich laufen.

Autor: Jetzt ist G. (hacky)
Datum:

Bewertung
3 lesenswert
nicht lesenswert
Die Zykluszeit der Regelung (Regler+ Stellglied) sollte im Idealfall 
viel schneller wie die Zeitkonstante der Strecke. Wenn das nicht mehr 
der Fall ist muss man die analoge Naeherung verlassen und einen 
digitalen Regler implementieren.
Die analoge Naeherung bedeutet der PID verhaelt sich intuitiv. Man kann 
an den Parametern drehen zum optimieren.

: Bearbeitet durch User
Autor: Bernd K. (prof7bit)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Jetzt ist G. schrieb:
> muss man die analoge Naeherung verlassen und einen
> digitalen Regler implementieren.

Er hat bereits einen digitalen Regler, von einem analogen war nie die 
Rede, oder was meinst Du damit?

Autor: Peter D. (peda)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
M. K. schrieb:
> Wie schnell sollte eine Berechnung der Werte erfolgen?

Nur Du kennst Deinen Regelkreis und weißt, wie schnell er sein muß.
Du hast nen Haufen Divisionen durch Konstanten drin, die kann man bequem 
aus der Schleife herausziehen.

Autor: Udo S. (urschmitt)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
Jetzt ist G. schrieb:
> Die Zykluszeit der Regelung (Regler+ Stellglied) sollte im Idealfall
> viel schneller wie die Zeitkonstante der Strecke.

Das ist so nicht uneingeschränkt richtig. Je kleiner die Zykluszeit des 
Reglers, desto kleiner (als Zahlenwert) werden deine Parameter, und 
zumindest bei 8 Bit Systemen wo man float vereiden möchte kommst du da 
ganz schnell an die Grenze für deine Festkommaarithmetik und die 
Rundungsfehler erhöhen sich dann auch irgendwann bei float.

Jetzt ist G. schrieb:
> Die analoge Naeherung bedeutet der PID verhaelt sich intuitiv.

Was bitte ist intuitiv? Bei einem digitalen PID hast du doch genauso 
Parameter für den P, den I und den D Teil, das verhält sich nicht 
grundlegend anders.
Bei so einfachen Strecken wie ein PT1 oder PT2 kann man dann auch mal 
mit "einfachen" Optimierungsverfahren wie Ziegler Nichols, Chien & 
Reswick, etc.
spielen.

Ich bin schon ewig aus dem Thema raus, aber wenn ich mich richtig 
erinnere ist ein guter Wert für die Zykluszeit des Reglers 6-10 mal 
schneller als die Haupt-zeitkonstante der Strecke. (Ohne Garantie)

Autor: Udo S. (urschmitt)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Nachtrag:
Auf jeden Fall sollte man die Reger-Zykluszeit konstant machen 
(Timergesteuert) und nicht von der Berechnungszeit abhängig lassen, denn 
wenn aus welchen Gründen auch immer sich die Berechnungszeit und damit 
die Zykluszeit verkleinert, dann erhöhen sich die effektiven Parameter 
für den P, I und D Teil und vice versa.

Autor: S. R. (svenska)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Bernd K. schrieb:
> Er hat bereits einen digitalen Regler, von einem analogen war nie die
> Rede, oder was meinst Du damit?

Er meint, dass sich ein digital implementierter PID-Regler nur für kurze 
Zykluszeiten (verglichen mit der Regelstrecke) auch so verhält, wie es 
ein analoger PID-Regler in der Theorie täte.

Wenn der Regler zu langsam wird, gibt es seltsame Effekte, z.B. dass die 
Parameter nicht mehr das erwartete Verhalten zeigen.

Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter D. schrieb:
> M. K. schrieb:
>> Wie schnell sollte eine Berechnung der Werte erfolgen?
>
> Nur Du kennst Deinen Regelkreis und weißt, wie schnell er sein muß.
> Du hast nen Haufen Divisionen durch Konstanten drin, die kann man bequem
> aus der Schleife herausziehen.

Hö? Wo hab ich das denn drin oder meinst du das vor dem Hintergrund, 
dass ja KP, KD usw. alles Konstanten sind? Das ist zwar bei diesem 
Beispiel richtig aber ich will ja ggf. später mal andere Werte benutzten 
und ggf. auch mehr als nur einen Regler einsetzen mit unterschiedlichen 
Werten. Daher müssen KP und Co als Variablen vorgesehen werden.

@all
Die Frage war nicht, wie schnell ist die Regelung sondern ob die 
Berechnung der diskreten Stell-Werte denn für einen PID-Regler langsam, 
normal oder schnell ist. Ich denke das geht hier ein wenig unter grade. 
Mir ist natürlich schon klar, dass die Regelgeschwindigkeit am Ende auch 
zu meiner Regel-Strecke passen muss.
Aber das ist ja gar nicht die Frage. Die Frage ist lediglich: Ist die 
Berechnung der Stell-Werte besonders schnell, besonders langsam oder ist 
das für einen digitalen Regler eine normale Geschwindigkeit. Hier fehlt 
mir einfach das Gefühl dafür.
Ich hatte auch mal den PID aus der AVR221 benutzt, da werden die 
Stell-Werte ca. 1000 Zyklen schneller berechnet was aber auch meine 
Erwartungshaltung entsprach da diese PID mit Festkommaarithmetik rechnet 
und das schon per se auf dem AVR schneller läuft als die 
Gleitkommaarithmetik, die ich einsetze.

Autor: Mach (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Bernd K. schrieb:
> Jetzt ist G. schrieb:
> muss man die analoge Naeherung verlassen und einen
> digitalen Regler implementieren.
>
> Er hat bereits einen digitalen Regler, von einem analogen war nie die
> Rede, oder was meinst Du damit?
Er meint die Reglerauslegung kann bei einer langsamen Strecke wie ein 
Analog- (d.h zeitkontinuierlicher) Regler ausgelegt werden. Ist die 
Strecke schnell, dann dann funktioniert das nicht mehr. Beispiel: Eine 
Stromregelung, wo nach 4 Reglertakten der Sollwert erreicht ist. Wuerde 
man den Regler zeitkontinuierlich auslegen, dann wuerde er 
ueberschiessen, da beim Annaehern an den Sollwert der Ausgang nicht 
rechtzeitig nachgeregelt (reduziert) wird. Die zeitdiskrete Auslegung 
beruecksichtigt diese Zeiten, Stichwort Z-Transformation.

Autor: Mach (Gast)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
M. K. schrieb:
> Die Frage war nicht, wie schnell ist die Regelung sondern ob die
> Berechnung der diskreten Stell-Werte denn für einen PID-Regler langsam,
> normal oder schnell ist. Ich denke das geht hier ein wenig unter grade.

Wenn deine Strecke schnell ist, musst du schnell nachfuehren. Deine 
Abtastrate ist also immer von der Strecke abhaengig. Natuerlich darf die 
Abtastrate auch beliebig hoeher sein, ist also nur eine untere Grenze.

Autor: Bernd K. (prof7bit)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
M. K. schrieb:
> Die Frage ist lediglich: Ist die
> Berechnung der Stell-Werte besonders schnell, besonders langsam oder ist
> das für einen digitalen Regler eine normale Geschwindigkeit.

Nochmal: "Normal" gibts nicht, ob es schnell genug oder zu langsam ist 
hängt von der Regelstrecke ab.

Wenn Du eine Heizplatte regeln willst die 10 Minuten braucht bis sie 
aufgeheizt ist würde wahrscheinlich ein Zyklus alle paar Sekunden 
reichen, wenn Du die Fluglage eines 250g leichten Multikopters regeln 
willst der in 2 Sekunden von 0 auf 150km/h kommt, in 200ms eine volle 
Rolle macht und danach wieder wie angenagelt waagerecht in der Luft 
stehen können soll wirst Du mehrere kHz brauchen.

Autor: Udo S. (urschmitt)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Vieleicht solltest du die Kernfrage mal deutlicher machen:

M. K. schrieb:
> Ich hab mal mit Timer1 die Counts gezählt,
> die die Funktion pid() benötigt und bekomme hier so rund 2500-2700
> Counts. Da der AVR bei mir mit 8 MHz läuft bedeutet dies, dass die
> Berechnung so rund 325µs dauert.
>
> Die Frage, die sich mir nun noch stellt, und die ich bisher nicht
> ermitteln konnte: Ist das für einen PID-Regler langsam, normal oder
> schnell?

Nur was für eine Antwort erwartest du hier?
Um auf der sicheren Seite zu bleiben würde ich mit so einem Algorithmus 
maximal Regler auslegen die nicht schneller sein müssen als 1ms 
Zykluszeit (500µs wäre das absolute Maximum).

Siehe mein Beitrag oben zu konstanter Zykluszeit.

Autor: Regler (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Hallo,

um kurz auf die Frage zurückzukommen:

M. K. schrieb:
> Die Frage, die sich mir nun noch stellt, und die ich bisher nicht
> ermitteln konnte: Ist das für einen PID-Regler langsam, normal oder
> schnell? Wie schnell sollte eine Berechnung der Werte erfolgen? Wie
> schon gesagt, mir ist klar, dass es auch schneller geht, mir fehlt jetzt
> nur das Gespür dafür, wie schnell meine Implementierung ist

es kommt wie bereits gesagt auf die Anwendung an, ob man das als schnell 
oder langsam betrachtet. Als Beispiel, bei einer Anwendung wie der 
digitale Regelung eines Schaltnetzteils wäre das deutlich zu langsam, 
dort soll oft (ideal) in jedem Schaltzyklus ein neuer Wert berechnet 
werden bei 100kHz Schaltfrequenz heißt das <10us bleiben um die analogen 
Werte einzulesen, Regler zu berechnen und das Ausgangssignal (PWM 
Register) zu ändern. Dort wären bspw. Rechenzeiten von 1-3us angepeilt. 
Wobei dann eine Realisierung nur dann mit float sinnvoll ist, wenn der 
Prozessor FPU hat, ansonsten halt fixed-point-arithmetic.

Gruß Regler

Autor: Peter D. (peda)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
M. K. schrieb:
> Hö? Wo hab ich das denn drin oder meinst du das vor dem Hintergrund,
> dass ja KP, KD usw. alles Konstanten sind? Das ist zwar bei diesem
> Beispiel richtig aber ich will ja ggf. später mal andere Werte benutzten
> und ggf. auch mehr als nur einen Regler einsetzen mit unterschiedlichen
> Werten. Daher müssen KP und Co als Variablen vorgesehen werden.

Sie sind während der Regelschleife konstant.
Du mußt sie daher nur dann neu berechnen, wenn Du sie auch änderst.
Divisionen sind recht langsam. Daher lohnt es sich, sie aus der Schleife 
heraus zu ziehen.

Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter D. schrieb:
> Sie sind während der Regelschleife konstant.
> Du mußt sie daher nur dann neu berechnen, wenn Du sie auch änderst.
> Divisionen sind recht langsam. Daher lohnt es sich, sie aus der Schleife
> heraus zu ziehen.

Ja, genau das ist ja auch die Idee der Variablen. Es soll ja flexibel 
sein sodass ich die Parameter ändern kann. Eine Idee/Anwendung wäre ja 
vielleicht, dass ein AVR drei unterschiedliche Strecken regeln soll.
Wenn man natürlich nur einen Regler auf eine konkrete Anwendung braucht 
hast du sicher recht, dann kann man alle Konstanten vorher berechnen und 
rausziehen um das System so noch schneller zu machen.

Udo S. schrieb:
> Nur was für eine Antwort erwartest du hier?
> ...

Es gibt halt verschiedenste Reglerimplementierungen und ich frage mich, 
wie schnell ist meine Implementierung im Vergleich zu anderen. Ich 
stimme dir da auch völlig zu: Meinen Regler würde ich hier auch nur bei 
Systemen einsetzen, die 1ms oder langsamer sind. Die 500 us wären mir 
schon zu kritisch.

Autor: Au weia (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der Regler muß zur Strecke passen. Zu langsam ist genau so Mist wie zu 
schnell....

Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Au weia schrieb:
> Der Regler muß zur Strecke passen. Zu langsam ist genau so Mist wie zu
> schnell....

Die Frage ist ja nicht, wie schnell der Regler an sich ist (das wird ja 
durch die Parameter eingestellt) sondern wie schnell die Berechnung ist 
;)

Autor: Peter D. (peda)
Datum:

Bewertung
2 lesenswert
nicht lesenswert
M. K. schrieb:
> Eine Idee/Anwendung wäre ja
> vielleicht, dass ein AVR drei unterschiedliche Strecken regeln soll.

Auch das ändert nichts daran, daß sie während der Regelung konstant 
bleiben. Du brauchst ja eh 3 Structs dafür.

Man sollte immer alle unnötigen Berechnungswiederholungen aus Schleifen 
heraus ziehen. Das erleichtert auch das Verständnis, was in der Schleife 
wirklich passiert.

Der Compiler ist auch nicht dumm. Wenn er merkt, daß sich die Werte zur 
Laufzeit nicht ändern, dann zieht er selber die Berechnungen raus und 
führt sie schon zur Compilezeit aus.
Aber sobald Du die Werte zur Laufzeit änderst, bleiben die unnötigen 
Wiederholungen in der Schleife drin und die Ausführungszeit kann sich 
deutlich verlängern.

Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dann weiß ich nicht, welche Konstanten du genau meinst bzw. ich wüsste 
jetzt nicht so spontan welche Konstanten ich da raus ziehen kann/soll. 
Kannst du das mal an einem Beispiel festmachen? Ich stelle mir das grade 
so vor:
pid_controller_t controller1, controller2, controller3;
//configure KP, KD usw.
controller1->kpFactor=0.8;
controller2->kpFactor=30.2;
controller3->taFactor=0.01; 
...
Stellwert1 = pid(SollWert1, IstWert1, &controller1);
...
Stellwert2 = pid(SollWert2, IstWert2, &controller2);
...
Stellwert3 = pid(SollWert3, IstWert3, &controller3);
...

Ich versteh da grad nicht wie ich aus der Funktion pid() die 
"Konstanten" rausziehen soll. Die sind doch vom jeweiligen Controller 
abhängig, also Variable.

Autor: Peter D. (peda)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
M. K. schrieb:
> controller->kdFactor/controller->taFactor

usw.

Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter D. schrieb:
> M. K. schrieb:
>> controller->kdFactor/controller->taFactor
>
> usw.

Achso meinst du das...ja, das macht Sinn. Hatte da grade nen Knoten im 
Kopf.

Autor: Bernd K. (prof7bit)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Hier ist meiner:
typedef struct {
  u16 kp;
  u16 ki;
  u16 kd;
  i32 out_max;
  i32 out_min;
  i32 integral;
  i16 error_prev;
} pid_controller_t;

#define PRECISION  16

void pid_controller_init(pid_controller_t* pid, u16 Kp, u16 Ki, u16 Kd, i16 out_min, i16 out_max) {
  pid->kp = Kp;
  pid->ki = Ki;
  pid->kd = Kd;
  pid->out_min = out_min << PRECISION;
  pid->out_max = out_max << PRECISION;
  pid->integral = 0;
  pid->error_prev = 0;
}

i16 pid_controller_compute(pid_controller_t* pid, i16 error) {
  pid->integral += error * pid->ki;
  i32 diff = pid->kd * (error - pid->error_prev);
  i32 out = pid->kp * error + pid->integral + diff;
  pid->error_prev = error;

  if (out < pid->out_min) out = pid->out_min;
  if (out > pid->out_max) out = pid->out_max;

  if (pid->integral < pid->out_min) pid->integral = pid->out_min;
  if (pid->integral > pid->out_max) pid->integral = pid->out_max;

  return out >> PRECISION;
}


: Bearbeitet durch User
Autor: Jetzt ist G. (hacky)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
Lehn  den an die Tonne, der ist einer Heizung nicht gerecht.
Beim normalen PID uebernimmt der I Anteil den Waermeverlust, welcher mit 
der Temperaturdifferenz nur Umgebung zunimmt. Dies loest man 
sinnvollerweise mit einem Vorwaertspfad, der die statische 
Waermeleistung uebernummt.
Also
Stellglied = (Tsoll-Tambient)x a1 + P x error + integrator

mit a1 einer proportionalitaetskonstante. Allenfalls kann dort auch eine 
Tabelle stehen. Muss nich allzu genau sein. Es ist viel schneller als 
den Integrator die Arbeit machen zu lassen.

Autor: Bernd K. (prof7bit)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Jetzt ist G. schrieb:
> Lehn  den an die Tonne, der ist einer Heizung nicht gerecht.
> Beim normalen PID uebernimmt der I Anteil den Waermeverlust, welcher mit
> der Temperaturdifferenz nur Umgebung zunimmt.

Es ging aber erstmal nur um einen "normalen" PID wie er im Buch steht.

Das Rausrechnen von irgendwelchen Dreckeffekten oder Nichtlinearitäten 
hinter oder vor dem Regler von denen man das Glück hat sie mathematisch 
halbwegs modellieren zu können damit der eigentliche Regler es leichter 
hat ist dann erst das nächste Kapitel.

Autor: Johannes S. (jojos)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
M. K. schrieb:
> wie schnell ist meine Implementierung im Vergleich zu anderen.

habe deine pid() Funktion gerade mal in meinem STM32F407 reingeworfen, 
0,54 µs braucht der für die Berechnung mit seiner FPU.

Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johannes S. schrieb:
> habe deine pid() Funktion gerade mal in meinem STM32F407 reingeworfen,
> 0,54 µs braucht der für die Berechnung mit seiner FPU.

Interessant. Bei welcher Taktrate des STM denn? Hast du vielleicht auch 
noch Vergleichswerte mit anderen PID-Implementierungen bei gleicher 
Taktrate mit dem STM? Dank dir schonmal für den Test ;)

Autor: Johannes S. (jojos)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
es ist das hier schon öfter erwähnte Board (die "Black" Variante, <10€):
http://wiki.stm32duino.com/index.php?title=STM32F407
CPU Takt ist 168 MHz und FPU aktiv.
https://www.st.com/en/microcontrollers/stm32f407ve.html
Das ist natürlich eine andere Kategorie als ein kleiner 8 Bitter, aber 
einfach mal so als Hausnummer wieviel schneller die tatsächlich sind. Da 
braucht man jedenfalls keine Angst mehr vor floats zu haben :)
Hier sieht man das alles nur in die FPU geschoben und verrechnet wird:
          pid(float, float, PID_CONTROLLER*):
08008820:   vmov    s15, r0
08008824:   vmov    s14, r1
100               + (controller->kpFactor+controller->kiFactor+controller->kdFactor/controller->taFactor) * controller->error
08008828:   vldr    s9, [r2, #12]
101               + (-controller->kpFactor-2*controller->kdFactor/controller->taFactor) * controller->lastError
0800882c:   vldr    s11, [r2, #24]
 97         controller->error = sollWert - istWert;
08008830:   vsub.f32        s13, s15, s14
100               + (controller->kpFactor+controller->kiFactor+controller->kdFactor/controller->taFactor) * controller->error
08008834:   vldr    s14, [r2, #8]
08008838:   vldr    s15, [r2]
 97         controller->error = sollWert - istWert;
0800883c:   vstr    s13, [r2, #20]
100               + (controller->kpFactor+controller->kiFactor+controller->kdFactor/controller->taFactor) * controller->error
08008840:   vdiv.f32        s10, s14, s9
 96       uint8_t pid(float sollWert, float istWert, pid_controller_t* controller){
08008844:   sub     sp, #8
101               + (-controller->kpFactor-2*controller->kdFactor/controller->taFactor) * controller->lastError
08008846:   vadd.f32        s14, s14, s14
0800884a:   vneg.f32        s12, s15
0800884e:   vdiv.f32        s8, s14, s9
100               + (controller->kpFactor+controller->kiFactor+controller->kdFactor/controller->taFactor) * controller->error
08008852:   vldr    s14, [r2, #4]
105         controller->lastError = controller->error;
08008856:   vstr    s13, [r2, #24]
100               + (controller->kpFactor+controller->kiFactor+controller->kdFactor/controller->taFactor) * controller->error
0800885a:   vadd.f32        s15, s15, s14
101               + (-controller->kpFactor-2*controller->kdFactor/controller->taFactor) * controller->lastError
0800885e:   vsub.f32        s12, s12, s8
08008862:   vadd.f32        s14, s15, s10
08008866:   vldr    s15, [r2, #16]
0800886a:   vfma.f32        s15, s13, s14
102               + controller->kdFactor/controller->taFactor * controller->preLastError;
0800886e:   vldr    s14, [r2, #28]
104         controller->preLastError = controller->lastError;
08008872:   vstr    s11, [r2, #28]
101               + (-controller->kpFactor-2*controller->kdFactor/controller->taFactor) * controller->lastError
08008876:   vfma.f32        s15, s12, s11
102               + controller->kdFactor/controller->taFactor * controller->preLastError;
0800887a:   vfma.f32        s15, s10, s14
108         if(y < 0) return 0;
0800887e:   vcmpe.f32       s15, #0.0

wichtig ist noch das man auch alles in float und nicht in double macht. 
Beim AVR wird da nicht unterschieden, aber beim ARM ist eine Konstante 
'0.8' ein double und dann werden die Berechnungen auch im ARM länger und 
der Code wird grösser. Da muss man gut aufpassen wenn man Code von 
Arduino Libs portiert.

: Bearbeitet durch User
Autor: M. K. (sylaina)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Johannes S. schrieb:
> Beim AVR wird da nicht unterschieden, aber beim ARM ist eine Konstante
> '0.8' ein double und dann werden die Berechnungen auch im ARM länger und
> der Code wird grösser. Da muss man gut aufpassen wenn man Code von
> Arduino Libs portiert.

Oh ja, das kenn ich in ähnlicher Richtung. Bin schon gefragt worden, 
warum ich bei der ein und anderen Stelle "nur" floats benutze und kein 
double, wäre doch...µC war ein ATTiny85. Musste ich auch erstmal 
aufklären, dass bei dem AVR kein Unterschied zwischen float und double 
besteht und ichs nicht mag wenn man was vorgegaukelt bekommt, was nicht 
drin steckt.

Autor: Johannes S. (jojos)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
weil ich gerade dabei war habe ich die gleiche Routine nochmal auf zwei 
anderen Boards laufen lassen.
STM32L031 @ 32 MHz (Cortex-M0+)
gcc 6.3.1 -Os: 38,6 µs
gcc 6.3.1 -O3: 39,5 µs

gcc 7.2.1 -Os: 39,3 µs
gcc 7.2.1 -O3: 39,8 µs

Keil: 24,3 µs

LPC1549 @ 72 MHz (Cortex-M3)
gcc 7.2.1 -0s: 19,3 µs
gcc 7.2.1 -O3: 9,5 µs

Auffällig ist die immer noch die stiefmütterliche Behandlung des 
Cortex-M0. Optimierung Os und O3 bringt kaum einen Unterschied, O3 ist 
hier sogar noch langsamer. Beim Cortex-M3 wird die Ausführung um Faktor 
2 schneller bei O3, das sieht gut aus. Gegenüber dem M0 ist der M3 
offensichtlich nur wegen dem doppelten Takt auch doppelt so schnell, das 
der M3 ein Hardware Div kann scheint nix zu machen. Oder hatte der M0+ 
das auch? Weiss ich gerade nicht.
Die Keil Version ist mit dem mbed Online erstllt, der ist beim M0 immer 
noch deutlich besser als der gcc.

Ist jetzt nicht ganz das PID Thema aber man sieht das die Compiler 
Optimierung hier auch noch eine Menge ausmachen kann.

Die Testroutine ist die PID Funktion aus dem ersten Post die 1000x 
aufgerufen wird und die Zeitdifferenz in µs Auflösung (mbed os Timer) 
gemessen wird.
    // Test pid
    // PID-Kontroller einstellen
    pid_controller_t myController;
    myController.kpFactor = KP;
    myController.kiFactor = KI;
    myController.kdFactor = KD;
    myController.taFactor = TA;

    volatile uint8_t val;

    Timer t;
    t.start();
    int tStart = t.read_us();
    for (int i=0; i<1000; i++) {
        val  = pid((float)i, 500.0f, &myController);
    }
    int tDiff = t.read_us() - tStart;
    printf("pid calc time: %i\n", tDiff);

: Bearbeitet durch User
Autor: M. K. (sylaina)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Ich hab jetzt heute noch mal ein wenig gespielt. Wie peda schon 
empfohlen hat hab ich die konstanten Faktoren raus genommen und nebenbei 
festgestellt, dass ich beim zweiten Summanden bei KI den Faktor TA 
vergessen hatte, lehrbuchmäßig hätte das KI*TA sein müssen aber das TA 
hatte ich vergessen.
However, durch das Rausziehen der Konstanten ist die Berechnung nochmal 
rund 1000 Zyklen schneller geworden und braucht nun "nur" noch rund 1700 
Zyklen.
Damit ich auf dem AVR auch noch einen konkreten Vergleich bekomme habe 
ich den PID-Regler aus er Appnote AVR221 implementiert. Dieser braucht 
etwa 1000 Zyklen, ist also erwartungsgemäß deutlich schneller.
Zu guter Letzt hatte ich dann mal meinen PID-Regler auf 
Festkommaarithmetik umgebaut und war darüber überrascht: Mit 
Festkommaarithmetik braucht meine PID-Lösung grad mal rund 320 Zyklen. 
Das ist eine enorme Steigerung die ich in dieser Größenordnung nicht 
erwartet hatte.
Allerdings ist die gewählte Teststrecke etwas ungünstig, bei der 
Festkommaarithmetik ists nicht so einfach mal grob aus der Hüfte zu 
schießen und hier passende Parameter zu finden, sodass es keinen Über- 
bzw. allgemeine Schwinger gibt. Da werd ich mir dann erst bei einem 
konkreten Problem mal Gedanken drüber machen.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.