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
> 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
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.
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.
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.
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).
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....
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.
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
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.
> 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
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 !
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
volatileuint16_tctr;
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_tctr_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.
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
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.
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!