Forum: Mikrocontroller und Digitale Elektronik Frage zur Verwendung von "volatile"


von Hannes (Gast)


Lesenswert?

Hallo,

ich habe ein Programm in C geschrieben für einen ATmega128. Alles 
funktioniert wunderbar. Aber ich habe für mein Verständnis trotzdem noch 
eine Frage zur Verwendung von "volatile" bei der Deklaration der 
Variablen:

Mein Programm besteht aus der Mainloop und 2 Interrupts. Alle globalen 
Variablen, die ich nur in der Mainloop benutze, habe ich ganz normal 
deklariert. Bei allen Variablen, die ich in den Interrupts nutze, habe 
ich diese zusätzlich mit "volatile" deklariert.

Jetzt meine Fragen:

1) Wenn ich eine Variable global deklariere, sie aber nur innerhalb 
einer einzigen Interrupt-Funktion verwende, muss ich diese dann auch mit 
"volatile" deklarieren ?

2) Wenn ich eine Variable global deklariere und diese in 
unterschiedlichen Interrupts verwende (nicht jedoch in der Mainloop!), 
muss ich diese dann auch mit "volatile" deklarieren? Wir gehen davon 
aus, dass eine ISR nicht durch eine andere ISR unterbrochen werden kann.

Vielen Dank für Erklärungen!!

Hannes

von olaf (Gast)


Lesenswert?

> 1) Wenn ich eine Variable global deklariere, sie aber nur innerhalb
> einer einzigen Interrupt-Funktion verwende, muss ich diese dann auch mit
> "volatile" deklarieren ?

Nein, allerdings waere es sinnvoller sie dann auch nur in der 
Interruptfunktion zu deklarieren. Im Zweifel als static.

> 2) Wenn ich eine Variable global deklariere und diese in

Nein, allerdings solltest du nochmal ueber deinen Programierstil 
nachdenken. :)

Olaf

von Peter II (Gast)


Lesenswert?

Hannes schrieb:
> 1) Wenn ich eine Variable global deklariere, sie aber nur innerhalb
> einer einzigen Interrupt-Funktion verwende, muss ich diese dann auch mit
> "volatile" deklarieren ?

nein

> 2) Wenn ich eine Variable global deklariere und diese in
> unterschiedlichen Interrupts verwende (nicht jedoch in der Mainloop!),
> muss ich diese dann auch mit "volatile" deklarieren? Wir gehen davon
> aus, dass eine ISR nicht durch eine andere ISR unterbrochen werden kann.

nein

> Vielen Dank für Erklärungen!!
nur Variablen die in er Main verwendet werden und in einer ISR geändert 
werden müssen volatile sein. Sonst fängt der Compiler an die main so zu 
optimieren, weil er nicht "sieht" das die Variabel sich ändert.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Hannes schrieb:
> 1) Wenn ich eine Variable global deklariere, sie aber nur innerhalb
> einer einzigen Interrupt-Funktion verwende, muss ich diese dann auch mit
> "volatile" deklarieren ?
Nein.
Volatile sagt dem Compiler, dass sich die Variable im Programmverlauf 
"von aussen" ändern kann. Volatile müssen nur die Variablen sein, die 
sich "unvohergesehen" ändern können.

> 2) Wenn ich eine Variable global deklariere und diese in
> unterschiedlichen Interrupts verwende (nicht jedoch in der Mainloop!),
> muss ich diese dann auch mit "volatile" deklarieren? Wir gehen davon
> aus, dass eine ISR nicht durch eine andere ISR unterbrochen werden kann.
Nein, weil sie sich nicht "von aussen unvorhergesehen" ändern kann. 
Somit darf der Compiler diese Variable auch in der ISR lokal optimieren.

: Bearbeitet durch Moderator
von Teo D. (teoderix)


Lesenswert?

1) Nein
2) Ja (der Compiler weiß ja nicht, wann ein Zugriff in einer ISR war)

von Georg G. (df2au)


Lesenswert?

Sicherheitshalber noch der Hinweis, dass "volatile" kein Allheilmittel 
ist. Bei Variablen mit mehr als 1 Byte Grösse, die in main() und im 
Interrupt verwendet werden, empfiehlt es sich, den Zugriff mit "atomic 
block" zu schützen.

von Peter II (Gast)


Lesenswert?

Teo D. schrieb:
> 2) Ja (der Compiler weiß ja nicht, wann ein Zugriff in einer ISR war)
nein, muss er auch nicht, weil beim Start einer isr immer die Daten aus 
Ram laden muss. (und am ende wieder wegschreiben).

von Teo D. (teoderix)


Lesenswert?

