Forum: Compiler & IDEs Verständnisfrage Application Note AVR221 PID Controller


von Meier (Gast)


Lesenswert?

Hallo,

angehängt ist der Beispielcode aus der AVR221 Application Note.

Kann mir jemand sagen warum die Variable pidTimer aus struct 
GLOBAL_FLAGS hier nicht volatile sein muss bzw. ist??

1
00001 /*This file has been prepared for Doxygen automatic documentation generation.*/
2
00022 #include <inavr.h>
3
00023 #include <ioavr.h>
4
00024 #include "stdint.h"
5
00025 #include "pid.h"
6
00026 
7
00032 
8
00033 #define K_P     1.00
9
00035 #define K_I     0.00
10
00037 #define K_D     0.00
11
00038 
12
00041 struct GLOBAL_FLAGS {
13
00043   uint8_t pidTimer:1;
14
00044   uint8_t dummy:7;
15
00045 } gFlags = {0, 0};
16
00046 
17
00048 struct PID_DATA pidData;
18
00049 
19
00056 
20
00057 #define TIME_INTERVAL   157
21
00058 
22
00061 #pragma vector = TIMER0_OVF_vect
23
00062 __interrupt void TIMER0_OVF_ISR( void )
24
00063 {
25
00064   static uint16_t i = 0;
26
00065   if(i < TIME_INTERVAL)
27
00066     i++;
28
00067   else{
29
00068     gFlags.pidTimer = TRUE;
30
00069     i = 0;
31
00070   }
32
00071 }
33
00072 
34
00075 void Init(void)
35
00076 {
36
00077   pid_Init(K_P * SCALING_FACTOR, K_I * SCALING_FACTOR , K_D * SCALING_FACTOR , &pidData);
37
00078 
38
00079   // Set up timer, enable timer/counte 0 overflow interrupt
39
00080   TCCR0A = (1<<CS00);
40
00081   TIMSK0 = (1<<TOIE0);
41
00082   TCNT0 = 0;
42
00083 }
43
00084 
44
00090 int16_t Get_Reference(void)
45
00091 {
46
00092   return 8;
47
00093 }
48
00094 
49
00099 int16_t Get_Measurement(void)
50
00100 {
51
00101   return 4;
52
00102 }
53
00103 
54
00109 void Set_Input(int16_t inputValue)
55
00110 {
56
00111   ;
57
00112 }
58
00113 
59
00114 
60
00117 void main(void)
61
00118 {
62
00119   int16_t referenceValue, measurementValue, inputValue;
63
00120   Init();
64
00121   __enable_interrupt();
65
00122 
66
00123   while(1){
67
00124 
68
00125     // Run PID calculations once every PID timer timeout
69
00126     if(gFlags.pidTimer)
70
00127     {
71
00128       referenceValue = Get_Reference();
72
00129       measurementValue = Get_Measurement();
73
00130 
74
00131       inputValue = pid_Controller(referenceValue, measurementValue, &pidData);
75
00132 
76
00133       Set_Input(inputValue);
77
00134 
78
00135       gFlags.pidTimer = FALSE;
79
00136     }
80
00137   }
81
00138 }
82
00139

Desweitern verstehe ich im nachfolgend angehängten Code nicht warum bei 
pid.maxError und bei pid.maxSumError dem Divisor jeweils noch 1 addiert 
wird.

