Forum: Compiler & IDEs Variablen in Hauptprogramm und ISR nutzen


von Be M. (bemi)


Lesenswert?

Ich habe da mal so ein paar Fragen, die ich mir so nicht selbst 
beantworten kann.

Generell geht es mir darum, dass verschiedene Variablen sowohl im 
Hauptprogramm als auch in Interrupts benutzt werden. Wie ich gelernt 
habe, "sollte" man solche Variablen als 'volatile' deklarieren, damit 
der Compiler weiß, dass er bei jeder Benutzung die Variable neu aus dem 
Speicher holt.

Nun ist es mir wichtig, dass das Programm auf Speed optimiert wird. Und 
wenn ich die Variablen als volatile deklariere, behindere ich den 
Compiler ja, den Quellcode maximal zu optimieren (Registernutzung).

Nun ist es aber so, dass es wenig Sinn mach, dass z.B. die Variable im 
Interrupt immer wieder aus dem Speicher gelesen wird, da der Interrupt 
durch nichts unterbrochen wird (nur ein Interrupt gleichzeitig/keine 
verschiedenen IRQ Level.). Also wäre hier eine volatile Dekleration nur 
Störend.

Gehen wir mal auf das Hauptprogramm ein. Da die Manipulation von 
Variableninhalten in meheren Befehlen erfolgen, muss ich zwangsläufig 
Interrupts dazwischen unterbinden ( cli(); ) und danach wieder freigeben 
( sei(); ). Also wird währen dieser Zeit die Variable auch nicht durch 
den Interrupt verändert, also wäre auch hier ein volatile unnötig und 
störend für die Speed-Optimierung.

Was ist aber nun, wenn ich folgendes Konstrukt habe:

-----------------------------------------
cli();
mach was mit den Variablen;
sei();
Schleife (10 mal durchlaufen)
{
  cli();
  mach was mit den Variablen;
  mach was mit den Variablen;
  mach was mit den Variablen;
  mach was mit den Variablen;
  sei();
}
cli();
mach was mit den Variablen;
sei();
-------------------------------------------

Klar ist, dass wenn die Interrupts enabled sind, sich die Variablen 
verändern können. Somit ist eine Optimierung zwichen sei() und cli() 
nicht zulässig (->volatile erforderlich), aber zwischen cli() und sei() 
kann ja kein Interrupt dazwischenhauen (-> volatile würde nur die 
Optimierung stören).

Wie sage ich dem Compiler denn nun, wo er die Variable tatsächlich neue 
holen muss, und wo er die Möglichkeit hat, nach Herzenslust, die 
Variablenwerte in Prozessorregistern zu halten.

In Assembler ist sowas ja kein Problem, aber in C?????

Wie geht man mit so einer Problematik um.

VG,
Bernd

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Zwei Wege, wie man das je nach Anwendung machen könnte.

Arbeitskopie
Du könntest die volatile definierte Variable an eine nicht-volatile 
definierte Variable zuweisen und letztere dann nach Herzenslust 
manipulieren (ohne cli/sei Abschnitt!), ohne dass diese jedesmal frisch 
eingelesen wird. Dieser Weg kann ich mir gut bei einer einmaligen 
Arbeitskopie eines Wertes vorstellen, wie z.B. einen Zählerstand 
auslesen und dann z.B. für LCD aufbereiten.

Semaphor
Oder du könntest mit einem atomar-manipulierbaren, volatile Semaphor 
("main_macht_io") der IRQ-Routine anzeigen, dass das Hauptprogramm 
gerade die gemeinsame Variablenstruktur manipuliert (und ggf. 
umgekehrt). Wenn es ein mehrfach auslösbarer IRQ ist (z.B. UART-Empfang 
bei Nichtauslesen des UDR-Registers), macht die IRQ-Routine im 
gesperrt-signailsierten Zustand nix mit der gemeinsamen 
Variablenstruktur und wird gleich nochmal aufgerufen in der Hoffnung, 
dass main() dann fertig oder ein Stück weiter ist. Diesem Weg kann ich 
mir gut z.B. bei einer UART-FIFO-Pufferung vorstellen.

von Oliver (Gast)


Lesenswert?

Du setzt die falschen Schwerpunkte. Wenn dein Programm zu langsam sein 
sollte, liegt es bestimmt nicht an volatile.

>Was ist aber nun, wenn ich folgendes Konstrukt habe:
>...

Na ja, wenn du das ganze Hauptprogramm über die Interrupts sperrst, 
brauchst du gar keine. Damit lösen sich alle deine Probleme von selbst.

Sinnvoller wäre:
1
volatile int isrVar;
2
3
ISR_XXX()
4
{
5
   int tmpVar = isrVar;
6
7
   mach was mit tmpVar
8
9
   sirVar = tmpVar;
10
}
11
12
main()
13
{
14
   int Var, Var2;
15
16
   init_isr();
17
   sei();
18
19
   for (;;)
20
   {
21
      cli();
22
      Var = isrVar; // hier wird der Wert geholt
23
      sei();
24
      Schleife (10 mal durchlaufen)
25
      {
26
         mach was mit Var; // hier kann der Compiler nach Herzenslust optimieren
27
      }
28
      cli();
29
      Var2 = isrVar; // falls erforderlich kann man den Wert natürlich nochmal holen
30
      sei();     
31
    
32
      mach noch mehr mit den Variablen
33
   }
34
}

