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
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
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
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.