Hätte da vielleicht jemand eine Idee?
1
00001 /*This file has been prepared for Doxygen automatic documentation generation.*/
2
00023 #include "pid.h"
3
00024 #include "stdint.h"
4
00025 
5
00035 void pid_Init(int16_t p_factor, int16_t i_factor, int16_t d_factor, struct PID_DATA *pid)
6
00036 // Set up PID controller parameters
7
00037 {
8
00038   // Start values for PID controller
9
00039   pid->sumError = 0;
10
00040   pid->lastProcessValue = 0;
11
00041   // Tuning constants for PID loop
12
00042   pid->P_Factor = p_factor;
13
00043   pid->I_Factor = i_factor;
14
00044   pid->D_Factor = d_factor;
15
00045   // Limits to avoid overflow
16
00046   pid->maxError = MAX_INT / (pid->P_Factor + 1);
17
00047   pid->maxSumError = MAX_I_TERM / (pid->I_Factor + 1);
18
00048 }
19
00049 
20
00050 
21
00059 int16_t pid_Controller(int16_t setPoint, int16_t processValue, struct PID_DATA *pid_st)
22
00060 {
23
00061   int16_t error, p_term, d_term;
24
00062   int32_t i_term, ret, temp;
25
00063 
26
00064   error = setPoint - processValue;
27
00065 
28
00066   // Calculate Pterm and limit error overflow
29
00067   if (error > pid_st->maxError){
30
00068     p_term = MAX_INT;
31
00069   }
32
00070   else if (error < -pid_st->maxError){
33
00071     p_term = -MAX_INT;
34
00072   }
35
00073   else{
36
00074     p_term = pid_st->P_Factor * error;
37
00075   }
38
00076 
39
00077   // Calculate Iterm and limit integral runaway
40
00078   temp = pid_st->sumError + error;
41
00079   if(temp > pid_st->maxSumError){
42
00080     i_term = MAX_I_TERM;
43
00081     pid_st->sumError = pid_st->maxSumError;
44
00082   }
45
00083   else if(temp < -pid_st->maxSumError){
46
00084     i_term = -MAX_I_TERM;
47
00085     pid_st->sumError = -pid_st->maxSumError;
48
00086   }
49
00087   else{
50
00088     pid_st->sumError = temp;
51
00089     i_term = pid_st->I_Factor * pid_st->sumError;
52
00090   }
53
00091 
54
00092   // Calculate Dterm
55
00093   d_term = pid_st->D_Factor * (pid_st->lastProcessValue - processValue);
56
00094 
57
00095   pid_st->lastProcessValue = processValue;
58
00096 
59
00097   ret = (p_term + i_term + d_term) / SCALING_FACTOR;
60
00098   if(ret > MAX_INT){
61
00099     ret = MAX_INT;
62
00100   }
63
00101   else if(ret < -MAX_INT){
64
00102     ret = -MAX_INT;
65
00103   }
66
00104 
67
00105   return((int16_t)ret);
68
00106 }
69
00107 
70
00112 void pid_Reset_Integrator(pidData_t *pid_st)
71
00113 {
72
00114   pid_st->sumError = 0;
73
00115 }

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Meier schrieb:

> Kann mir jemand sagen warum die Variable pidTimer aus struct
> GLOBAL_FLAGS hier nicht volatile sein muss bzw. ist??

Weil es der IAR zufälligerweise auch so richtig compiliert und
daher keiner gemerkt hat, dass sie volatile sein sollte?

> Desweitern verstehe ich im nachfolgend angehängten Code nicht warum bei
> pid.maxError und bei pid.maxSumError dem Divisor jeweils noch 1 addiert
> wird.

Die entsprechenden Paramter sind erklärt als "Tuning constants for PID
loop".  Damit ist es wohl eine reine Definitionsfrage, wie man diese
genau festlegt.

von Martin T. (mthomas) (Moderator) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Meier schrieb:
>
>> Kann mir jemand sagen warum die Variable pidTimer aus struct
>> GLOBAL_FLAGS hier nicht volatile sein muss bzw. ist??
>
> Weil es der IAR zufälligerweise auch so richtig compiliert und
> daher keiner gemerkt hat, dass sie volatile sein sollte?

Nur ergänzend: ist mir bei der Portierung von Code für IAR EWAVR und 
EWARM und bei dem Compiler von ARM (Realview-Tools) für ARM nach GNU 
schon mehrfach aufgefallen, dass volatiles gefehlt haben. Möglicherweise 
behandeln diese Compiler implizit alle globalen Variablen oder zumindest 
solche, die auch in ISRs verwendet werden, als wären sie mit volatile 
deklariert oder es wird einfach nicht mit eingeschalteter Optimierung 
getestet.

von Stefan++ (Gast)


Lesenswert?

Hallo,

> Kann mir jemand sagen warum die Variable pidTimer aus struct
> GLOBAL_FLAGS hier nicht volatile sein muss bzw. ist??

Es gilt per Definition:
Das Schlüsselwort volatile teilt dem Compiler mit, daß die mit name 
bezeichnete Variable mit dem Datentyp typ durch Ereignisse außerhalb der 
Kontrolle des Programms verändert werden kann.

