Forum: Mikrocontroller und Digitale Elektronik UART ISR und volatile


von Stefan (Gast)


Lesenswert?

Hallo,

ich hab mal wieder eine Frage zum UART. Diesmal geht es um volatile. Mir 
ist hier nicht ganz klar, ob ich alle Variablen, die von der ISR 
geändert werden können volatile deklariert werden müssen.
Ich habe 3 globale Variablen die von der ISR geändert werden:
1
uint8_t USARTInBuf[0x10];
2
fifo_t USART_RX_Buf;
3
volatile CtrlInterface_t CtrlInterface;

Das ist ein CtrlInterface welches das USART_RX_Buf FIFO beinhaltet.
Das USART_RX_Buf FIFO beinhaltet wiederum ein uint8_t Array.
Wie weiter unten im Code zu sehen kann die ISR Routine alle drei Objekte 
verändern.
Die Member "Status" und USART_RX_Buf FIFO (und dadurch dann natürlich 
dessen uint8_t buffer).
1
ISR(USART_RX_vect)
2
{
3
  uint8_t uartRX = UDR0;
4
  switch(CtrlInterface.state)
5
  {
6
    case RXSTATE_IDLE:
7
      // waiting for start of transmission
8
      if(uartRX == ASCII_STX)
9
        CtrlInterface.state = RXSTATE_RECEIVE;
10
      break;
11
    case RXSTATE_COMPLETE:
12
      // trash data
13
      break;
14
    case RXSTATE_RECEIVE:
15
      // receiving data
16
      fifo_push(CtrlInterface.pUSART_RX_Buf, uartRX);
17
      if(uartRX == ASCII_ETX)
18
        CtrlInterface.state = RXSTATE_COMPLETE;
19
  }  
20
}

Zuerst bin ich davon ausgegangen, das alles volatile deklariert werden 
muss, da sich ja alles ändern kann.
Als ich dann alles als volatile deklariert hatte, hat jedoch der 
Compiler angemerkt, dass meine Funktionen die ich für das FIFO 
geschrieben habe eigentlich ihre Parameter (also das FIFO das ich zum 
bearbeiten übergebe) volatile deklariert haben sollten, da sie nun ja 
eine volatile Variable übergeben bekommen.
Das geht mir aber eigentlich einen Schritt zu weit, weil ich die FIFO 
Funktionen ja allgemein nutze, nicht nur für das FIFO, dass eventuell 
volatile sein muss.

Also die Frage die sich mir jetzt stellt: genügt es das CtrlInterface 
Objekt als volatile zu deklarieren, weil das FIFO und dessen Array sich 
ja als Member im CtrlInterface befinden, oder muss alles volatile sein.
Wenn alles volatile sein muss, wie geh ich dann damit um, dass der 
Compiler über Funktionsparameter meckert die in den FIFO Funktionen 
nicht als volatile deklariert sind.

von Karl H. (kbuchegg)


Lesenswert?

Stefan schrieb:

> Also die Frage die sich mir jetzt stellt:

Die Frage, die sich dir eigentlich stellen sollte, lautet:
Was bewirkt eigentlich volatile?

Daraus ergibt sich dann nämlich, welche Variablen volatile sein müssen 
und welche nicht.

von uart (Gast)


Lesenswert?

Stefan schrieb:
> Als ich dann alles als volatile deklariert hatte, hat jedoch der
> Compiler angemerkt, dass meine Funktionen die ich für das FIFO
> geschrieben habe eigentlich ihre Parameter (also das FIFO das ich zum
> bearbeiten übergebe) volatile deklariert haben sollten, da sie nun ja
> eine volatile Variable übergeben bekommen.

Das hört sich für mich so an, als hättest du eine Funktion
1
void foo(char* daten);

und möchtest dieser einen volatile char fifo[xx] übergeben und bekommst 
dann eine warning vom compiler.
Die warning ist völlig korrekt aber unbedenklich.
Mach einfach einen cast beim aufruf
1
foo( (char*)fifo );

von Stefan (Gast)


Lesenswert?

Hallo,

wenn ich es richtig verstanden habe bewirkt volatile, dass die Variable 
vom Optimieren geschützt ist.
Am Beispiel des Members CtrlInterface->state. Dieses überprüfe ich in 
einer Loop in der nichts passiert solange "state" nicht auf "complete" 
steht. Da sonst nichts passiert würde der Compiler ohne volatile für 
CtrlInterface wohl sagen "state wird nie auf complete gesetzt -> also 
mach ich einfach nie was".
Also ich denke im Prinzip ist mir schon klar was volatile macht.

von Karl H. (kbuchegg)


Lesenswert?

Stefan schrieb:
> Hallo,
>
> wenn ich es richtig verstanden habe bewirkt volatile, dass die Variable
> vom Optimieren geschützt ist.

Ja. Darauf läuft es hinaus.

