www.mikrocontroller.net

Forum: Compiler & IDEs Variablen in Hauptprogramm und ISR nutzen


Autor: Be Mi (bemi)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Oliver (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
volatile int isrVar;

ISR_XXX()
{
   int tmpVar = isrVar;

   mach was mit tmpVar

   sirVar = tmpVar;
}

main()
{
   int Var, Var2;

   init_isr();
   sei();

   for (;;)
   {
      cli();
      Var = isrVar; // hier wird der Wert geholt
      sei();
      Schleife (10 mal durchlaufen)
      {
         mach was mit Var; // hier kann der Compiler nach Herzenslust optimieren
      }
      cli();
      Var2 = isrVar; // falls erforderlich kann man den Wert natürlich nochmal holen
      sei();     
    
      mach noch mehr mit den Variablen
   }
}

Oliver

Autor: Bernd (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :-(

Autor: Oliver (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Bernd (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Bernd (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gibt's für Push-SREG und Pop-SREG fertige C-Funktionen?

Autor: Johannes M. (johnny-m)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nee, bzw. zu trivial ;-)

  {
    uint8_t oldSREG;
    oldSREG = SREG;   // "Push"
    cli();
    // Tu was
    SREG = oldSREG;   // "Pop"
  }


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

Autor: Andreas Schwarz (andreas) (Admin) Benutzerseite Flattr this
Datum:

Bewertung
0 lesenswert
nicht 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".

Autor: Werner B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Be Mi (bemi)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Apropos volatile,

verstehe ich hier was falsch?
volatile unsigned char *msgQueueReadPtr, *msgQueueWritePtr;          // pointers for message queue handling


143:          if (msgQueueReadPtr!=msgQueueWritePtr) {                // any new messages?
+00000194:   91E00168    LDS     R30,0x0168       Load direct from data space
+00000196:   91F00169    LDS     R31,0x0169       Load direct from data space
+00000198:   91800100    LDS     R24,0x0100       Load direct from data space
+0000019A:   91900101    LDS     R25,0x0101       Load direct from data space
+0000019C:   17E8        CP      R30,R24          Compare
+0000019D:   07F9        CPC     R31,R25          Compare with carry
+0000019E:   F3A9        BREQ    PC-0x0A          Branch if equal
144:            messageSize = *msgQueueReadPtr++;                     // read message length byte
+0000019F:   9161        LD      R22,Z+           Load indirect and postincrement
+000001A0:   93600124    STS     0x0124,R22       Store direct to data space
+000001A2:   93F00169    STS     0x0169,R31       Store direct to data space
+000001A4:   93E00168    STS     0x0168,R30       Store direct to data space
145:            if (msgQueueReadPtr == msgQueue+64) msgQueueReadPtr = msgQueue;   // prevent read ptr to leave msg queue memory
+000001A6:   E645        LDI     R20,0x65         Load immediate
+000001A7:   E051        LDI     R21,0x01         Load immediate
+000001A8:   17E4        CP      R30,R20          Compare
+000001A9:   07F5        CPC     R31,R21          Compare with carry
+000001AA:   F421        BRNE    PC+0x05          Branch if not equal
+000001AB:   93D00169    STS     0x0169,R29       Store direct to data space
+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?

Autor: Andreas K. (a-k)
Datum:

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

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

Autor: Be Mi (bemi)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Be Mi (bemi)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.