Problem:
So funktioniert es nicht. Der Compiler sagt nix. Der Simulator muckt
nicht. Der Timer läuft. Die sec_takt Variable wird wie erwartet
inkrementiert. Die LED auf PINB1 blinkt im 100ms-Takt ( naja - in etwa
jedenfalls ). Also läuft sowohl der Interrupt, als auch die Main-While.
Nur funktioniert:
1
if(sec_takt==0){
2
sbi(BEEP_PORT,BEEP_PIN);
3
}
4
5
if(sec_takt>=1){
6
sec_takt=0;
7
cbi(BEEP_PORT,BEEP_PIN);
8
}
zwar in der ISR ( oder einer von ihr aufgerufenen Routine ). Aber nicht
in der Main-While.
Das verstehe ich nicht. Die sec_takt ist global und der Compiler sagt
mir auch nix, in keiner der Optimize-Stufen.
Bitte helft mir. Sicher ist es ein Denkfehler.
Vielen Dank für eure Mühe!
Peter II schrieb:> variabeln die in der ISR und main verwendet werden müssen volatile sein.
MÜSSEN? Seit wann? Habe bei mir in einer ISR div. globale Variabeln als
normale int deklariert und funzt trotzdem (benutze sie auch in der
main).
Gruss
Gordon
@ Matthias T. (goldencue)
>Tatsache. Danke danke danke! Ich weiss wofür volatile ist. Aber das es>hier greift konnte ich mir nicht vorstellen.
Jetzt weißt du es. Es ist aber immer noch ein Fehler drin. Dein
Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.
Und NEIN, nur weil es 24h ohne Fehler läuft ist das KEIN Beweis, dass es
OK ist, siehe Artikel.
Und lass den Käse mit dem Timer laden, das geht mit CTC beium AVR
deutlich besser.
@ gordon51freeman (Gast)
>> variabeln die in der ISR und main verwendet werden müssen volatile sein.>MÜSSEN? Seit wann?
Seit es C gibt.
> Habe bei mir in einer ISR div. globale Variabeln als>normale int deklariert
Kaum, denn das wären lokale Variablen, an die man ausserhalb der ISR gar
nicht rankommt.
> und funzt trotzdem (benutze sie auch in der main).
Dann sit es Glück, dass der Optimizer das nicht soweit optimiert hat,
dass es nicht mehr geht. Und nein, das ist kein Compilerfehler sondern
ein Programmiererfehler.
Falk Brunner schrieb:> Seit es C gibt.
Erst seit es ANSI-C gibt. Davor gab es kein volatile. Weil die Compiler
noch nicht so agressiv optimierten war es anfangs nicht erforderlich.
Falk Brunner schrieb:> Dein> Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.
das stimmt. Ich hatte nur den Text minimiert und alle nicht zwingend
fürs Problem notwendigen Zeilen entfernt. Danke aber für den Tip.
Falk Brunner schrieb:> Jetzt weißt du es. Es ist aber immer noch ein Fehler drin. Dein> Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.
welchen zugriff meinst du überhaupt?
sys_ms_Inkr wird doch nur in der ISR verwendet?
@ Peter II (Gast)
>> Jetzt weißt du es. Es ist aber immer noch ein Fehler drin. Dein>> Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.>welchen zugriff meinst du überhaupt?
if(sec_takt == 1){
gordon51freeman schrieb:> Peter II schrieb:>> variabeln die in der ISR und main verwendet werden müssen volatile sein.>> MÜSSEN? Seit wann?
Eigentlich erst, seit dem es Compiler gibt, deren Optimizer
Datenflussanylse betreiben.
Wenn man C naiv in Maschinencode übersetzt, braucht man es nicht. Aber
so einen Compiler würde keiner haben wollen.
> Habe bei mir in einer ISR div. globale Variabeln als> normale int deklariert und funzt trotzdem (benutze sie auch in der> main).
Dann hast du noch nicht verstanden, wo das Problem liegt.
FAQ: Was hat es mit volatile auf sich
Falk Brunner schrieb:> @ Peter II (Gast)>>>> Jetzt weißt du es. Es ist aber immer noch ein Fehler drin. Dein>>> Variablenzugiff ist NICHT atomar, muss er aber, siehe Interrupt.>>>welchen zugriff meinst du überhaupt?>> if(sec_takt == 1){
uint8_t sec_takt;
damit ist es doch immer atomar, oder kommt gcc auf die idee die bits
einzeln zu setzen?
hier stehts:
Variablen, auf die sowohl innerhalb wie auch außerhalb einer
Interruptserviceroutine zugegriffen wird (schreibend oder lesend),
müssen (ähnlich wie Hardwareregister) mit dem Schlüsselwort volatile
(flüchtig) versehen werden, damit der C-Compiler berücksichtigen kann,
dass diese Variablen jederzeit (durch das Auftreten des Interrupts)
gelesen oder geschrieben werden können. Ansonsten würde der C-Compiler
das regelmäßige Abfragen oder Beschreiben dieser Variablen ggf.
wegoptimieren, da er nicht damit rechnet, dass auf die Variable auch
"ohne sein Zutun" zugegriffen wird.
Zitat Gcc-Tur
:)
und atomar bedeutet, das ich jegliche "Bearbeitungen" von Interrupts
z.B. unterbinden muss, WÄREND ich auf die Variable zugreife oder sie
verändere. Wenn ich also if ( sec_takt == 1) abfrage, könnte ( bei nicht
atomar ) der Interrupt den Wert der Variable ändern WÄREND ich auslese.
Und das führt zu sporadischen Laufzeitfehlern.
Matthias T. schrieb:> und atomar bedeutet, das ich jegliche "Bearbeitungen" von Interrupts> z.B. unterbinden muss, WÄREND ich auf die Variable zugreife oder sie> verändere. Wenn ich also if ( sec_takt == 1) abfrage, könnte ( bei nicht> atomar ) der Interrupt den Wert der Variable ändern WÄREND ich auslese.> Und das führt zu sporadischen Laufzeitfehlern.
das ist halt das Problem wenn man text zitiert und nicht versteht was
der grund ist.
Das gilt hier nicht für 8bit variablen ( bzw. für alle Variabeln die die
CPU direkt verarbeiten kann)
Matthias T. schrieb:> das war nicht zitiert
dann hast du dir es falsch gemerkt.
> ändern WÄREND ich auslese.> Und das führt zu sporadischen Laufzeitfehlern.
es kommt auch zu keinen laufzeitfehler sonder der verhalten ist nicht
das was man erwartet.
eine 16bit variable wird auf einem 8bit system in 2 oder mehr schritten
geändert, damit gibt es ungwollte zwischenwerte. Beim Vergleichen kann
es dann zu unerwarteten ergebnissen kommen.
Bei einer 8bit Variable kann das hier aber nicht passieren.
Anderes währe wenn in der main etwas wie
wert++;
und in der isr auch
wert++
gemacht wird, dann können dabei schritte verloren gehen. Aber hier wird
in der main nur auf 0 gesetzt und in der isr ++ gemacht. Dabei kann kein
Fehler entstehen.
@ Peter II (Gast)
>uint8_t sec_takt;>damit ist es doch immer atomar,
Ahhh stimmt, Tomaten auf den Augen. sys_ms_Inkr ist ja nur 16 Bit.
>oder kommt gcc auf die idee die bits>einzeln zu setzen?
Eher nicht.
>gemacht wird, dann können dabei schritte verloren gehen. Aber hier wird>in der main nur auf 0 gesetzt und in der isr ++ gemacht. Dabei kann kein>Fehler entstehen.
Stimmt.
kannst du mir einen Tip geben, was du mit:
Falk Brunner schrieb:> Und lass den Käse mit dem Timer laden, das geht mit CTC beium AVR> deutlich besser.
meintest?
Matthias T. schrieb:> kannst du mir einen Tip geben, was du mit:>> Falk Brunner schrieb:>> Und lass den Käse mit dem Timer laden, das geht mit CTC beium AVR>> deutlich besser.>> meintest?
Siehe
*** FAQ: Timer ***
Dein Timer kann Modi! Der kann das alles auch ganz von alleine.
Matthias T. schrieb:> aber das habe ich! Ich habe den Compare Match Mode genutzt
Wo genau hast du den Compare Match Mode benutzt?
> immer nur bis
1
TCNT0=256-CM_Val;
zählen.
genau das nennt sich Timer vorladen.
Und ist Quatsch. Das kann der Timer im CTC viel besser. Nämlich
taktgenau.
>> Was meintest du genau?
Lies den Link. Ich hab mir nicht umsonst die Mühe gemacht, da zumindest
die häufigsten Timer Sachen im Detail runterzubrechen.
Matthias T. schrieb:> System: ATMEGA8, STK500, AVR-Studio4, Gcc-CompilerKarl Heinz Buchegger schrieb:> Und ist Quatsch. Das kann der Timer im CTC viel besser. Nämlich> taktgenau.
Aber nicht der, den er gerade benutzt. Das kann die alte Gurke im Timer0
nicht, sondern nur in 1 und 2.
mfg.
Peter II schrieb:> uint8_t sec_takt;>> damit ist es doch immer atomar, oder kommt gcc auf die idee die bits> einzeln zu setzen?
Weißt du denn, ob deine Architektur uint8_ts atomar inkrementiert? Weißt
du, ob dein Compiler die passende Instruktion aufruft?
Vielleicht ja. Aber portabel ist das dann dennoch nicht.
Sascha schrieb:> Aber portabel ist das dann dennoch nicht.
Das wird er ohnehin nie sein können. Auf eine Maschine mit
beispielsweise 4-Bit breiten Datenoperationen ist ein unvermeidlich
Eigenschaften eines bestimmten AVR Controllers voraussetzender Code
prinzipiell nicht portabel.
> Weißt du denn, ob deine Architektur uint8_ts atomar inkrementiert? Weißt> du, ob dein Compiler die passende Instruktion aufruft?
Entweder er arbeitet byteweise, dann ist die Inkrementierung in einer
normalen AVR-ISR atomar, oder der Compiler wurde nur geschrieben, um
Anwender in den Irrsinn zu treiben.
Thomas Eckmann schrieb:> Matthias T. schrieb:>> System: ATMEGA8, STK500, AVR-Studio4, Gcc-Compiler>> Karl Heinz Buchegger schrieb:>> Und ist Quatsch. Das kann der Timer im CTC viel besser. Nämlich>> taktgenau.> Aber nicht der, den er gerade benutzt. Das kann die alte Gurke im Timer0> nicht, sondern nur in 1 und 2.
Ah. Hast du recht.
Peter II schrieb:> Bei einer 8bit Variable kann das hier aber nicht passieren.
Das unterstellt aber, dass der Compiler immer passenden Maschinencode
erzeugt.
Auch wenn es heute so ist, ich würde mich nicht darauf verlassen.
Grüße
Stefan
Stefan Wagner schrieb:> Auch wenn es heute so ist, ich würde mich nicht darauf verlassen.
man sollte halt sein hardware kennen, sonst kann man überhaupt nicht
sinnvoll programmieren.
Nur ein cli sli ringrum würde schon 200% overhead bedeuten, das muss nun
wirklich nicht sein.
Stefan Wagner schrieb:> Das unterstellt aber, dass der Compiler immer passenden Maschinencode> erzeugt.
Was soll er denn sonst machen? Unpassenden Code erzeugen?
Man kann Probleme, die keine sind, auch an den Haaren herbeiziehen.
mfg.
die Diskussion hat sich hier offenbar zu ner öffentlichen Debatte
entwickelt :)
Mir soll es recht sein. Ich entwickle als Hobby ( muss also nicht alles
im Detail wissen wie die Vollpros ). Allerdings entwickle ich nur AVR
und habe nicht vor zu portieren.
Ich finde eure Unterhaltung sehr lehrreich und verfolge mit großem
Interesse weiter. Lasst euch nicht stören :)
Thomas Eckmann schrieb:> Was soll er denn sonst machen? Unpassenden Code erzeugen?
Ja.
Irgendwann unterzieht jemand den Compiler einem Redesign, und schon ist
die Annahme nicht mehr erfüllt.
Oder der Code wird auf eine andere Zielplattform portiert. Oder...
Wär mir zu blöd, plötzlich an "Altbaustellen" Fehler suchen zu müssen.
Grüße
Stefan
Stefan Wagner schrieb:> Thomas Eckmann schrieb:>> Was soll er denn sonst machen? Unpassenden Code erzeugen?>> Ja.>> Irgendwann unterzieht jemand den Compiler einem Redesign, und schon ist> die Annahme nicht mehr erfüllt.
Ähm, wir reden über ein Inkrement IN EINER ISR.
DIe ist per Def. atomar, solange die Interrupts nicht explizit innerhalb
der ISR aufgedreht werden.
Ausserhalb einer ISR muss ein
i++;
nicht atomar sein. Soweit hast du schon recht.
Ich glaube, ihr redet aneinander vorbei.
Stefan redet vom allgmeinen Fall und der Rest von einem Inkrement in
einer ISR
Karl Heinz Buchegger schrieb:> Stefan redet vom allgemeinen Fall
So ist es! Ich hatte angenommen, dass es um den Code außerhalb der ISR
geht.
Grüße
Stefan
Thomas Eckmann schrieb:> Ja nee is' klar.
Wahrscheinlich verwendet er grad einen Multiprozessor-AVR mit
gemeinsamem Speicher, per FPGA dem Mega8 exakt nachempfunden. Wenn dann
mehrere seiner Cores auf die Variable zugreifen, dann ists Essig mit
atomarer Inkrementierung in der ISR. ;-)
Wer es gerne offiziell hat: C99 definiert via signal.h einen atomaren
Datentyp sig_atomic_t, der entweder mindestens -127..+127 oder
mindestens 0..255 umfasst. Wer es also auf maximale Portabilität anlegt,
der verwendet diesen Typ und hat damit definiert atomaren Zugriff
(freilich keine atomare Inkrementierung).
Thomas Eckmann schrieb:> Ja nee is' klar.
Jetzt mal halblang. Für einen derart sarkastischen Ton sehe ich keinen
sachlichen Grund.
Ich redete über allgemeinen C-Code, nicht über den Sonderfall einer
definitiv nicht unterbrechbaren ISR auf einer ganz bestimmten CPU.
Grüße
Stefan
Stefan Wagner schrieb:> Ich redete über allgemeinen C-Code, nicht über den Sonderfall einer> definitiv nicht unterbrechbaren ISR auf einer ganz bestimmten CPU.
Ach so.
mfg.
Matthias T. schrieb:> if(sec_takt >= 2){> sec_takt = 0;
ich stelle mir hier vor dass hier trotz 8-Bit Variable auch der
Interrupt gesperrt werden muss.
Sonst kann doch zwischen Abfrage und Nullsetzen der Interrupt kommen und
so theoretisch eine Sekunde verloren gehen. (Praktisch dann evtl. nicht
unter der Voraussetzung dass die main-Schleife schnell genug durchlaufen
wird)
Oder sehe ich das falsch?
ich kann lange nicht mehr viel dazu sagen. Aber ich finde die Diskussion
hier wirklich toll. Auch wenn's hart zur Sache geht ;)...in anderen
Threads steht sooviel Genörkel und Geningel.
Ich les gern weiter zu :) ...nur zu
Walter S. schrieb:> Sonst kann doch zwischen Abfrage und Nullsetzen der Interrupt kommen und> so theoretisch eine Sekunde verloren gehen. (Praktisch dann evtl. nicht> unter der Voraussetzung dass die main-Schleife schnell genug durchlaufen> wird)> Oder sehe ich das falsch?
ja und nein, die sekunde geht auch denn verloren wenn die main zu lange
braucht. das muss nicht zwischen abfrage und 0 setzen passieren. Und
diesen Fall verhindert auch ein atomic nicht.
Walter S. schrieb:> da hast Du recht,> dann also doch die altbewährte Methode, nämlich Zähler nur im IRQ> verändern, im main nur abfragen.
nein das hilft hier auch nicht.
man müsste ihn nicht auf 0 setzen sonder 2 abziehen;
sec_takt -= 2;
jetzt hat man sich damit aber wirklich ein atomar problem eingehandelt.
Da aber selbst ISR verloren gehen können, weil sie auch nur ein flag
haben spielt das überhaupt keine rolle in der Praxis. Man muss es nur
wissen und dafür sorgen das die main schnell genug ist.
spontan schrieb:> Oder in ISR nur Flags setzen und in der Main die Zähler bedienen.
Wozu dann eine ISR, gleich das Interruptflag in Main auswerten.
MfG Klaus
@ Walter S. (avatar)
>> if(sec_takt >= 2){>> sec_takt = 0;>ich stelle mir hier vor dass hier trotz 8-Bit Variable auch der>Interrupt gesperrt werden muss.>Sonst kann doch zwischen Abfrage und Nullsetzen der Interrupt kommen und>so theoretisch eine Sekunde verloren gehen. (Praktisch dann evtl. nicht>unter der Voraussetzung dass die main-Schleife schnell genug durchlaufen>wird)
Die MUSS so oder so schnell genug sein, dass zwischen zwei Veränderungen
von sec_takt das Ganze einmal abgearbeitet wird. Sonst funktioniert es
nicht, mit oder ohne atomaren Zugriff.
Allerding gibt es Konstellationen, wo es atomar sein muss,
beispielsweise bei einem Software-FIFO, dort werden ja Lese- und
Schreibzeiger sowie der Füllstand manipuliert.
>dann also doch die altbewährte Methode, nämlich Zähler nur im IRQ>verändern, im main nur abfragen.
Das ist natürlich noch besser, vor allem wenn die Programme größer
werden. Wenn da an mehreren Stellen eine Schreibzugriff auf solche
Variablen gemacht wird, hat man schnell Chaos. UNd wenn man in der ISR
dann direkt für die jeweiligen Ereignisse die Flags schon ausdekodiert
und setzt, wird sowohl die Auswertung als auch das Handshake minimiert
und gesichert. Denn dann kann z.B. die ISR prüfen, ob das FLag, das
gerade gesetzt werden soll, schon gelöscht ist. Wenn nein, ist die
main-Schleife zu lang oder irgendwo hängen geblieben und man kann einen
Fehler ausspucken. Optimale Laufzeitprüfung!