Peter II schrieb:
> Teo D. schrieb:
>> 2) Ja (der Compiler weiß ja nicht, wann ein Zugriff in einer ISR war)
> nein, muss er auch nicht, weil beim Start einer isr immer die Daten aus
> Ram laden muss. (und am ende wieder wegschreiben)

Jo, wenn man genauer drüber nachdenkt....

von Stefan E. (sternst)


Lesenswert?

Peter II schrieb:
> nur Variablen die in er Main verwendet werden und in einer ISR geändert
> werden müssen volatile sein.

Immer diese falsche Einschränkung, die man immer wieder ließt.
Es spielt keine Rolle, wo geändert und wo nur gelesen wird.
Es kommt zwar deutlich seltener vor, dass eine Variable in Main geändert 
und in der ISR nur gelesen wird, aber auch dann muss sie volatile sein.

von Hannes (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank für die vielen klaren Antworten. So in etwa habe ich mir das 
gedacht, war mir aber wie gesagt nicht sicher.

@ Olaf: Zu meinem Programmierstil: mein Programm ist recht 
übersichtlich, etwa 3 Seiten Code. Ich habe absichtlich alle Variablen 
global deklariert, weil ich dann nach dem Compilieren genau sehe, wie 
mein RAM-Verbrauch ist. Das ist bequem so finde ich. Wie könnte ich das 
besser/einfacher machen?

nochmal @ Olaf: Was macht der Compiler genau, wenn ich eine Variable im 
Interrupt als 'static' deklariere? Wo ist der Unterschied zu einer ganz 
normalen Deklaration innerhalb der ISR?

vielen Dank !
Hannes

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Hannes schrieb:
> Was macht der Compiler genau, wenn ich eine Variable im
> Interrupt als 'static' deklariere? Wo ist der Unterschied zu einer ganz
> normalen Deklaration innerhalb der ISR?

Eine normale Variablendeklaration innerhalb einer Funktion (also auch 
innerhalb einer ISR) wird automatisch genannt. Die Variable landet auf 
dem Stack, d.h. sie verliert beim Beenden der Funktion ihren Wert und 
ist beim Aufruf der Funktion auch nicht initialisiert (d.h. es kann 
beliebiger Unfug drinstehen).

Wird eine Variable innerhalb einer Funktion als static deklariert, 
entspricht das einer globalen Variablen, d.h. sie landet nicht auf dem 
Stack, sondern ist dem Linker bekannt (und Du erhältst Deine 
Informationen über den RAM-Verbrauch). Die Variable behält auch über 
mehrere Funktionsaufrufe hinweg ihren Wert, verhält sich also genau so 
wie eine globale Variable.
Die Variable ist aber außerhalb der Funktion nicht "sichtbar", d.h. auf 
sie kann nicht von anderen Orten über ihren Namen zugegriffen werden.

Zu guter letzt: Eine Variable, die außerhalb einer Funktion als static 
deklariert wird, ist nur innerhalb der zugehörigen C-Übersetzungseinheit 
sichtbar, d.h. aus anderen C-Übersetzungseinheiten kann nicht auf sie 
über ihren Namen zugegriffen werden. Davon abgesehen verhalten sich auch 
diese Variablen wie globale Variablen.

von olaf (Gast)


Lesenswert?

> Das ist bequem so finde ich.

Du sollst aber kein bequemes leben haben. Du sollst gut lesbaren und 
wiederverwendbaren Source schreiben den du auch noch in einem Jahr 
verstehst. Und da ist es eine Hilfe wenn Variablen nur dort bekannt sind 
wo sie auch gebraucht werden.

> Interrupt als 'static' deklariere?

Der Variableninhalt bleibt dann auch nach der Funktion erhalten und ist 
beim naechsten IRQ wieder verfuegbar. Technisch bedeutet es das die 
Variable dann nicht auf dem Stack liegt.

Olaf

von Hannes (Gast)


Lesenswert?

Super, vielen Dank für die sehr guten Erklärungen. Ich habe meinen Code 
entsprechend umgebaut und es funktioniert nach wie vor alles wunderbar. 
Das werde ich zukünftig so beibehalten !

von Carl D. (jcw2)


Lesenswert?

Hallo Hannes,
Georg hat mit seinem Kommentar recht:
Georg G. schrieb:
> Sicherheitshalber noch der Hinweis, dass "volatile" kein Allheilmittel
> ist. Bei Variablen mit mehr als 1 Byte Grösse, die in main() und im
> Interrupt verwendet werden, empfiehlt es sich, den Zugriff mit "atomic
> block" zu schützen.
auch wenn jemand meinte, ihn negativ zu bewerten zu müssen.

Aus der AVRlibc Doku:
1
#include <inttypes.h>
2
#include <avr/interrupt.h>
3
#include <avr/io.h>
4
#include <util/atomic.h>
5
volatile uint16_t ctr;
6
ISR(TIMER1_OVF_vect)
7
{
8
  ctr--;
9
}
10
...
11
int
12
main(void)
13
{
14
   ...
15
   ctr = 0x200;
16
   start_timer();
17
   sei();
18
   uint16_t ctr_copy;
19
   do
20
   {
21
     ATOMIC_BLOCK(ATOMIC_FORCEON)
22
     {
23
       ctr_copy = ctr;
24
     }
25
   }
26
   while (ctr_copy != 0);
27
   ...
28
}
ATOMIC_BLOCK(..) sperrt Int's für die Dauer des Blocks.

ATOMIC_FORCEON
ist für den Fall daß Int's hinterher freigegeben sein sollen, 1 Register 
und 2..3 Befehle kürzer als
ATOMIC_RESTORESTATE,
was der SREG-Zustand sicher und am Ende wieder den alten SREG-Zustand 
herstellt.

: Bearbeitet durch User
von Hannes (Gast)


Lesenswert?

Hallo Carl,

vielen Dank für das prima Beispiel. Das habe ich so noch nicht 
verwendet, aber ich habe auch 16 und 32 Bit Variablen in meiner 
Mainloop, das betrifft mich also auch.

Ich werde das jetzt gleich ergänzen...

Vielen Dank für die tolle Unterstützung! Bei jedem Projekt mit neuen 
Fragen lernt man hier im Forum viel dazu!

Hannes

von W.S. (Gast)


Lesenswert?

Carl D. schrieb:
> ATOMIC_BLOCK(..) sperrt Int's für die Dauer des Blocks.

Ähemm.. nun, die AVR-Leute machen das eben so, allerdings halte ich es 
für einen doch eher unüberlegten Programmierstil, mit dem Aus- und 
Einschalten der Interrupts um sich zu werfen. Auf größeren Systemen 
macht man sowas nicht und dort, wo es irgend ein OS gibt, erst recht 
nicht.

Also sollte man sich besser überlegen, wie man bei nichtatomaren 
Zugriffen die beteiligten Instanzen miteinander synchronisiert.

Oftmals ist solcher Zugriff bei genauerer Betrachtung auch garnicht 
notwendig. Es ist ja nicht wie im Kindergarten, wo ein Kind sagt "ich 
male A auf die Tafel" und zugleich Kind 2 ein B drauf malen will und 
ebenso zu gleicher Zeit Kind 3 ein C malen will. Stattdessen hat man es 
eigentlich immer mit Kommunikation zu tun, also wo eine Instanz eier 
anderen etwas sagen will. Und das kann man IMMER ohne ..BLOCK schreiben, 
indem man einen Handshake einrichtet: Beide Instanzen halten einen 
atomaren Kenner vor (z.B. ein Byte). Die ansagende Instanz ändert irgend 
etwas, z.B. den Inhalt eines struct's und dann ändert sie ihren Kenner 
(z.B. kenner++ ). Die zuhörende Instanz vergleicht ihren Kenner mit dem 
anderen und greift nur auf den struct zu, wenn beide ungleich sind. 
Anschließend macht sie ihren Kenner dem Kenner des Ansagenden gleich und 
fertig ist die Laube - ganz OHNE konkurrierende Zugriffe.

W.S.

von Carl D. (jcw2)


Lesenswert?

W.S. schrieb:
> Carl D. schrieb:
>> ATOMIC_BLOCK(..) sperrt Int's für die Dauer des Blocks.
>
> Ähemm.. nun, die AVR-Leute machen das eben so, allerdings halte ich es
> für einen doch eher unüberlegten Programmierstil, mit dem Aus- und
> Einschalten der Interrupts um sich zu werfen. Auf größeren Systemen
> macht man sowas nicht und dort, wo es irgend ein OS gibt, erst recht
> nicht.

Ja, auf Mainframes hab ich das auch nie gemacht, aber es geht hier um 
einen AVR, der zwischen jedem Lesen eines Bytes einer Mehrbyte-Variable 
aus dem RAM durch einen Interrupt unterbrochen werden kann. Und dann hat 
man ein "geht meistens"-Programm. Also besser verstehen, was passieren 
kann und gleich richtig machen.
BTW, ein AVR-Leut bin ich dann, wenn ich es mit dem AVR zu tun habe. Ich 
kann aber auch ARM-Leut, x86/x64-Leut, /370-Leut, ... Und in jedem Fall 
muß man die Besonderheiten der HW beachten.
Aber was red ich, das weißt du sicher selbst.

Und Lock-Free macht eigentlich nur Sinn, wenn man mehrere Cores hat, die 
auch wirklich parallel zugreifen. AVR!

: Bearbeitet durch User
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.