Oliver

von Bernd (Gast)


Lesenswert?

Na ja,

der Anwendungszweck ist ein bischen anders. Bei der Datenstruktur 
handelt es sich um eine Message Queue mit den dazugehörigen Schreib- und 
Lesezeigern.

Was ich nun brauche ist eine Möglichkeit, der Queue Messages sowohl aus 
dem Hauptprogramm als auch aus einer ISR heraus Messages hinzuzufügen. 
Dabei bestehen Messages aus mehreren Bytes. Das heißt, dass das 
Hauptprogram eine Message immer komplett hinzufügen muss, ohne das es 
dabei unterbrochen werden darf, da auf der anderen Seite die ISR auch 
immer in der Lage sein muss ihre Messages hinzuzufügen, damit keine 
Nachrichten verloren gehen. Also ist das reservieren/allokieren der 
Struktur nicht sinnvoll, da dann ja die ISR zwar weiß, dass sie nicht 
schreiben darf, nur was macht sie dann mit den zu sendenden Daten?

Letztlich gibt es nur zwei Möglichkeiten.
1. Beim schreiben aus der Anwendung immer IRQs verbeiten. Dann brauche 
ich auch kein volatile. Fraglich ist nur, ob wenn am Anfang einer 
Funktion ein cli() kommt und am Ende der Funktion ein sei(), ob der 
Compiler auf die Idee kommen könnte, aus welchen Gründen auch immer, 
bereits vorher dem cli() oder nach dem sei() noch auf die Variable im 
Speicher zuzugreifen.
2. Zwei Message Queues verwenden, dann sollte das Problem in der Form 
nicht auftauchen. Nachteil, ich muss Speicher für 2 Message Queues 
reservieren :-(

von Oliver (Gast)


Lesenswert?

Wenn die Größe einer Message vor dem Schreiben bekannt ist (was 
eigentlich immer der Fall sein sollte), reicht es doch aus, im 
Hauptprogramm nur das Weitersetzen des Schreibzeigers um eine 
Messagegröße durch cli() und sei() ununterbrechbar zu machen. Damit 
reservierst du den Schreibzugriff auf diese Message in der Queue. Das 
anschliessende Füllen des Blocks darf dann durch die ISR unterbrochen 
werden (wenn der Inhalt der zu schreibenden Daten nicht wiederum von 
aktuell empfangenen Daten der ISR abhängt). Die ISR kann währenddessen 
in den nächsten Block schreiben, falls erforderlich, so daß nichts 
verloren gehen kann.

Oliver

von Bernd (Gast)


Lesenswert?

Die Idee mit dem Block reservieren gefällt mir. Da habe ich noch 
garnicht dran gedacht. Werde ich bei Gelegenheit mal ausprobieren. 
Scheint mir eine gute Lösung zu sein.

Danke

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Blockreservieren ist quasi die Semaphor-Methode.

Aber was anderes: Ich würde eine Funktion SendMessage() benutzen )die 
Idee hatte schon Bill...). In der kann man "minimalinvasiv" und zentral 
die Interrupts sperren oder nicht.

Wenn man die SendMessage() aus dem Interrupthandler aufruft (spricht 
nichts dagegen, falls die SenMessage() keine nicht-reentranten 
Libraryfunktionen benutzt), dann an den Trick mit 
"Push-SREG"/CLI/"Pop-SREG" denken, statt Brute-Force CLI/SEI zu machen. 
Mit dem Trick schaltet man die Interrupts nur ein, wenn sie 
eingeschaltet waren und zwar sowohl bei dem Aufruf aus dem Interruptcode 
als auch aus dem Nicht-Interruptcode.

von Bernd (Gast)


Lesenswert?

Gibt's für Push-SREG und Pop-SREG fertige C-Funktionen?

von Johannes M. (johnny-m)


Lesenswert?

Bernd wrote:
> Gibt's für Push-SREG und Pop-SREG fertige C-Funktionen?
Was willste denn damit? Im Interrupt-Handler macht der Compiler das 
selbst.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Nee, bzw. zu trivial ;-)
1
  {
2
    uint8_t oldSREG;
3
    oldSREG = SREG;   // "Push"
4
    cli();
5
    // Tu was
6
    SREG = oldSREG;   // "Pop"
7
  }

http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Bernd wrote:
> Letztlich gibt es nur zwei Möglichkeiten.
> 1. Beim schreiben aus der Anwendung immer IRQs verbeiten. Dann brauche
> ich auch kein volatile.

Für Variablen auf die in Interrupt und Main-Loop zugegriffen wird 
braucht man immer volatile, auch wenn man während dem Zugriff Interrupts 
deaktiviert. Siehe Beitrag "Re: Geschwindigkeit bei volatile-variablen".

von Werner B. (Gast)


Lesenswert?

> Gibt's für Push-SREG und Pop-SREG fertige C-Funktionen?

