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
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.
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
volatileintisrVar;
2
3
ISR_XXX()
4
{
5
inttmpVar=isrVar;
6
7
machwasmittmpVar
8
9
sirVar=tmpVar;
10
}
11
12
main()
13
{
14
intVar,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(10maldurchlaufen)
25
{
26
machwasmitVar;// hier kann der Compiler nach Herzenslust optimieren
27
}
28
cli();
29
Var2=isrVar;// falls erforderlich kann man den Wert natürlich nochmal holen
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 :-(
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
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
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.
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".
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.
+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?
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.
@ 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
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.