Hallo zusammen, ich sitze hier schon recht lange und suche nach einer Lösung für mein Problem - leider ohne Erfolg Habe ein Atmega16 und programmiere mit WinAVR. habe ein Uhrenquarz der ein OC Timerinterrupt auslöst. In dem Interruptfunktion wird eine globale Variable erhört. Funktion timer_get() funktioniert In der timer_wait funktion habe ich Probleme mit der while-schleife. wenn ich im Makefile den Optimierungsgrad auf 0 stelle oder in die while-Schleife z.B.setLED(2); reinschreibe geht die Funktion. Dann "optimiert" der Compiler die Funktion nicht mehr so, dass sie nicht mehr geht - bleibt dann aber in der Schleife hängen und geht nicht weiter (finde ich komisch sollte er nicht die so "optimieren", dass er die einfach weglässt?) uint32_t timer_get(){ //liefert Zeit im ms seit Anschalten return (timer_overflows * 125)>>4; } void timer_wait(uint32_t delay){ uint32_t old_timer_value = timer_get(); uint32_t i; while (old_timer_value + delay > timer_get()); //geht nur wenn optimierungsgrad = 0 //sonst bleibt er hängen und schaltet LED(1) nicht ein setLED(1); //zu testzwecken } //die folgende While-Schleife geht auch mit optimierungsgrad = s //hier "optimiert" der compiler die schleife nicht so, dass sie nicht mehr geht. // aber ich möchte ja nicht eine LED anschalten müssen, damit die Schleife geht. ist nur für testzwecke drin. while (old_timer_value + delay > timer_get()){ setLED(3); }; kann ich hier ein anderen befehl reinschreiben, damit der compiler die schleife nicht "zerstört" oder kann ich die Optimierung temporär ausschalten? Vielen Dank für eure Hilfe Torsten
Normalerweise optimiert der gcc Programme so, daß sie hinterher genauso fehlerhaft sind wie vorher, nur schneller und/oder kleiner. An den Codeschnipseln kann man nichts erkennen - bitte das ganze Programm posten. Oliver
@ Torsten Müller (Gast) >Habe ein Atmega16 und programmiere mit WinAVR. >habe ein Uhrenquarz der ein OC Timerinterrupt auslöst. >In dem Interruptfunktion wird eine globale Variable erhört. >Funktion timer_get() funktioniert Guggst du hier, das geht escht ab. Sleep Mode Interrupt >so, dass sie nicht mehr geht - bleibt dann aber in der Schleife hängen >und geht nicht weiter (finde ich komisch sollte er nicht die so >"optimieren", dass er die einfach weglässt?) Ich wette drei Bier, dass da ein volatile fehlt. >oder kann ich die Optimierung temporär ausschalten? Nöö, nicht nötig. MFG Falk
Hallo ihr seit echt super. Und danke auch für die schnelle Antwort. Also es lag wirklich an dem volatile. das mit dem volatile habe ich allerdings noch nicht so ganz verstanden. Hab auch nochmal im Skript nachgeschaut. Also volatile braucht man, wenn das Programm und die Nebenläufigkeit/Interrupt auf die gleichen Daten zugreifen. (Also bei mehr als 8 bit- Variablen) im skript seht: Lösung für dieses Problem: -compiler muss variablen vor jedem Zugriff aus dem Speicher laden und anschließend zurückschreiben. => attribut volatile Aber wenn er den Interruptzugriff wieder rückgängig macht (timer_overflows++;), dann verzählt der sich doch, oder? @Oliver (Volatile ist jetzt eingebaut) Bitte Kommentare ignorieren ;-) #include <avr/signal.h> #include <avr/io.h> #include <avr/interrupt.h> #include <inttypes.h> #include "Timer.h" volatile static uint32_t timer_overflows; //volatile ist wichtig sonst geht die Funktion timer_wait() nicht mehr SIGNAL(SIG_OVERFLOW2){ //sollte einmal im Jahr (365Tage) zurückgesetzt werden sonst läuft der Zähler über //also wenn timer_overflows== 4.036.608.000 ist timer_overflows++; } void timer_init(){ // Verwendet Interrupt 2 //Auf den externen Quarz stellen (S.129) ASSR|= (1<<AS2); //Kontrollregister setzen //-Taktteilung auf 0 einstellen //-Normaler Zählerbetrieb //-Keine Veränderung von OC0 TCCR2 = (1<<CS20); //DER ZÄHLER LÄUFT JETZT BEREICHTS! //Interrupts setzen //-Interrupt bei Überlauf TIMSK |= (1<<TOIE2); //Globaler sei(); } uint32_t timer_get(){ /* 32,768 kHz => 30,5175781 µs/Takt => 7,8125 ms / TimerOverflow (265Takte) => 7,8125 ms / Tick return (Ticks*7,8125ms/Tick) return (Ticks * 78125ms / 10000 Ticks) return (Ticks * 5^7 ms / (5^4 * 2^4) Ticks) return (Ticks * 5^3 ms / 2^4 Ticks) return (Ticks * 125 ms / 2^4 Ticks) return (Ticks * 125)>>4 (für ms) */ return (timer_overflows * 125)>>4; } //!!!Achtung wenn void timer_wait(uint32_t delay){ uint32_t old_timer_value = timer_get(); uint32_t i; while (old_timer_value + delay > timer_get()); }
Hallo ich nochmal wie kann ich mir eigentlich den Maschinencode anschauen? Hab allerdings davon noch keine Ahnung.
Torsten Müller wrote: > im skript seht: > Lösung für dieses Problem: > -compiler muss variablen vor jedem Zugriff aus dem Speicher laden und > anschließend zurückschreiben. > => attribut volatile > > Aber wenn er den Interruptzugriff wieder rückgängig macht > (timer_overflows++;), dann verzählt der sich doch, oder? Selbstverständlich ist damit gemeint: Der Compiler muss Variablen vor jedem Zugriff aus dem Speicher laden und anschliessend, nach dem die Variable verändert wurden, wieder zurückschreiben. In Summe etwas unglücklich ausgedrückt. volatile teilt dem Compiler eigentlich mit, dass er mit dieser Variablen keinerlei Optimierung machen darf, weil sich diese auf Wegen ändert, die der Compiler nicht einsehen kann. Es geht um diese pathologischen Fälle uint8_t xyz; ... xyz = 0; while( xyz == 0 ) { ... irgendwas in dem xyz nicht vorkommt, zb PORTA = 0x00; PORTA = 0xFF; } in diesem Code gibt es für xyz keine Möglichkeit, wie sich diese Variable innerhalb der Schleife verändern könnte. Der Optimizer könnte daher auf die Idee kommen, sich mal auszurechnen, was das denn für die Schleifenabfrage while( xyz == 0 ) bedeutet: "Wenn sich xyz innerhalb der Schleife nicht ändern kann und xyz vor der Schleife auf 0 gesetzt wird, dann muss xyz offensichtlich Zeit seines Lebens in der Schleife immer 0 sein und die Schleife wird nie abgebrochen. Wenn aber die Schleife sowieso nie abgebrochen werden kann, braucht es auch keinen Test dafür. Ergo ist obiger Code völlig äquivalent zu while( 1 ) { PORTA = 0x00; PORTA = 0xFF; } und da hier die Variable xyz gar nicht mehr zugewiesen oder abgefragt werden muss, läuft dieser Code schneller ab. Die nobelste Aufgabe eines Optimizers ist es, für seinen Programmierer Laufzeit aus dem Code herauszuholen. Ergo werde ich diese Optimierung nehmen" Das es noch einen zweiten Ausführungspfad gibt, in dem xyz über einen Interrupt verändert werden kann, kriegt der Optimizer ganz einfach nicht mit. Mittels volatile bei der Variablen xyz kannst du dem Optimizer einen Strich durch die Rechnung machen. Sie verbietet ganz einfach jegliche Optimierung, die auf einer Analyse der Verwendung von xyz beruht. Statt dessen muss der Wert von xyz jedesmal aus dem Speicher geholt werden, bzw. muss in den Speicher geschrieben werden, wenn das ursprüngliche Programm dies verlangt. Nimm mal folgenden Code: int i; i = 5; i = 7; Warum soll der Compiler die Zuweisung mit 5 ausführen. i wird sofort danach mit 7 überschrieben, die Zuweisung von 5 hat daher keinerlei erkennbare Auswirkung. Wird sie wegoptimiert, so ändert sich am Verhalten von i gar nichts. Nach den beiden Zuweisungen hat i auf jeden Fall den Wert 7. Und jetzt überleg dir mal, was so eine Optimierung wohl für Auswirkungen hätte, wenn sie zb hier zuschlagen würde PORTA = 0x00; PORTA = 0xFF; Genau: DIe Zuweisung von 0x00 an den PORTA wäre komplett sinnlos und könnte wegoptimiert werden. Den nach Ablauf der Sequenz hätte PORTA auf jeden Fall den Wert 0xFF. Das du als Programmierer die erste Zuweisung nicht aus Jux und Tollerei machst, sondern dass du damit einen Puls am PORTA erzeugen willst, kann der Compiler ja nicht wissen. Und jetzt rate mal, wie die 'Variable' PORTA wohl definiert sein wird?
@ Torsten Müller (Gast) >Also volatile braucht man, wenn das Programm und die >Nebenläufigkeit/Interrupt auf die gleichen Daten zugreifen. (Also bei >mehr als 8 bit- Variablen) NEIN! Das ist so nicht richtig. Auch bei 8 Bit wird volatile benötigt! >Aber wenn er den Interruptzugriff wieder rückgängig macht >(timer_overflows++;), dann verzählt der sich doch, oder? ??? Wer sollte den Interruptzugriff rückgängig machen?
1 | volatile static uint32_t timer_overflows; //volatile ist wichtig sonst |
2 | geht die Funktion timer_wait() nicht mehr |
Volatile allein reicht NICHT. Der Zugriff muss auch noch atomar sein. Siehe Interrupt. Und 32 Bit sind auf einem 8-Bit Prozessor ganz sicher nicht atomar. D.h., alle Zugiffe auf timer_overflows audsserhalb der ISR müssen atomar gebaut werden. Siehe Link.
1 | SIGNAL(SIG_OVERFLOW2){ |
2 | //sollte einmal im Jahr (365Tage) zurückgesetzt werden sonst läuft der
|
3 | Zähler über |
4 | //also wenn timer_overflows== 4.036.608.000 ist
|
5 | |
6 | timer_overflows++; |
7 | }
|
- Signal ist veraltet, nutze ISR() - Nutze doch einfach den Vorteiler, dann gibt es weniger Überläufe. Siehe Sleep Mode .
1 | uint32_t timer_get(){ |
2 | /*
|
3 | 32,768 kHz
|
4 | => 30,5175781 µs/Takt
|
5 | => 7,8125 ms / TimerOverflow (265Takte)
|
6 | => 7,8125 ms / Tick
|
7 | return (Ticks*7,8125ms/Tick)
|
8 | return (Ticks * 78125ms / 10000 Ticks)
|
9 | return (Ticks * 5^7 ms / (5^4 * 2^4) Ticks)
|
10 | return (Ticks * 5^3 ms / 2^4 Ticks)
|
11 | return (Ticks * 125 ms / 2^4 Ticks)
|
12 | return (Ticks * 125)>>4 (für ms)
|
13 | |
14 | |
15 | */
|
16 | return (timer_overflows * 125)>>4; |
17 | }
|
Das muss atomar gemacht werden. und zwar so.
1 | uint32_t timer_get(){ |
2 | /*
|
3 | 32,768 kHz
|
4 | => 30,5175781 µs/Takt
|
5 | => 7,8125 ms / TimerOverflow (265Takte)
|
6 | => 7,8125 ms / Tick
|
7 | return (Ticks*7,8125ms/Tick)
|
8 | return (Ticks * 78125ms / 10000 Ticks)
|
9 | return (Ticks * 5^7 ms / (5^4 * 2^4) Ticks)
|
10 | return (Ticks * 5^3 ms / 2^4 Ticks)
|
11 | return (Ticks * 125 ms / 2^4 Ticks)
|
12 | return (Ticks * 125)>>4 (für ms)
|
13 | |
14 | |
15 | */
|
16 | volatile uint32_t tmp; |
17 | cli(); |
18 | tmp = timer_overflows; |
19 | sei(); |
20 | return (tmp*125)>>4; |
21 | |
22 | }
|
MFG Falk
Es gibt viele Leute, die volatile mit Atomar verwechseln. Volatile verhindert aber nur Optimierungen die wg. Unkenntniss des Compilers hinsichtlich Interrupts gemacht werden (aber nicht dürfen). Atomar heißt, dass während der Veränderung eines Wertes (Read/Write) kein Interrupt dazwischenkommen darf.
wow... erst mal danke für eure ausführlichen Antworten. Das muss ich erst mal verdauen... Aber ich glaube das habe ich immer noch nicht ganz verstanden. Das Thema ist nicht ganz einfach oder? @Karl heinz Buchegger >Selbstverständlich ist damit gemeint: >Der Compiler muss Variablen vor jedem Zugriff aus dem Speicher >laden und anschliessend, nach dem die Variable verändert wurden, >wieder zurückschreiben. Aber dann ist doch die Änderung wieder weg?! Und der "Timer" würde nicht mehr richtig sein. (siehe auch nächsten Abschnitt) >>Also volatile braucht man, wenn das Programm und die >>Nebenläufigkeit/Interrupt auf die gleichen Daten zugreifen. (Also bei >>mehr als 8 bit- Variablen) > >NEIN! Das ist so nicht richtig. Auch bei 8 Bit wird volatile benötigt! > >>Aber wenn er den Interruptzugriff wieder rückgängig macht >>(timer_overflows++;), dann verzählt der sich doch, oder? > >??? >Wer sollte den Interruptzugriff rückgängig machen? der Compiler? das habe ich in einem anderen Forum gefunden: >http://www.wer-weiss-was.de/theme9/article92054.html >An volatile-Variablen werden keine Optimierungen ausgeführt, da der >Compiler annimmt, daß sich der Inhalt jederzeit ändern kann, z. B. durch >andere Programme oder (Hardware-)Interrupts. > >Es scheint ja doch ein bisschen Verwirrung um "volatile" zu geben. Dabei >ist es ganz einfach, es ist das Gegenteil von "register"! > >Zur Optimierung von Rechenprozessen versucht der Compiler, so viele Werte >wie moeglich in den Prozessor-Registern zu halten. Das kann unter >Umstaenden in die Hose gehen, weil sich Speicherinhalte auch ohne Wissen >des Compilers aendern koennen (z.B. Timer). Mit "volatile" wird der >Compiler angewiesen, einen Code zu erzeugen, der jedesmal bei Benutzung >der volatile-Variablen direkt auf die Speicheradresse zugreift und diese >niemals in einem Prozessor-Register haelt ... > >cu Stefan. Also volatile verhindert optimierungen - so wie meine schöne Schleife, die dann nicht mehr ging. ich habe in dem "Bericht" >http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html was von "The header ‘signal.h’ declares a type called sig_atomic_t which is guaranteed to be modifiable safely in the presence of asynchronous events. " gefunden. Könnt ihr mir dazu was sagen? dieses atomar... also das brauche ich bei einer 8 Bit maschine (bei Nebenläufigkeit), wenn ich variablen mit mehr als 8 Bit benutze, oder? Aber so wie ich das im meinem Skript verstehe hilft das volatile aber auch bei int (2 Byte?) variablen. Siehe Anhang. Oder habe ich das falsch verstanden? S.24 Sagt aber, dass ich vor kritischen Zugriffen auf gemeinsame Daten Interrupts sperren muss. Aber dann können Interrupts verloren gehen. (->falsche Zeit) jeder Interruptfehler bedeutet 7,8ms. Auf lange Zeit gesehen, eine ganz schön krasser fehler. Dann hilft mir der 32,768 kHz Quarz auch nichts. :-( Ich hänge mal ein Ausschnitt von meinem Script an, bei dem ich ein paar Sachen markiert habe. gibt es bei µC "spezielle atomare Maschinenbefehle" (siehe Skript S.24) Das ist nicht einfach... Vielen dank nochmal für eure hilfe.
@ Torsten Müller (Gast) >Das Thema ist nicht ganz einfach oder? Ja ;-) >>Der Compiler muss Variablen vor jedem Zugriff aus dem Speicher >>laden und anschliessend, nach dem die Variable verändert wurden, >>wieder zurückschreiben. >Aber dann ist doch die Änderung wieder weg?! ??? Variable vom RAM in CPU Register laden Variable in CPU-Registern ändern Variable in den RAM zurückschreiben Klassische Read-Modify-Wrtie Operation. >>??? >>Wer sollte den Interruptzugriff rückgängig machen? >der Compiler? ??? RÜCKGÄNG macht der Compiler GAR nichts. Er kann nur bisweilen feststellen, dass eine Variable nie gelesen wird und damit ungenutzt ist und er sie damit rausschmeisst (bei aktiver Optimierung). >http://www.wer-weiss-was.de/theme9/article92054.html >An volatile-Variablen werden keine Optimierungen ausgeführt, da der >Compiler annimmt, daß sich der Inhalt jederzeit ändern kann, z. B. durch >andere Programme oder (Hardware-)Interrupts. Stimmt. > >Es scheint ja doch ein bisschen Verwirrung um "volatile" zu geben. Dabei >ist es ganz einfach, es ist das Gegenteil von "register"! Stimmt NICHT! >Zur Optimierung von Rechenprozessen versucht der Compiler, so viele Werte >wie moeglich in den Prozessor-Registern zu halten. Das kann unter >Umstaenden in die Hose gehen, weil sich Speicherinhalte auch ohne Wissen >des Compilers aendern koennen (z.B. Timer). Mit "volatile" wird der >Compiler angewiesen, einen Code zu erzeugen, der jedesmal bei Benutzung >der volatile-Variablen direkt auf die Speicheradresse zugreift und diese >niemals in einem Prozessor-Register haelt ... Stimmt fast. Irgendwann muss der Wert mal ins Prozessor-Register, denn nur dort kann damit gerechnet werden. Aber es wird dort nur solange wie unbedingt nötig verweilen. Ein Lesezugriff in verschiedenen C-Anweisungen leist IMMMER WIEDER NEU, verschiedene Schreibzugriffe SCHREIBEN IMMER WIEDER NEU. >Also volatile verhindert optimierungen - so wie meine schöne Schleife, >die dann nicht mehr ging. Genau. >dieses atomar... also das brauche ich bei einer 8 Bit maschine (bei >Nebenläufigkeit), wenn ich variablen mit mehr als 8 Bit benutze, oder? Ja. >Aber so wie ich das im meinem Skript verstehe hilft das volatile aber >auch bei int (2 Byte?) variablen. Siehe Anhang. Oder habe ich das falsch NEIN. Atomar und volatile hängen meist zusammen, sind aber dennoch verschiedene Sachen. Lies die Links nochmal und denk drüber nach. volatile: Verhindert "Wegoptimieren" und Zwischenspeichern von Variablen in CPU-Registern atomar: Verhindert das Unterbrechen eines Varablenzugiffs, der mehrere CPU-Befehle benötigt, weil die Daten breiter als die CPU-Register sind (z.B. 32 Bit auf 8-Bit CPU) >gemeinsame Daten Interrupts sperren muss. Aber dann können Interrupts >verloren gehen. (->falsche Zeit) jeder Interruptfehler bedeutet 7,8ms. NEIN! Die Sperre ist nur SEHR kurz, typisch eine Handvoll CPU-Takte. In der Zeit sollte kein Interrupt verloren gehen. >Auf lange Zeit gesehen, eine ganz schön krasser fehler. Dann hilft mir >der 32,768 kHz Quarz auch nichts. :-( DOCH! Locker bleiben. Ruhe bewahren. Rechnen. Denken. >gibt es bei µC "spezielle atomare Maschinenbefehle" (siehe Skript S.24) Jeder Maschinenbefehl ist atomar. Aber manchmal braucht man zum Zugriff auf grosse Variablen mehrere Maschinenbefehle. In diese Sequenz kann ein Interrupt dazwischenfunken -> SCHLECHT >Das ist nicht einfach... Wenns einfach wäre würde es jeder machen ;-) MFG Falk
@ Torsten Müller (Gast) >Dateianhang: 3.jpg (335,5 KB, 8 Downloads) >Skript 3 Das Eingerahmte oben links ist ein wenig irritierend und bisweilen falsch. Denn WENN Variabeln optimiert in CPU-Registern gespeichert werden, dann MUSS der Compiler auch direkt darauf zugreifen und ausserdem können diese Register nicht mehr für die normalen Berechnungen verwendet werden (logisch!). Und dann werden diese Register auch im Interrupt nicht mehr gesichert. MFG Falk
Falk Brunner wrote: > @ Torsten Müller (Gast) >>Es scheint ja doch ein bisschen Verwirrung um "volatile" zu geben. Dabei >>ist es ganz einfach, es ist das Gegenteil von "register"! > > Stimmt NICHT! So Falsch ist das unter Umständen garnicht. Im Endeffekt heißt "register", dass die Variable ihre ganze Lebenszeit in einem Register verbringt (oder?). Und volatile verursacht (!) eben, dass die Variable ihre ganze Lebenszeit im Hauptspeicher liegt. Allerdings sind nur die Ursachen gegenteilig. Der Sinn, bzw. Zweck beim Einsetzen der Schlüsselwörter hat nämlich keinen Zusammenhang.
Simon K. wrote: > Im Endeffekt heißt "register", dass die Variable ihre ganze Lebenszeit > in einem Register verbringt (oder?). Register hiess mal, dass der Compiler, so er kann und will, das in ein Register packen sollte. Aber eigentlich heisst das heute garnichts mehr. Vor 3 Jahrzehnten hatten C-Compiler oft keine selbstständige Registeroptimierung. Ist heute anders und dementsprechend sinnlos ist das geworden. > Und volatile verursacht (!) eben, > dass die Variable ihre ganze Lebenszeit im Hauptspeicher liegt. Praktisch meist ja, theoretisch nicht. Immerhin sind auch I/O-Register üblicherweise "volatile", liegen aber nicht im Hauptspeicher. Das heisst nur, dass die Variable bei jedem Zugriff aus Sicht des Programmierers zu ebendiesem Zeitpunkt von dort geholt werden soll / darin reingeschrieben werden soll, wo diese Variable auch aus Sicht anderer Funktionen liegt. Grundsätzlich könnte ein Compiler auch eine "volatile" Variable in ein Register legen, vorausgesetzt sie läge aus Sicht jeder sie benutzenden Funktion permanent in eben diesem Register. Also in einem globalen Register. PS: Zu jener Zeit, zu der "register" noch eine Bedeutung hatte, existierte "volatile" noch garnicht. Weil mangels Optimierung unnötig. Als die Optimierung kam und "volatile" nötig wurde, wurde "register" unnötig.
Ich bedanke mich ganz herzlich für eure Hilfe. Liebe Grüße Torsten
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.