Für das aktuelle WinAVR sieh dir die Doku zu <util/atomic.h> an.

WeeB

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Stefan "stefb" B. wrote:

> *Arbeitskopie*
> ... Dieser Weg kann ich mir gut bei einer einmaligen
> Arbeitskopie eines Wertes vorstellen, wie z.B. einen Zählerstand
> auslesen und dann z.B. für LCD aufbereiten.

Funktioniert auch prima für Manipulationen innerhalb der ISR.

von Be M. (bemi)


Lesenswert?

Apropos volatile,

verstehe ich hier was falsch?
1
volatile unsigned char *msgQueueReadPtr, *msgQueueWritePtr;          // pointers for message queue handling
2
3
4
143:          if (msgQueueReadPtr!=msgQueueWritePtr) {                // any new messages?
5
+00000194:   91E00168    LDS     R30,0x0168       Load direct from data space
6
+00000196:   91F00169    LDS     R31,0x0169       Load direct from data space
7
+00000198:   91800100    LDS     R24,0x0100       Load direct from data space
8
+0000019A:   91900101    LDS     R25,0x0101       Load direct from data space
9
+0000019C:   17E8        CP      R30,R24          Compare
10
+0000019D:   07F9        CPC     R31,R25          Compare with carry
11
+0000019E:   F3A9        BREQ    PC-0x0A          Branch if equal
12
144:            messageSize = *msgQueueReadPtr++;                     // read message length byte
13
+0000019F:   9161        LD      R22,Z+           Load indirect and postincrement
14
+000001A0:   93600124    STS     0x0124,R22       Store direct to data space
15
+000001A2:   93F00169    STS     0x0169,R31       Store direct to data space
16
+000001A4:   93E00168    STS     0x0168,R30       Store direct to data space
17
145:            if (msgQueueReadPtr == msgQueue+64) msgQueueReadPtr = msgQueue;   // prevent read ptr to leave msg queue memory
18
+000001A6:   E645        LDI     R20,0x65         Load immediate
19
+000001A7:   E051        LDI     R21,0x01         Load immediate
20
+000001A8:   17E4        CP      R30,R20          Compare
21
+000001A9:   07F5        CPC     R31,R21          Compare with carry
22
+000001AA:   F421        BRNE    PC+0x05          Branch if not equal
23
+000001AB:   93D00169    STS     0x0169,R29       Store direct to data space
24
+000001AD:   93C00168    STS     0x0168,R28       Store direct to data space

In Zeile 143 verwende ich den Pointer msgQueueReadPtr, der volatile 
deklariert ist und in R30/31 landet.
Warum wird er in Zeile 144/145 nicht erneut gelesen? Da wird einfach mit 
R30/31 verglichen, anstatt ihn erneut aus dem Speicher zu lesen und das, 
obwohl der Ptr volatile ist? Als Optimierung habe ich -Os verwendet. Was 
ist, wenn sich der Ptr in der Zwischenzeit geändert hat. War volatile 
nicht gerade dafür, dass die Variable immer neu gelesen wird?

von Andreas K. (a-k)


Lesenswert?

1
volatile unsigned char *msgQueueReadPtr;
Das Objekt auf das msgQueueReadPtr zeigt ist volatile, nicht aber 
msgQueueReadPtr selbst.
1
unsigned char *volatile msgQueueReadPtr;
Der Zeiger selbst ist volatile.

PS: Wenn dir diese Syntax missfällt:
1
typedef unsigned char * msgQueuePtr_t;
2
volatile msgQueuePtr_t msgQueueReadPtr, msgQueueWritePtr;

von Be M. (bemi)


Lesenswert?

Ich muss zugeben, dass ich über solche Dinge noch nie nachgedacht habe. 
In Asm war das alles so einfach ( traurig :-( ).
Ich muss da wohl noch ein bischen rumexperimentieren, bis der Compiler 
das macht, was ich will.

von Falk B. (falk)


Lesenswert?

@ Bernd M. (bemi)

>Ich muss da wohl noch ein bischen rumexperimentieren, bis der Compiler
>das macht, was ich will.

Oder systematisch vorgehen. Siehe Interrupt, dort ist ein Link auf 
eine gute Erklärung von volatile drin.

MFG
Falk

von Be M. (bemi)


Lesenswert?

Ich habe mich jetzt dazu entschlossen alle Variablen, die sowohl im 
Hauptprogramm als auch in der ISR vorkommen als volatile zu deklarieren 
und mir dann zu Beginn eine temporäre Arbeitskopie zu erzeugen, die ich 
am Ende des jeweiligen Blocks dann expliziet wieder in die 
Ursprungsvariable zurückschreibe. Dies Temp Variable wird vom Optimizer 
in CPU Register gelegt, so dass der gewünschte, optimale Code entsteht.

Dumm nur, dass der gleiche C-Code für einen PIC18 extrem ineffizient 
wird, da er nur ein Arbeitsregister hat und dann die Temp-Variable auch 
noch auf dem Stack ablegt, der beim PIC18 ohnehin etwas kompliziert 
arbeitet.

Nun ja, man kann nicht alles haben.

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.