Hallo, habe mir kürzlich ein STK500 besorgt und spiele jetzt ein bisschen damit herum. Ich benutze einen ATMega8, der vom internen RC auf 8MHz getaktet wird. Jetzt wollte ich erstmal zum Üben eine Uhr programmieren. Dazu benutze ich den Timer2 des Mega8 im CTC-Modus. Analog zum Tutorial habe ich erstmal folgenden Code (angepasst auf Mega8 und 8MHz): #include <avr/io.h> #include <avr/interrupt.h> volatile unsigned int millisekunden=0; volatile unsigned int sekunde=0; volatile unsigned int minute=0; volatile unsigned int stunde=0; int main(void) { TCCR2 |= (1<<2) | (1<<3); //CTC-Modus, Prescaler=64 -> Takt=125000 OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6);//=125->1ms TIMSK |= (1<<7); // Compare Match Interrupt aktiviert sei(); { //Hier kann z.B. eine Ausgabe stattfinden } } ISR (TIMER2_COMP_vect) { millisekunden++; if(millisekunden==1000) { sekunde++; millisekunden=0; if(sekunde==60) { minute++; sekunde=0; } if(minute ==60) { stunde++; minute=0; } } } Das funktioniert auch gut, aber jetzt meine Frage: Sollte man nicht besser die Berechnung von Sekunden, Minuten und Stunden aus der ISR auslagern? Also vielleicht so: #include <avr/io.h> #include <avr/interrupt.h> volatile unsigned int millisekunden=0; unsigned int sekunde=0; unsigned int minute=0; unsigned int stunde=0; int main(void) { TCCR2 |= (1<<2) | (1<<3); //CTC-Modus, Prescaler=64 -> Takt=125000 OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6);//=125 -> 1ms TIMSK |= (1<<7); // Compare Match Interrupt aktiviert sei(); { if(millisekunden==1000) { sekunde++; millisekunden=0; if(sekunde==60) { minute++; sekunde=0; } if(minute ==60) { stunde++; minute=0; } } //Hier kann z.B. eine Ausgabe stattfinden } } ISR (TIMER2_COMP_vect) { millisekunden++; } Ich habe immer gedacht, dass eine ISR so kurz wie möglich sein sollte. Danke im Vorraus. Gruß Paul
:
Verschoben durch Moderator
Paul schrieb: > Das funktioniert auch gut, aber jetzt meine Frage: Sollte man nicht > besser die Berechnung von Sekunden, Minuten und Stunden aus der ISR > auslagern? Jein. Die wenigen Vergleichsoperationen erzeugen nur wenig Code und sind sehr rasch abgearbeitet. In deinem Fall hast du sogar das Problem von race conditions. Was ist, wenn du in main() den Moment verpasst, in welchem die Variable "millisekunden" genau den Wert 1000 hat? > Ich habe immer gedacht, dass eine ISR so kurz wie möglich sein sollte. Gute Faustregel. > TCCR2 |= (1<<2) | (1<<3); //CTC-Modus, Prescaler=64 -> Takt=125000 > OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6);//=125 -> 1ms > TIMSK |= (1<<7); // Compare Match Interrupt aktiviert Verwende unbedingt die vordefinierten Symbole, wenn du Bits/Flags setzt! Das macht den Code besser lesbar und portabler. Oder weisst du in 10 Tagen noch, welche Funktion Bit 5 des OCR2 auf deinem MP Variante XY hat? ;) > Danke im Vorraus. Ist das eigentlich die neue Rechtschreibung, "vorraus"? Lese ich immer wieder mal... Viel Spass mit deinem neuen Hobby. :)
@ Paul (Gast) > OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | >(1<<6);//=125->1ms Was soll das denn? Solche Zahlen schreibt man direkt hin. Die (1<<n) Schreibweise nimmt man nur für Steuerregister, in denn die einzelnen Bits einzelne Funktionen haben. > TIMSK |= (1<<7); // Compare Match Interrupt aktiviert Ausserdem nimmt man die symbolischen Bitnamen, das erleichtet das Lesen deutlich. Also eher (1 << OCIE2). >Das funktioniert auch gut, aber jetzt meine Frage: Sollte man nicht >besser die Berechnung von Sekunden, Minuten und Stunden aus der ISR >auslagern? Also vielleicht so: Kann man machen, ist hier aber nicht nötig. Die paar Vergleiche und Rechenoperationen sind OK. >Ich habe immer gedacht, dass eine ISR so kurz wie möglich sein sollte. Ja, man muss es aber nicht übertreiben. MfG Falk
Danke Tom für die schnelle Hilfe, das Stichwort "race conditions" war was mir gefehlt hat. Ich glaube ich habs jetzt kapiert. Gruß Paul
Grundsätzlich hast du Recht mit den möglichst kurzen ISRs. Im Detail hat deine 2. Lösung mehrere Haken, die man berücksichtigen muss: 1. Du musst in main() noch für atomaren Zugriff auf millisekunden sorgen. siehe Artikel Interrupt 2. Das Abprüfen auf exakt 1000 kann schief gehen. Du kannst nicht davon ausgehen, dass du pro Schleifendurchlauf in while immer eine Erhöhung von millisekunden um max. 1 hast. 3. Was machst du mit den millisekunden, die vielleicht von der ISR zwischen der Abfrage (if(millisekunden==1000)) und dem Reset (millisekunden=0) schon gezählt wurden? 4. Die erste Lösung ist praktischer, wenn im Restprogramm stunde, minute, sekunde ebenfalls benötigt werden. Die Routinen dort können dann direkt darauf zugreifen und brauchen nicht auf eine Bereitstellung durch main() zu hoffen.
Falk Brunner schrieb: > Was soll das denn? Solche Zahlen schreibt man direkt hin. Die (1<<n) > Schreibweise nimmt man nur für Steuerregister, in denn die einzelnen > Bits einzelne Funktionen haben. Hallo Falk, also einfach den hexadezimalen bzw. binären Wert da rein schreiben? Ok werd ich in Zukunft so machen. Falk Brunner schrieb: > Ausserdem nimmt man die symbolischen Bitnamen, das erleichtet das Lesen > deutlich. Also eher (1 << OCIE2). Das meinte Dein Vorredner auch schon, auch das werde ich in Zukunft beherzigen. Danke Paul
Hallo Stefan, auch dir vielen Dank. Jetzt ist die Sache schon klarer. Gruß Paul
Bei der Berechnung des Wertes für OCR2 solltest du noch mal im aktuellen Tutorial nachsehen (AVR-GCC-Tutorial/Die Timer und Zähler des AVR). Dort ist letztens ein Bugfix reingekommen. Um 125 Zählerschritte abzupassen, muss man den OCR2 Wert auf 125-1 setzen.
Wenn es um eine Uhr geht, die im Sekundentakt hochzählt, würde man, um die ISR und die CPU-Belastung möglichst kurz zu halten, etwas anders vorgehen. Voraussetzung ist, daß die genaue Millisekunde nicht benötigt wird. In der ISR läuft ein Zähler hoch, und nach Ablauf einer Sekunde wird ein Flag gesetzt, welches dann im Hauptprogramm die Uhr eine Sekunde weiterzählen lässt (Stunde, Minute und Sekunde). Die ISR muß dabei gar nicht im Millisekundentakt laufen, der langsamste Takt, der restfrei eine Sekunde ergeben kann, reicht völlig aus. Falls an mehreren Stellen im Hauptprogramm eine (fast) millisekundengenaue Uhrzeit benötigt wird, muß die Uhrzeit vor direkt diesen Stellen halt aktualisiert werden. Oliver
Stefan B. schrieb: > Dort ist letztens ein Bugfix reingekommen. Um 125 Zählerschritte > abzupassen, muss man den OCR2 Wert auf 125-1 setzen. Hallo Stefan, gilt der Bugfix für alle Atmel 8-bit MCs oder nur bei Verwendung eines bestimmten Prescalers oder generell für AVR-GCC oder ... ??? Das hat mich schon beim lesen etwas verwirrt, daher hab ich das mal einfach ignoriert. Gruß Paul
Hallo Oliver, hier wird man ja mit Antworten geradezu überschüttet! Auch deinen Beitrag finde ich sehr hilfreich! Für meine Anwendung (Fahrradcomputer) sollte eine 1/100 Sekunde locker ausreichen. Allerdings weiß ich noch nicht welche Frequenz mein Nabendynamo so erzeugt. Ich stecke eben noch in den Anfängen. Und ja, ich weiß, dass es so ein Projekt schon gibt. Ich will eben etwas lernen, und nicht einfach fremden Code übernehmen. Dank und Gruß Paul
Hallo, schau ins Datenblatt des AVR nach der Formel. Der Fehler war im Tutorial. Zu den Zeiten: bei läuft oft ein 10ms Interrupt, der dann auch Tastenentprellung/DCF77-Dekodierung/Timeouts o.ä. übernimmt, irgendwas wird da ohnehin immer gebraucht. Da läuft dann auch gleich die Uhr mit, zählt eben bis 100 für eine Sekunde. Gruß aus Berlin Michael
Paul schrieb: > Stefan B. schrieb: >> Dort ist letztens ein Bugfix reingekommen. Um 125 Zählerschritte >> abzupassen, muss man den OCR2 Wert auf 125-1 setzen. > > Hallo Stefan, > gilt der Bugfix für alle Atmel 8-bit MCs oder nur bei Verwendung eines > bestimmten Prescalers oder generell für AVR-GCC oder ... ??? > Das hat mich schon beim lesen etwas verwirrt, daher hab ich das mal > einfach ignoriert. Er gilt für alle. Der Timer soll nach jeweils 125 Takt-Zählungen einen Interrupt auslösen. Da er aber bei 0 zu zählen anfängt, muss der Vergleich logischerweise auf 124 erfolgen. Denn von 0 bis 124 sind es 125 Zählungen. Wenn du zählst 0, 1, 2, 3 dann hast du 4 Zahlen gezählt.
Michael U. schrieb: > schau ins Datenblatt des AVR nach der Formel. Der Fehler war im > Tutorial. Hallo Michael, im Datenblatt zum Mega8 hab ich nach kurzer Durchsicht nichts dazu gefunden. Was meinst du mit:"Der Fehler war im Tutorial."? a): Das Tutorial war fehlerhaft oder b): Der Fehler wurde im Tutorial erwähnt.? Paul
Danke Karl, jetzt bin ich schlauer. Da hätte ich eigentlich auch selbst drauf kommen können. Gruß Paul P.S. @ Michael: hat sich erledigt, Danke euch allen!
Bugfix heisst, das ist eine Korrektur des Beispielcodes, der vorher dort im Tutorial gestanden hat (ohne -1). Die Logik wie man OCRx im CTC-Modus belegt, gilt für alle 8-Bit AVRs. Schau dir die Timing Diagramme im Datenblatt an und achte darauf, wann der Interrupt ausgelöst wird und welche Werte der Zähler TCNTx dann hat. Spiele Mit Papier und Bleistift µC. Wenn du z.B. OCR auf 4 setzt, dann wird der Interrupt beim Übergang von TCNT=4 auf TCNT=4+1 ausgelöst. Dabei wird TCNT im CTC-Modus auf 0 gesetzt. An Schritten sind - in dem Moment in dem das Interruptflag gesetzt wird - abgearbeitet: Die Schritte bei denen TCNT die Werte 0,1,2,3,4 hatte. Das sind 5 Schritte. Wenn du vom Programm her also X Schritte brauchst, musst du OCR mit X-1 belegen.
@ Paul (Gast) >also einfach den hexadezimalen bzw. binären Wert da rein schreiben? Ok >werd ich in Zukunft so machen. Auch dezimale Zahlen sind in dem Fall vollkommen OK. MfG Falk
> if(millisekunden==1000) > { > sekunde++; > millisekunden=0; Kann man das nicht besser so schreiben? > if(millisekunden>1000) > { > sekunde++; > millisekunden %=1000; //auf 999 bregrenzen Wenn die Main Schleife erst bei Wert 1004 zum abfragen kommen sollte, dann wird die Sekunde trotzdem hochgezählt, und als Rest bleibt der Wert "5" in der Variable millisekunden.
Igor Ebner schrieb: > Kann man das nicht besser so schreiben? Im Prinzip ja. Kommt drauf an an welcher Stelle. Wenn das Weiterzählen der Millisekunden in der main Schleife passiert: Ja dann kann es natürlich sein, dass Millisekunden über 1000 drüber geht. In einer ISR, unmittelbar nach Erhöhen von Millisekunden kann das aber nicht passieren. >> if(millisekunden>1000) >> { >> sekunde++; >> millisekunden %=1000; //auf 999 bregrenzen Besser while( millisekunden > 999 ) millisekunden -= 1000; Divisionen (und damit auch Restbildung) sind auf einem AVR ziemlich teuer. Die willst du nicht wirklich haben, wenn es eine andere Möglichkeit gibt. Und im Regelfall wird praktisch immer eine Subtraktion reichen. Ich würde die Erhöhung der Uhr in der ISR lassen. Das vereinfacht die Situation indem die Funktionseinheit 'Uhr' als Ganzes im restlichen Programm immer als in sich konsistente Einheit angesehen werden kann, die keiner weiteren Betreuung bedarf. Die Uhr tickt ganz von alleine vor sich hin ohne den Prozessor in der ISR großartig zu belasten. Vor Auslesen der Uhr, wie bei allen Datentypen die aus mehreren Bytes bestehen, die Interrupts sperren, danach wieder aktivieren.
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.