Wichtig hierbei ist "außerhalb der Kontrolle des Programms". Wird die 
Variable innerhalb des Programms (eines Source-Files) verändert ist 
"volatile" nicht nötig denn dann weiss es der Compiler schon!

von Klaus (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Weil es der IAR zufälligerweise auch so richtig compiliert und
> daher keiner gemerkt hat, dass sie volatile sein sollte?

Könnte es daran liegen:

in der main wird zweimal auf gFlags.pidTimer zugegriffen. Dazwischen ist 
ein Funktionsaufruf. Kann der Compiler/Optimiser sicher sein, daß 
Registerinhalte über diesen Funktionsaufruf, der ja nun weitere Aufrufe 
oder auch Librarycalls enthalten kann, erhalten bleiben? Der Code dieser 
Funktionen kann ja unbekannt sein, da sie ja erst der Linker (oder bei 
großen Systemen erst der Loader) einbindet.

MfG Klaus

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Klaus schrieb:

> in der main wird zweimal auf gFlags.pidTimer zugegriffen. Dazwischen ist
> ein Funktionsaufruf.

Jain.  Der Funktionsaufruf ist da nur dazwischen, wenn die Variable
true ist.  Wenn sie false ist, ist keiner drin, und der Compiler
könnte das erneute Abfragen des Variablenwerts aus dem Speicher
wegoptimieren.

> Kann der Compiler/Optimiser sicher sein, daß
> Registerinhalte über diesen Funktionsaufruf, der ja nun weitere Aufrufe
> oder auch Librarycalls enthalten kann, erhalten bleiben?

Hängt davon ab, ob er diese Funktion selbst mit auf Seiteneffekte
analysieren kann.  Ich glaube, auch IAR hat derartige Möglichkeiten.

von Christof Ermer (Gast)


Lesenswert?

..ganz klar... bei Benutzung des GNU.C Compilers ( WinAVR )ist "jede", 
nur in der Interruptfunktion gesetzte Variable unbedingt volatile zu 
deklarieren !!
Die in der AVR221 Appnote gezeigten definerte struct-Variable 
"gFlags.pidTimer"  funktioniert in der main()while(1){} schleife NICHT!
Diese Variable muß volatile deklariert sein!!

Besser so machen: ( wen man es so machen will ...)

typedef struct GLOBAL_FLAGS {
  //! True when PID control loop should run one time
  uint8_t pidTimer :1;
  uint8_t :7;  // dummy Alignment {0}; nicht {0, 0} ;
}GLOBAL_PID_FLAG_TYPE;  // Nur Type definieren)

// dann erst globale struct anlegen, aber volatile

volatile GLOBAL_PID_FLAG_TYPE gFlags = {0}; //volatile

dann kann in main().....

if( gFlags.pidTimer == TRUE )// gefragt werden
// oder natürlich nur
if( gFlags.pidTimer )

von Karl H. (kbuchegg)


Lesenswert?

Auch nicht zu vergessen.

Das volatile ist auch nur in bestimmten Situationen notwendig (im Sinne 
von: wirkt sich tatsächlich aus). So wie hier, wenn die Hauptschleife 
ausser der PID Sache nichts anderes mehr macht. Allerdings baut man ja 
eher selten eine µC-Schaltung, die nur einen PID Regler in sich enthält 
und sonst nichts.
D.h. in einem realen Programm, bei dem die Hauptschleife ein wenig mehr 
macht, als nur PID-Regeln, ist es gar nicht so unwahrscheinlich, dass 
das Versäumnis gar nicht auffällt, weil der COmpiler schlicht und 
ergreifend gar keine Möglichkeit hat, dieses Versäumnis auszunutzen, 
weil er die Register innerhalb der Hauptschleife auch noch für andere 
Dinge braucht. Erst wenn man dann aus einem realen Beispiel nur diesen 
Teil isoliert und nur mehr oberflächlich testet, dann würde das Problem 
sichtbar, wird aber durch den oberflächlichen Test vor der 
Veröffentlichung nicht bemerkt - denn schliesslich hat der Regler, sogar 
4 PID-Regler gleichzeitig, im viel komplexeren Quadrokopter ja auch 
wunderbar funktioniert.

von Karl H. (kbuchegg)


Lesenswert?

Meier schrieb:


> Desweitern verstehe ich im nachfolgend angehängten Code nicht warum bei
> pid.maxError und bei pid.maxSumError dem Divisor jeweils noch 1 addiert
> wird.
>
> Hätte da vielleicht jemand eine Idee?

Klopf mal den Code darauf hin ab, was passiert, wenn die entsprechenden 
Faktoren auf 0 gesetzt werden. Ist nur so ein Bauchgefühl.

von Oliver (Gast)


Lesenswert?

Stefan++ schrieb:
> Wichtig hierbei ist "außerhalb der Kontrolle des Programms". Wird die
> Variable innerhalb des Programms (eines Source-Files) verändert ist
> "volatile" nicht nötig denn dann weiss es der Compiler schon!

Das hat mit innerhalb oder ausserhalb eines source-Files gar nichts zu 
tun.
C als Sprache kennt das Konzept "Interrupt" nicht, und gcc als 
C-Sprach-konformer Compiler daher auch nicht. Für den gcc wird eine ISR 
nie aufgerufen, weil kein Programmpfad die jemals aufruft. Daher erkennt 
der gcc auch nicht, daß die ISR Variablen verändert, egal, ob die ISR 
innerhalb oder ausserhalb des Files mit dem Hauptprogramm steht.

Oliver

von bal (Gast)


Lesenswert?

Aber müsste nicht eigentlich jede Variable die nicht explizit als 
"static" gekennzeichnet ist, und damit exportiert wird, als jederzeit 
veränderbar angenommen werden?

Denn jede solche Variable kann ja per "extern" überall bekannt gemacht 
werden und damit, außerhalb der momentanen "Sicht" des Compilers, 
beschrieben werden.

von Oliver (Gast)


Lesenswert?

Der Compiler anaylisert den Progammablauf nach den Regeln des C-Stadards 
und erkennt, an welchen Stellen sich eine Variable ändert oder ändern 
kann. Ein Funktionsaufruf einer Funktion, die nicht im aktuellen 
sourcefile steht, ist so ein Fall. Danach sind alle globalen Variablen 
potentiell verändert, und würden für danachfolgende Zugriffe neu 
gelesen.

Der Unterschied zur ISR ist der, daß die ISR eine Variable auch an 
Stellen ändern kann, die das nach dem C-Standard nicht erwarten lassen.

Klassiker ist z.B
>int flag = 0;
> while (!flag);

Oliver

von Karl H. (kbuchegg)


Lesenswert?

bal schrieb:
> Aber müsste nicht eigentlich jede Variable die nicht explizit als
> "static" gekennzeichnet ist, und damit exportiert wird, als jederzeit
> veränderbar angenommen werden?

Nein. Warum sollte sie

Welche Möglichkeit gibt es für i in

extern int i;

void foo()
{
  int k = 2 * i;
      // <----
  int j = 3 * i;
}

an der markierten Stelle seinen Wert zu ändern? Richtig. Es gibt keine 
Möglichkeit. Da wird keine Funktion aufgerufen und auch sonst nichts 
getan, wodurch sich i verändern könnte. Nachdem der Compiler Code 
eingebaut hat um für die Berechnung von k den momentanen Wert von i 
festzustellen, kann er denselben Wert auch für die Berechnung von j 
benutzen, denn es muss sich an dieser Stelle ja um denselben Wert 
handeln.
Die einzige Ausnahme sind Interrupts bzw. memory mapped Variablen oder 
Multithreading. Aber das sind Konzepte, die es in C schlicht und 
ergreifend nicht gibt. Daher muss man als Programmierer nachhelfen und 
dem Compiler mitteilen: du kannst dich bei i auf nichts verlassen - die 
Variable verändert sich, ohne dass du das detektieren könntest:

extern volatile int i;

> Denn jede solche Variable kann ja per "extern" überall bekannt gemacht
> werden und damit, außerhalb der momentanen "Sicht" des Compilers,
> beschrieben werden.

Aber damit sie das tun könnte, muss an der betreffenden Stelle im Code 
etwas geschehen. Zb eine Funktion aufgerufen werden, die die Variable 
verändern könnte. Gibt es keinen Funktionsaufruf, und wird auch sonst 
nicht an der Variablen rumgefummelt - wodurch sollte sie sich verändern?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.