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.
Gast
#740563
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 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | |
29 | |
30 | |
31 | |
32 | |
33 | |
34 | |
Oliver
Gast
#740799
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 :-(
Gast
#740830
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
Gast
#740914
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.
Gast
#740946
Gibt's für Push-SREG und Pop-SREG fertige C-Funktionen?
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.
Nee, bzw. zu trivial ;-)
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff
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".
Gast
#741257
> 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
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.
Apropos volatile, verstehe ich hier was falsch?
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | |
16 | |
17 | |
18 | |
19 | |
20 | |
21 | |
22 | |
23 | |
24 | |
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?
1 | |
Das Objekt auf das msgQueueReadPtr zeigt ist volatile, nicht aber msgQueueReadPtr selbst.
1 | |
Der Zeiger selbst ist volatile. PS: Wenn dir diese Syntax missfällt:
1 | |
2 | |
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.
Antwort schreiben
Bitte melde dich an, um einen Beitrag zu schreiben.