Hallo,
ich arbeite gerade an einem Projekt mit einem ATXMEGA128A1 un Atmel
Studio und habe folgende Frage:
Ich möchte in einer ISR eine kleine Funktion aufrufen und zwar abhängig
von einer Bedingung entweder Funktion1 oder Funktion2. Beide Funktionen
haben die gleiche Signatur.
Ich habe mit dazu einen Funtionspointer definiert
1
uint16_t(*volatilereadSensor)(void)
dem weise ich abhängig von der Bedingung am Anfang der main die Funktion
zu:
1
if(read1==1)
2
readSensor=readSensor1;
3
else
4
readSensor=readSensor2;
Ist das so korrekt? AtmelStudio markiert mir die Pointerdefinition rot,
weil dort volatile steht und sagt Symbol undefiniert. Ist das ein "Bug"
im AS oder habe ich da noch einen Fehler gemacht? Compilieren kann ich
es.
Grüße, Alex
Jörg W. schrieb:> Auch ganz ohne volatile wird das funktionieren.
Warum? Was unterscheidet einen Funktionszeiger von einer Variablen?
Werden Funktionszeiger prinzipiell nicht optimiert und lokal gepuffert?
Falk B. schrieb:> Jörg W. schrieb:>> Auch ganz ohne volatile wird das funktionieren.>> Warum? Was unterscheidet einen Funktionszeiger von einer Variablen?> Werden Funktionszeiger prinzipiell nicht optimiert und lokal gepuffert?
Die Frage stelle ich mir auch gerade.
Falk B. schrieb:>> Auch ganz ohne volatile wird das funktionieren.>> Warum?
Weil der Compiler in einer ISR gar keine Chance hat, da irgendwie
irgendwas unpassendes zu cachen. Der kann nur auf den globalen
Funktionszeiger zugreifen, um die entsprechende Funktion aufzurufen.
Anders wäre die Situation, wenn sich noch während der Laufzeit der ISR
dieser Zeiger ("von außen") ändern könnte. Aber das wird ja wohl eher
nicht der Fall sein.
Jörg W. schrieb:> Auch ganz ohne volatile wird das funktionieren.
Eher nicht.
Der Compiler sieht im Mainkontext keinerlei Verwendung und schenkt sich
die Zuweisung in die Variable.
Den Interrupt interessiert das volatile nicht. Er kennt keine vorherige
Instanz, d.h. muß die Variable in jedem Fall neu einlesen.
Peter D. schrieb:> Der Compiler sieht im Mainkontext keinerlei Verwendung und schenkt sich> die Zuweisung in die Variable.
Darf er nicht. Die ist global, da muss er immer mit Seiteneffekten
rechnen.
Jörg W. schrieb:>> Der Compiler sieht im Mainkontext keinerlei Verwendung und schenkt sich>> die Zuweisung in die Variable.>> Darf er nicht. Die ist global, da muss er immer mit Seiteneffekten> rechnen.
Wenn das so ist, warum müssen dann Variablen volatile deklariert werden?
Falk B. schrieb:> Jörg W. schrieb:>>> Der Compiler sieht im Mainkontext keinerlei Verwendung und schenkt sich>>> die Zuweisung in die Variable.>>>> Darf er nicht. Die ist global, da muss er immer mit Seiteneffekten>> rechnen.>> Wenn das so ist, warum müssen dann Variablen volatile deklariert werden?
Hab ich mich auch gerade gefragt.
1
uint8_tmyVar=0;
2
3
intmain(){
4
while(1){
5
if(myVar==24)doSomething();
6
}
7
}
8
9
ISR(){
10
myVar=24;
11
}
klappt nicht ohne volatile weil der compiler myVar nicht mehr aus dem
speicher liest. es wird im prinzip der ganze schleifeninhalt
wegrationalisiert.
Cyblord -. schrieb:> klappt nicht ohne volatile weil der compiler myVar nicht mehr aus dem> speicher liest. es wird im prinzip der ganze schleifeninhalt> wegrationalisiert.
Das ist allerdings auch der gegenteilige Fall.
In der ISR gibt es keine Schleife, also muss der Wert aus dem globalen
Kontext stammen.
Falk B. schrieb:> Aber auch hier muss man aufpassen! Die Zuweisung muss ATOMAR erfolgen!
Quatsch. Am Anfang der main() erfolgt die Zuweisung, also bevor die
Interrupts aufgesetzt werden. Da muß überhaupt nichts atomar sein.
Walter T. schrieb:> Cyblord -. schrieb:>> klappt nicht ohne volatile weil der compiler myVar nicht mehr aus dem>> speicher liest. es wird im prinzip der ganze schleifeninhalt>> wegrationalisiert.>> Das ist allerdings auch der gegenteilige Fall.
Bitte was?
Es geht nicht um den Fall, sondern um die Aussage globale Variablen
bräuchten kein volatile.
Cyblord -. schrieb:> Es geht nicht um den Fall, sondern um die Aussage globale Variablen> bräuchten kein volatile.
Die Aussage gab es nie. Es gab die Aussage, dass in diesem Fall eine
globale Variable kein volatile benötigte.
Ja richtig. So rum gehts weil er beim Eintritt in die ISR myVar aus dem
Speicher lesen muss.
Nur die Aussage von Jörg war an diese Stelle etwas zu allgemein.
Bezogen auf diesen Fall aber korrekt.
Walter T. schrieb:> In der ISR gibt es keine Schleife, also muss der Wert aus dem globalen> Kontext stammen.
s/Schleife/wiederholte Verwendung/
Nur in dem Zusammenhang ist das so relevant. Aber ja, in der Regel
erfolgt diese wiederholte Verwendung natürlich innerhalb einer Schleife,
insbesondere, wenn man dabei auf irgendeine Veränderung pollt, die durch
einen Interrupt verursacht wird.
Einfaches Beispiel:
1
#include<avr/interrupt.h>
2
#include<avr/io.h>
3
#define F_CPU 1E6
4
#include<util/delay.h>
5
6
void(*fptr)(void);
7
8
ISR(USART_RX_vect)
9
{
10
if(fptr)
11
fptr();
12
}
13
14
void
15
set_led(void)
16
{
17
PORTB&=~_BV(1);
18
}
19
20
void
21
clear_led(void)
22
{
23
PORTB|=_BV(1);
24
}
25
26
int
27
main(void)
28
{
29
DDRB|=_BV(1);
30
31
for(;;)
32
{
33
fptr=set_led;
34
_delay_ms(100);
35
fptr=clear_led;
36
_delay_ms(100);
37
}
38
}
Auch mit maximaler Optimierung wirft der Compiler die Zuweisung an
"fptr" nicht weg, und die ISR holt sich selbstverfreilich den Wert von
da. Was sollte sie auch sonst tun?
1
.file "example.c"
2
__SP_H__ = 0x3e
3
__SP_L__ = 0x3d
4
__SREG__ = 0x3f
5
__tmp_reg__ = 0
6
__zero_reg__ = 1
7
.text
8
.global set_led
9
.type set_led, @function
10
set_led:
11
/* prologue: function */
12
/* frame size = 0 */
13
/* stack size = 0 */
14
.L__stack_usage = 0
15
cbi 0x5,1
16
/* epilogue start */
17
ret
18
.size set_led, .-set_led
19
.global clear_led
20
.type clear_led, @function
21
clear_led:
22
/* prologue: function */
23
/* frame size = 0 */
24
/* stack size = 0 */
25
.L__stack_usage = 0
26
sbi 0x5,1
27
/* epilogue start */
28
ret
29
.size clear_led, .-clear_led
30
.global __vector_18
31
.type __vector_18, @function
32
__vector_18:
33
push r1
34
push r0
35
in r0,__SREG__
36
push r0
37
clr __zero_reg__
38
push r18
39
push r19
40
push r20
41
push r21
42
push r22
43
push r23
44
push r24
45
push r25
46
push r26
47
push r27
48
push r30
49
push r31
50
/* prologue: Signal */
51
/* frame size = 0 */
52
/* stack size = 15 */
53
.L__stack_usage = 15
54
lds r30,fptr
55
lds r31,fptr+1
56
sbiw r30,0
57
breq .L4
58
icall
59
.L4:
60
/* epilogue start */
61
pop r31
62
pop r30
63
pop r27
64
pop r26
65
pop r25
66
pop r24
67
pop r23
68
pop r22
69
pop r21
70
pop r20
71
pop r19
72
pop r18
73
pop r0
74
out __SREG__,r0
75
pop r0
76
pop r1
77
reti
78
.size __vector_18, .-__vector_18
79
.section .text.startup,"ax",@progbits
80
.global main
81
.type main, @function
82
main:
83
/* prologue: function */
84
/* frame size = 0 */
85
/* stack size = 0 */
86
.L__stack_usage = 0
87
sbi 0x4,1
88
ldi r18,lo8(gs(set_led))
89
ldi r19,hi8(gs(set_led))
90
ldi r24,lo8(gs(clear_led))
91
ldi r25,hi8(gs(clear_led))
92
.L10:
93
sts fptr+1,r19
94
sts fptr,r18
95
ldi r30,lo8(24999)
96
ldi r31,hi8(24999)
97
1: sbiw r30,1
98
brne 1b
99
rjmp .
100
nop
101
sts fptr+1,r25
102
sts fptr,r24
103
ldi r30,lo8(24999)
104
ldi r31,hi8(24999)
105
1: sbiw r30,1
106
brne 1b
107
rjmp .
108
nop
109
rjmp .L10
110
.size main, .-main
111
.global fptr
112
.section .bss
113
.type fptr, @object
114
.size fptr, 2
115
fptr:
116
.zero 2
117
.ident "GCC: (GNU) 10.2.0"
118
.global __do_clear_bss
(Dass Funktionszeiger in ISRs auf dem AVR keine sonderlich gute Idee
sind, weil sie eine push/pop-Orgie nach sich ziehen, steht auf einem
anderen Blatt.)
Peter D. schrieb:> Der Compiler sieht im Mainkontext keinerlei Verwendung und schenkt sich> die Zuweisung in die Variable.
Das reicht allein nicht.
Main muss nach der Zuweisung noch eine weitere Zuweisung haben,
dazwischen keinen Funktionsaufruf aber eine loop (mit volatile var als
Bedingung).
Also kann prinzipiell Probleme bereiten, aber praktisch selten. Quasi
nur wenn alles im Interrupt läuft.
Jörg W. schrieb:> (Dass Funktionszeiger in ISRs auf dem AVR keine sonderlich gute Idee> sind, weil sie eine push/pop-Orgie nach sich ziehen, steht auf einem> anderen Blatt.)
Die Aussage reicht mir schon, um die Bedingung in der ISR zu machen und
auf den Pointer zu verzichten.
Danke für die hilfreichen Beiträge.
Grüße, Alex
Alexander H. schrieb:> Die Aussage reicht mir schon, um die Bedingung in der ISR zu machen und> auf den Pointer zu verzichten.
Naja wenn man in einer Hochsprache schreibt, sollte der Fokus erst mal
auf gutem Code in dieser Hochsprache liegen. Erst wenn es sein muss
sollte man diesen Code auf besseren ASM Code hin optimieren. z.B. wenn
man wirklich Zeitkritische Dinge macht, welche den Controller sowieso
schon an den Rand der Leistungsfähigkeit bringen.
A. S. schrieb:> Main muss nach der Zuweisung noch eine weitere Zuweisung haben,> dazwischen keinen Funktionsaufruf aber eine loop (mit volatile var als> Bedingung).
Ja, korrekt, man muss natürlich irgendwas haben, was potenziell in der
Lage sein könnte, den Wert zu verwenden. Wenn ich in meinem Beispiel die
_delay_ms()-Aufrufe rausnehme, werden die wiederholten Zuweisungen an
die Variable weg optimiert.
Aber wie du schon schriebst: in der Praxis normalerweise kein Problem.
Alexander H. schrieb:>> (Dass Funktionszeiger in ISRs auf dem AVR keine sonderlich gute Idee>> sind, weil sie eine push/pop-Orgie nach sich ziehen, steht auf einem>> anderen Blatt.)>> Die Aussage reicht mir schon, um die Bedingung in der ISR zu machen und> auf den Pointer zu verzichten.
Hast du das auch richtig verstanden? Es geht bei dem Problem nicht nur
um Funktionszeiger, sondern auch um normale Funktionsaufrufe in einer
ISR. Die sind beim avr gcc relativ "teuer" in bezug auf die push/pop
Liste der Register.
Beitrag "Atmega ISR Prolog dauert zu lange - verschieben möglich?"
Ich dachte, Jörgs Aussage bezog sich jetzt konkret auf mein Problem mit
dem Funktionszeiger. Mir ist bekannt, dass Funktionsaufrufe push/pop
verursachen.
Bei mir ist es im wesentlichen das lesen einer uint16_t Variable über
SPI. Die Lesefunktion ist in einer (eigenen) Bibliothek für den
Sensor-Baustein, die ich mir geschrieben habe. Ich könnte das noch als
inline deklarieren, wenn das was bringt. Es sind aber nicht viele
ineinander verschachtelte Funktionen, nur Lesen und darin noch eine
Funktion, die die Pins setzt und eine, die die SPI bedient. Das ist
nicht ewig tief.
Alexander H. schrieb:> Ich dachte, Jörgs Aussage bezog sich jetzt konkret auf mein Problem mit> dem Funktionszeiger. Mir ist bekannt, dass Funktionsaufrufe push/pop> verursachen.
Sie verursachen in einer ISR aber noch mehr push/pop.
> Ich könnte das noch als> inline deklarieren, wenn das was bringt.
Das funktioniert nicht immer. Probier's aus.
> Es sind aber nicht viele> ineinander verschachtelte Funktionen, nur Lesen und darin noch eine> Funktion, die die Pins setzt und eine, die die SPI bedient. Das ist> nicht ewig tief.
Klingt trotzdem nach einer arg akademischen Implementierung eines
schnöden SPI-Zugriffs.
Falk B. schrieb:> Klingt trotzdem nach einer arg akademischen Implementierung eines> schnöden SPI-Zugriffs.
Falls ein SPI-Zugriff timergesteuert sehr exakt erfolgen soll, mag sowas
Sinn haben.
Jörg W. schrieb:>> Klingt trotzdem nach einer arg akademischen Implementierung eines>> schnöden SPI-Zugriffs.>> Falls ein SPI-Zugriff timergesteuert sehr exakt erfolgen soll, mag sowas> Sinn haben.
Das meinte ich nicht. Sondern die Verschachtelung von 2-3 Funktionen für
bissel SPI, was mit einem Dutzend Zeilen in einer Funktion erledigen
kann.
Jörg W. schrieb:> Falls ein SPI-Zugriff timergesteuert sehr exakt erfolgen soll, mag sowas> Sinn haben.
Das ist bei mir der Fall. Ich habe eine genaue Uhr auf dem Board und der
Messwert muss zum Takt der Uhr (1kHz) genommen werden.
In der ISR passiert im Wesentlichen das:
Alexander H. schrieb:> tmp = adis16003SendReceiveByte(ADIS_Y_DIRECTION);> tmp = (tmp << 8) | adis16003SendReceiveByte(ADIS_Y_DIRECTION);
Das kann man in eine Funktionsaufruf zusammenfassen.
Spart Zeit / Overhead.
Alexander H. schrieb:> adis16003SetCS(0);> adis16003SetCS(1);
Das kann man auch ohne Funktionsaufruf machen, nämlich als Makro.
Spart Zeit / Overhead.
vonweiterweg schrieb:> Das kann man in eine Funktionsaufruf zusammenfassen.> Spart Zeit / Overhead.
Klar. Eine Eine Zeile Sourcecode statt zwei spart 50%. Wenn du dann noch
die Funktionsnamen auf die halbe Länge kürzt, spart das nochmal 50%.
Macht zusammen dann 100%.
Oliver
mh schrieb:> Die Zuweisung von fptr müsste noch atomisiert werden oder?
Naja, du meinst eine atomare Zuweisung. Atomisieren bedeutet im
Allgemeinen eher das Gegenteil ;-)
Hier meine Version wie ich das direkt in der ISR implementieren
würde ohne Zeit mit Funktionsaufrufen und entsprechendem push/pull
Overhead zu verschwenden.
1
#define WAITSPI_RDY do { /* wait */ } while ( (SPIF_STATUS & 0x80) == 0);
2
3
#define SPI_LCD_CS_BIT (1<<PIN0)
4
#define CS_High PORTF_OUTSET = SPI_LCD_CS_BIT
5
#define CS_Low PORTF_OUTCLR = SPI_LCD_CS_BIT
6
7
uint16_tXMega_SPI_ReadWord(void)// das ganze in die ISR
8
{
9
uint8_tbyte0,byte1;
10
11
// Hier muss man ggf noch etwas Sinnvolles senden
12
SPIF_DATA=0;// Transfer starten (adis16003SendReceiveByte)
13
WAITSPI_RDY;
14
byte0=SPIF_DATA;// SPI lesen
15
16
// Hier muss man ggf noch etwas Sinnvolles senden
17
SPIF_DATA=0;// Transfer starten (adis16003SendReceiveByte)
vonweiterweg schrieb:> return ( (uint16_t)(byte0<<8) | byte1 );
Das funktioniert nur deshalb, weil die Integer Promotion vom Compiler
sowieso VOR dem Schieben gemacht wird. Bei 32 Bit Zielgröße würde es
NICHT funktionieren. Richtig ist es so. Erst der cast, dann schieben!
Die Klammer um byte0 ist eine Angstmaßnahme, braucht man eigentlich
nicht.
return ( (((uint16_t)byte0)<<8) | byte1 );
Alexander H. schrieb:> Ich habe mit dazu einen Funtionspointer definiert> uint16_t ( *volatile readSensor )( void )>> dem weise ich abhängig von der Bedingung am Anfang der main die Funktion> zu:> if(read1 == 1)> readSensor = readSensor1;> else> readSensor = readSensor2;>> Ist das so korrekt?
Natürlich nicht. Du hast ein Fu..poi.. deklariert. Und da ist das
volatile blödsinn. :-<<<
Wenn volatile, gehört zur Definition der Variablen readSensor.
Und den atomaren Zuweisungskrams erwarte ich vom Compiler. Wenn er das
nicht kann, ist er Schrott. :-<<<
MaWin schrieb:> Natürlich nicht. Du hast ein Fu..poi.. deklariert. Und da ist das> volatile blödsinn. :-<<<
Blödsinn ist hier die Hälfte von dem, was du schreibst.
> Wenn volatile, gehört zur Definition der Variablen readSensor.
Genau da stand/steht es.
> Und den atomaren Zuweisungskrams erwarte ich vom Compiler. Wenn er das> nicht kann, ist er Schrott. :-<<<
Schrott ist, was du da schreibst. Nur, weil du dir "MaWin" als Nick
klaust, hilft das noch lange nichts.
Mittlerweile gibt es sogar ein <stdatomic.h>, welches in den nächsten
C-Standard mit reinkommen wird, um sowas abzuhandeln. Das werden sie
sich nicht aus Spaß so augedacht haben.
MaWin schrieb:> Du hast ein Fu..poi.. deklariert. Und da ist das> volatile blödsinn. :-<<<>> Wenn volatile, gehört zur Definition der Variablen readSensor.
Bist du dir da sicher?
MaWin schrieb:> mh schrieb:>> wer auch>> immer du bist.>> MaWin
Naja, jedenfalls nicht der MaWin. Der, den die, die ihn kennen, schon
aus dem Usenet kennen.
Ist aber auch egal, deine Ahnungslosigkeit hinsichtlich C hast du uns ja
nun ausreichend demonstriert.
MaWin schrieb:> Jörg W. schrieb:>> der MaWin>> Doch ich bin MaWin, der MaWin.
Du brauchst nicht zu lügen. Der MaWin bist du nicht, sondern
irgendeiner. Wir kennen unsere Pappenheimer hier …
Alexander H. schrieb:> dem weise ich abhängig von der Bedingung am Anfang der main die Funktion> zu:> if(read1 == 1)> readSensor = readSensor1;> else> readSensor = readSensor2;
Da sind dann mehrere Abhängigkeiten über den Sourcecode verstreut.
Warum machst du das nicht ohne Funktionspointer und fragst read1 in der
ISR ab? So ist es eine Operation mehr (das Laden der Sprungadresse)
während die sonst "fest verdrahtet" im Opcode steht.
Das ist leichter zu lesen (auch im Assembler) und auch in 10 Jahren noch
nachvollziehbar. Wenn die read1 Variable der Bitbreite des Prozessors
entspricht muss das auch nicht atomar sein. Der Prozessor schreibt die
Variable dann ja in einem Befehlszyklus, der ist imo auch bei Atmel uCs
atomar.