volatile Funktionspointer in ISR

OP #6682750
Lesenswert?

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 ( *volatile readSensor )( 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
Moderator Persönliche Seite #6682790
Lesenswert?

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.
#6682840
Lesenswert?

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_t myVar=0;
2

3
int main() {
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.
#6682850
Lesenswert?

Walter T. schrieb:
> Analog wäre:
>
1
> uint8_t myVar=0;
2
> 
3
> int main() {
4
> myVar = 0;
5
> while(1) {
6
>     myVar = !myVar;
7
> }
8
> 
9
> }
10
> 
11
> ISR() {
12
>    if (myVar) doSomething();
13
> }
14
>

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.
Moderator Persönliche Seite #6682864
Lesenswert?

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.)
Gast #6682929
Lesenswert?

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.
#6682937
Lesenswert?

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.
Moderator Persönliche Seite #6682940
Lesenswert?

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.
#6682954
Lesenswert?

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?"
OP #6682989
Lesenswert?

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.
#6683003
Lesenswert?

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.
#6683011
Lesenswert?

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.
OP #6683019
Lesenswert?

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:
1
uint16_t adis16003ReadValueY( void )
2
{
3
  int16_t tmp;
4
  adis16003SetCS(0);              
5
  tmp = adis16003SendReceiveByte(ADIS_Y_DIRECTION);
6
  tmp = (tmp << 8) | adis16003SendReceiveByte(ADIS_Y_DIRECTION);
7
  adis16003SetCS(1);
8
  return tmp;
9
}

Das ist während der Messung außer einem 100ms Timer, der noch paar LED 
setzt, auch das Einzige was an Interrupts passiert.
Gast #6683101
Lesenswert?

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_t XMega_SPI_ReadWord (void) // das ganze in die ISR
8
{
9
  uint8_t  byte0, 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)
18
  WAITSPI_RDY  ;
19
  byte1 = SPIF_DATA;  // SPI lesen
20

21
  return ( (uint16_t)(byte0<<8) | byte1 );
22

23
} /* --- XMega_SPI_ReadWord --- */
#6683106
Lesenswert?

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 );
Gast #6684486
Lesenswert?

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. :-<<<
Moderator Persönliche Seite #6684495
Lesenswert?

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.
Beitrag #6684619 wurde von einem Moderator gelöscht.
Beitrag #6684625 wurde von einem Moderator gelöscht.
Beitrag #6684629 wurde von einem Moderator gelöscht.
Beitrag #6684633 wurde von einem Moderator gelöscht.
Beitrag #6684634 wurde von einem Moderator gelöscht.
Gast #6684636
Lesenswert?

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.
Beitrag #6685044 wurde von einem Moderator gelöscht.
Beitrag #6685048 wurde von einem Moderator gelöscht.
Beitrag #6685700 wurde von einem Moderator gelöscht.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren