Hallo,
Ich habe ein externes ADC welches über einen Timer ausgelesen und der
Wert in einem Buffer gespeichert wird.
Von Zeit zu Zeit lass ich den Buffer auslesen, das Ergebnis aus dem
Buffer aufsummieren und bilde den Mittelwert.
1
structadcBuffer
2
{
3
int16data[8][8];
4
uint8index;
5
};
6
7
...
8
cli();
9
for(uint8i=0;i<8;i++)
10
{
11
result+=_buffer.data[ch][i];
12
}
13
sei();
14
..
Eigentlich müsste es auch funktionieren wenn "data[]" im Buffer nicht
volatile deklariert ist, da meiner Ansicht nach (bin kein Assembler
spezialist) der ist, dass er den Z Pointer jedesmal neu beschreibt,
anstatt ihn zu inkrementieren.
Ohne volatile:
1
ld r24, Z+
2
ld r25, Z+
3
add r18, r24
4
adc r19, r25
5
cp r30, r20
6
cpc r31, r21
7
brne .-14
mitm volatile:
1
movw r30, r22
2
add r30, r20
3
adc r31, r21
4
add r30, r30
5
adc r31, r31
6
subi r30, 0xEF ; 239
7
sbci r31, 0xFD ; 253
8
ldd r24, Z+9 ; 0x09
9
ldd r25, Z+10 ; 0x0a
10
add r18, r24
11
adc r19, r25
12
subi r20, 0xFF ; 255
13
sbci r21, 0xFF ; 255
14
cpi r20, 0x08 ; 8
15
cpc r21, r1
16
brne .-32 ; 0x12d4
Wenn ich das richtig verstanden habe, welchen grund hat der Compiler
hier, den Z Pointer jedesmal neu zu beschreiben?
Oder bin ich mit meinen Überlegungen komplett falsch?
grüße
Philipp
Philipp schrieb:> Eigentlich müsste es auch funktionieren wenn "data[]" im Buffer nicht> volatile deklariert ist, da meiner Ansicht nach (bin kein Assembler> spezialist) der ist, dass er den Z Pointer jedesmal neu beschreibt,> anstatt ihn zu inkrementieren.
Was der Compiler macht oder nicht kann sich mit Optionen und Versionen
ändern. Anhand des erzeugten Assembler-Code darauf zu schließen, was man
machen kann wird früher oder später in Tränen enden.
Hier braucht man kein volatile, solange auf adcBuffer wie oben
zugegriffen wird, also nur im Interrupt und außerhalb nur in cli()/sei()
geschützten Bereichen.
Im Interrupt braucht man es nicht, da es eine Funktion ist, die nur
aufgerufen, durchgelaufen und beendet wird ohne dass es unterbrochen
wird oder parallel etwas anderes läuft. Also genau so, wie es der
Compiler annimmt.
Außerhalb braucht man das volatile nicht, weil durch das cli() auch
nichts unterbricht und nichts parallel läuft. Ein Problem wäre, dass der
Compiler Daten aus dem Array vor dem cli() oder nach dem sei() laden
oder speichern könnte. Das passiert hier nicht, weil die cli() und sei()
Makros zweckmäßigerweise als Speicherbarrieren definiert sind, d.h. der
Compiler darf beim Optimieren niemals Speicherzugriffe über diese beiden
Makro-Aufrufe hinweg verschieben.
Also wird hier kein volatile gebraucht. Auch wenn man es an einzelnen
Stellen brauchen würde, muss man nicht gleich die ganze Variable
volatile machen, man kann auch bei den entsprechenden Zugriffen die
Variable nach volatile casten.
Andreas B. schrieb:> Hier braucht man kein volatile, solange auf adcBuffer wie oben> zugegriffen wird, also nur im Interrupt und außerhalb nur in cli()/sei()> geschützten Bereichen.
Der Interruptschutz ist für volatile unerheblich. Interessant ist,
ob der Compiler an der gegebenen Stelle noch implizite Annahmen
machen kann, welchen Wert _buffer tatsächlich hat bzw. ob er
schließen kann, dass das Beschreiben eigentlich für die Katz' ist
und daher auch weggelassen werden darf (weil sich an der Funktion
des sich ergebenden Codes aus seiner Sicht dann nichts ändert).
Mit dem Inkrementieren irgendwelcher Zeiger oder anderer
Implementierungsdetails hat das alles ohnehin nichts (direkt) zu
tun. volatile ist dafür da, dem Compiler anzuzeigen, dass das
damit gekennzeichnete Objekt auf eine Weise geändert werden könnte,
die für ihn aus dem aktuellen Programmfluss nicht ersichtlich ist
(bspw. direkt durch die Hardware, wie bei IO-Registern, oder durch
andere Threads, wie eben in einer ISR).
Bezeichner, die mit Unterstrichen anfangen, sollte man (so man die
Regeln des C-Standards nicht gerade ganz genau kennt) tunlichst
vermeiden, da viele von ihnen "reserved for the implementation" sind.
Andreas B. schrieb:> Hier braucht man kein volatile, solange auf adcBuffer wie oben> zugegriffen wird, also nur im Interrupt und außerhalb nur in cli()/sei()> geschützten Bereichen.> Ein Problem wäre, dass der> Compiler Daten aus dem Array vor dem cli() oder nach dem sei() laden> oder speichern könnte. Das passiert hier nicht, weil die cli() und sei()> Makros zweckmäßigerweise als Speicherbarrieren definiert sind,
cli() und sei() sind nicht als memory barriers definiert:
Michael Buesch schrieb:> Andreas B. schrieb:>> Makros zweckmäßigerweise als Speicherbarrieren definiert sind,>> cli() und sei() sind nicht als memory barriers definiert:>>
>> Der Compiler kann daher einen Variablenzugriff so verschieben, dass er> nicht mehr innerhalb des IRQ-disable bereiches liegt.
Sagt wer? Ich halte mal dagegen mit avr-libc 1.7.1:
>>>> Der Compiler kann daher einen Variablenzugriff so verschieben, dass er>> nicht mehr innerhalb des IRQ-disable bereiches liegt.>> Sagt wer?
ich?
> Ich halte mal dagegen mit avr-libc 1.7.1:>>
Michael Buesch schrieb:> Tja, wenn man portablen Code schreiben will, darf man sich eben nicht> darauf verlassen. Aber schön zu sehen, dass dies in neueren avr-libc> gefixt ist.
Portabler Code mit direktem Aufruf von cli/sei. Aber klar doch.
A. K. schrieb:> Michael Buesch schrieb:>>> Tja, wenn man portablen Code schreiben will, darf man sich eben nicht>> darauf verlassen. Aber schön zu sehen, dass dies in neueren avr-libc>> gefixt ist.>> Portabler Code mit direktem Aufruf von cli/sei. Aber klar doch.
Gehe nochmal zurück auf Los und lies worum es hier geht.