> Am Beispiel des Members CtrlInterface->state. Dieses überprüfe ich in
> einer Loop in der nichts passiert solange "state" nicht auf "complete"
> steht. Da sonst nichts passiert würde der Compiler ohne volatile für
> CtrlInterface wohl sagen "state wird nie auf complete gesetzt -> also
> mach ich einfach nie was".
> Also ich denke im Prinzip ist mir schon klar was volatile macht.

Gut.

> Mir ist hier nicht ganz klar, ob ich alle Variablen, die von der
> ISR geändert werden können volatile deklariert werden müssen.

Angewendet auf diese Aussage:
Du musst jene Variablen volatile machen, die sowohl in einer ISR als 
auch ausserhalb der ISR benutzt werden. Eine ISR stellt für den Compiler 
in dieser Hinsicht etwas 'nicht-existentes' dar. Wenn der Compiler 
optimiert, geht er davon aus, dass eine Variable nicht magisch ihren 
Wert ändert.

   i = 5;

   ... mach irgendwas wo i nicht vorkommt

   // an dieser Stelle muss i immer noch 5 sein

Der Compiler benutzt dieses Wissen um Speicherzugriffe einzusparen, wenn 
der Wert von i noch irgendwo in einem Register rumlungert.
Jetzt kommen aber ISR ins Spiel, von denen der Compiler nichts weiß, 
wenn es um die Optimierung dieser Codestelle geht. Wird i in einer ISR 
verändert, dann KANN i an der mit // markierten Stelle sehr wohl einen 
anderen Wert als 5 haben.

volatile teilt dem Compiler mit, dass eine Variable ihren Wert ändern 
kann und zwar ohne dass der Compiler dies selbst rausfinden könnte. Die 
Konsequenz daraus ist, dass der Compiler diese Variable aus seinen 
Optimierschemata ausnehmen muss, weil er bei seinen Datenflussanalysen 
zu falschen Ergebnissen kommen würde.

Also:
Alle Variablen, die sowohl in einer ISR als auch ausserhalb benutzt 
werden, müssen volatile sein.

von Karl H. (kbuchegg)


Lesenswert?

uart schrieb:

> Die warning ist völlig korrekt aber unbedenklich.

Sie kann unbedenklich sein, muss es aber nicht

> Mach einfach einen cast beim aufruf
>
1
> foo( (char*)fifo );
2
>

was ist wenn sich der Inhalt auf den fifo zeigt, tatsächlich während der 
Ausführung von foo ändert?

von uart (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> was ist wenn sich der Inhalt auf den fifo zeigt, tatsächlich während der
> Ausführung von foo ändert?

dann hätte man ein mächtiges Problem :)
deswegen schalte ich bei der Abarbeitung immer den entsprechenden IRQ 
aus

von Klaus F. (kfalser)


Lesenswert?

uart schrieb:
> Karl heinz Buchegger schrieb:
>> was ist wenn sich der Inhalt auf den fifo zeigt, tatsächlich während der
>> Ausführung von foo ändert?
>
> dann hätte man ein mächtiges Problem :)
> deswegen schalte ich bei der Abarbeitung immer den entsprechenden IRQ
> aus

Eben. Und deshalb braucht fifo nicht volatile sein, weil sichergestellt 
ist, dass es sich nicht unbemerkt ändert.
Es muß also nicht das ganze CtrlInterface volatile sein, sondern nur 
innerhalb der struct das state Element.

von Stefan (Gast)


Lesenswert?

Hallo,

vielen Dank für Eure Anmerkungen.
Ich hätte gedacht, dass eventuell durch die Deklaration des 
CtrlInterface auch seine Member geschützt werden, also implizit als 
volatile angesehen.
Nachdem ich jetzt noch einmal mit Optimierung durchsimuliert habe 
scheint dies jedoch nicht der Fall zu sein.

Bleibt nur noch die Frage mit den FIFO Funktionen. Im aktuellen Fall ist 
es so, dass sich der FIFO Inhalt nicht ändern wird. Er wird nur während 
der ISR geändert bis der Status "complete" erreicht wird, danach wird 
dich der Inhalt nicht mehr durch eine ISR ändern, sondern nur noch durch 
den zyklischen Programmablauf.
Aber sauberer erscheint mir die Deklaration der Parameter mit volatile, 
da sich das im Laufe der Entwicklung ja noch ändern könnte.

Wäre dann eine Überladung der Funktion sinnvoll?
Also quasi:
1
bool_t fifo_push(fifo_t* const fifo, uint8_t data)
2
{
3
  if((fifo->count < fifo->size) && (fifo->pBuffer != 0))  // buffer full or uninitialized?
4
  {
5
    fifo->count++;                    // increase number of stored bytes
6
    *(fifo->pBuffer + fifo->posWrite) = data;      // save byte on next position in buffer
7
    
8
    if(fifo->posWrite == fifo->size-1)          // did we reach the end of the buffer?                       
9
      fifo->posWrite = 0;                // if so restart at beginning
10
    else
11
      fifo->posWrite++;                // otherwise increase write pointer position
12
    return TRUE;
13
  }
14
  else
15
  {
16
    return FALSE;
17
  }
18
}
19
20
bool_t fifo_push(volatile fifo_t* const fifo, uint8_t data)
21
{
22
  if((fifo->count < fifo->size) && (fifo->pBuffer != 0))  // buffer full or uninitialized?
23
  {
24
    fifo->count++;                    // increase number of stored bytes
25
    *(fifo->pBuffer + fifo->posWrite) = data;      // save byte on next position in buffer
26
    
27
    if(fifo->posWrite == fifo->size-1)          // did we reach the end of the buffer?                       
28
      fifo->posWrite = 0;                // if so restart at beginning
29
    else
30
      fifo->posWrite++;                // otherwise increase write pointer position
31
    return TRUE;
32
  }
33
  else
34
  {
35
    return FALSE;
36
  }
37
}

Das erscheint mir nur ein wenig seltsam, da ja der komplette 
Funktionsrumpf identisch ist und sich nur die Parameter unterscheiden.

Viele Grüße
Stefan

von Karl H. (kbuchegg)


Lesenswert?

Die Sache ist die:
Wenn du ein volatile hinzufügst, ändert sich erst mal nichts an der 
Logik. Man kann es auch so sagen: pessimistischer zu sein ist immer 
gefahrlos (solange es sich um nichts zeitkritisches handelt)

Das schlimmste was einem passieren kann, ist ein Zuwachs in der 
Laufzeit.

Du hast aber auch schon mitgekriegt, dass man mit solchen 
Pauschalaussagen ( wie volatile nimmt man da und da) immer etwas 
vorsichtig sein soll.
Auch der Rat volatile wegzucasten, kann gut gehen, muss es aber nicht. 
Was man in deiner Situation aber machen kann: Die Situation so 
herstellen, wie sie der Compiler erwartet. Sprich, keine Interrupts 
zulassen.
Was man auch machen kann, zunächst mit einer Kopie arbeiten und so die 
Bearbeitung zunächst von der globalen Variablen wegziehen und damit die 
Gefahr loswerden, dass einem die globale Variable unter dem A... 
umgedreht wird.

Das trickreiche an der Sache ist: Jeder Fall ist etwas anders gelagert 
und man muss sich jeden Fall einzeln ansehen. Drum funktionieren auch 
die Pauschalempfehlungen nicht immer.

von Klaus F. (kfalser)


Lesenswert?

1
bool_t fifo_push(volatile fifo_t* const fifo, uint8_t data);
ist unsinnig.

Außerdem :
Angenommen, Du hast CtrlInterface_t folgendermaßen deklariert:
1
typedef struct {
2
   int state;
3
   fifo_t *pFifo;
4
} CtrlInterface_t ;

und du machst CtrlInterface volatile :
1
volatile CtrlInterface_t CtrlInterface;

so bedeutet das für den Compiler nicht, dass das fifo volatile ist, 
sondern dass der Pointer pFifo in der ISR eventuell von einem fifo auf 
ein anders umgesetzt werden kann. Der Pointer ist volatile, nicht das 
FIFO selbst.

von Stefan (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Auch der Rat volatile wegzucasten, kann gut gehen, muss es aber nicht.
> Was man in deiner Situation aber machen kann: Die Situation so
> herstellen, wie sie der Compiler erwartet. Sprich, keine Interrupts
> zulassen.

So ist die Situation momentan. Das FIFO wird durch die ISR gefüllt, 
sobald die Übertragung beendet ist wird der FIFO Inhalt ausgelesen und 
verarbeitet. Währenddessen wird der Interrupt abgeschaltet bis alle 
Daten des FIFO verarbeitet wurden.
Dennoch ist es so, dass bei der Simulation Dinge die mit dem FIFO zu tun 
haben wegoptimiert werden.
Beschrieben wird das FIFO ja nur in der ISR, also wird der Compiler wohl 
denken dass es nie gefüllt wurde.

Klaus Falser schrieb:
> bool_t fifo_push(volatile fifo_t* const fifo, uint8_t data);
> ist unsinnig.

Das versteh ich nicht so recht. Wenn das FIFO volatile ist meckert der 
Compiler über die Deklaration und ist zufrieden, wenn ich es so 
hinschreibe.

Klaus Falser schrieb:
> Der Pointer ist volatile, nicht das
> FIFO selbst.

Das hab ich jetzt verstanden.

von Klaus F. (kfalser)


Lesenswert?

1
typedef struct {
2
    char f[10];
3
} fifo_t;
4
5
typedef struct {
6
    int state;
7
    fifo_t *pFifo;
8
} ci_t ;
9
10
volatile ci_t ci;
11
12
void fifo_push(fifo_t *p, char a)
13
{
14
    p->f[0] = a;
15
}
16
int main(void)
17
{
18
    fifo_push(ci.pFifo, 'x');
19
    return 0;
20
}

Da meckert nix.

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.