Hallo zusammen, ich versuche gerade, Timer1 eines Atmega 32 Microcontrollers zu programmieren, so dass dieser jede Millisekunde einen SIG_OUTPUT_COMPARE1A Interrupt auslöst. Der Controller ist mit 16 Mhz getaktet. Ich probiere im Moment eine kleine Uhr zu programmieren (naja, eigentlich nicht ... aber ich musste mein Programm in ein Uhrenprogramm umwandeln, nachdem ich Unstimmigkeiten festgestellt habe). Mein Plan war folgender: Ich lasse den Controller jede Millisekunde den Interrupt triggern und zähle dann in der Interrupt Routine bis 1000, um eine Sekunde zu bekommen. Doch leider musste ich feststellen, dass meine Sekundenanzeige schon nach 2 Minuten Laufzeit um 2 Sekunden von der Funkuhr abweicht und das ist definitiv nicht normal. Dass der Quarz korrekt/genau funktioniert habe ich festgestellt, als ich Timer0 verwendet hatte bzw einen anderen "Weg". Da ging die Uhr nach 10 Std Laufzeit um 2 Sekunden nach, was natürlich kein Problem ist. Aber hier mal mein Code: void timer_init(void) { /* 0,001s * 16.000.000 Hz = 16000; 16000 / 64 => Prescaler = 250 to get an interrupt every 1ms */ OCR1A = 250; /* Set timer 1 to compare mode */ TIMSK = _BV(OCIE1A); /* Reset the timer */ TCNT1 = 0; /* Prescaler set to 64 & reset to 0 after triggering the interrupt */ TCCR1B = _BV(CS10) | _BV(CS11) | _BV(WGM12); sei(); } SIGNAL(SIG_OUTPUT_COMPARE1A) { extern void timer_clock(void); extern void display_print(void); static volatile uint16_t counter; if(++counter == 1000) { counter = 0; timer_clock(); display_print(); } } Da das eigentlich kein Uhrenprogramm sein sollte und ich es ohne viele Handgriffe Umsticken wollte, bitte nicht wundern über den komischen Code ;) Was hier passieren soll: Alle 1ms Interrupt auslösen bei 16 Mhz heißt bei einem Prescaler von 64, dass ich bis 250 hochzählen muss, bevor ein Interrupt ausgelöst werden darf. Leider stimmt nach 2 Minuten der Wert schon um 2 Sekunden nicht mehr. timer_clock erhöht einfach einen Sekundenzähler und berechnet Std/Mins bzw deren Überläufe bei 60 und 24. display_print ... naja, zeigt mir die Uhr auf nem Display an. Im Grunde kann ich mit der Verzögerung leben. Mich würde aber dennoch interessieren, ob ich nicht eher einen Fehler im Quellcode habe (oder einen Denkfehler bei der Berechnung vom OCR1A Wert). Mich wundert diese riesen Abweichung innerhalb von 2 Minuten. Oder liegt es vielleicht dadran, dass jede ms Interrupts auslösen nicht so wirklich praktikabel ist? Mit der Forensuche habe ich schon geschaut, aber Fragen zu "Hilfe meine Uhr weicht in 200 Stunden um 1,535 Sekunden ab" treffen leider nicht so ganz auf meine Problembeschreibung zu, aber vielleicht habe ich auch einen hilfreichen Thread übersehen :) Wie gesagt, mit der Verzögerung kann ich leben, aber es würde mich einfach nur interessieren, ob ich nicht doch was falsch mache im Code.
Benutz doch mal einen Timer-Interrupt. Und wenn Du einen 16-Bit-Timer verwendest kannst Du ihn gleich so konfigurieren dass er jede Sekunde ausloest, passender Quarz vorausgesetzt.
@ feig (Gast) >Interrupt ausgelöst werden darf. Leider stimmt nach 2 Minuten der Wert >schon um 2 Sekunden nicht mehr. Ganz schön viel. >timer_clock erhöht einfach einen Sekundenzähler und berechnet Std/Mins >bzw deren Überläufe bei 60 und 24. display_print ... naja, zeigt mir die >Uhr auf nem Display an. Wenn die Routine länger als 2ms dauert, verschluckst du jede Sekunde einen Interrupt. >Im Grunde kann ich mit der Verzögerung leben. Na, DAS würde ICH nicht. >riesen Abweichung innerhalb von 2 Minuten. Oder liegt es vielleicht >dadran, dass jede ms Interrupts auslösen nicht so wirklich praktikabel >ist? Doch, dass passt. Wie Spess53 schon sagte, es muss 249 sein. Macht einen Fehler von 1/249, das ist aber nur 1/2 Sekunde pro zwei Minuten. Wahrscheinlich ist deine Displayausgabe zu langsam. Siehe Artikel Interrupt. Oder [[AVR - Die genaue Sekunde / RTC]] MFG Falk
Hast du die möglichkeit, die Quarzfrequenz zu messen? Die HC49 - Teile werden hier gerne mit 2x 22pF belastet, was nicht richtig ist (im Zweifelsfall im Datenblatt de Quarzes nachsehen). Ich benutze für Uhren einen 33pF an XTAL2 und einen Trimmer 10..60pF an XTAL1. Damit lässt sich die Frequenz sauber auf 16MHz ziehen.
probiers mal damit: TCCR1A=0x00; TCCR1B=0x04; TCNT1H=0x0B; TCNT1L=0xDB; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; TIMSK=0x04; interrupt [TIM1_OVF] void timer1_ovf_isr(void) { TCNT1H=0x0B; TCNT1L=0xDB; //hier den pin setzen/löschen } das ergebnis sollte ein timer1 ovfl interrupt jede sekunde sein. wenn du das unbedingt im compare a interrupt brauchst - einfach OCR1A mit einem wert höher als dem reload wert des timer1 laden.
Hi Das mir dem Preload ist unnötig. Mit Vorteiler 1024 und einem OC-Wert von $3D08 gibt auch eine Sekunde. MfG Spess
dr. boot wrote: > probiers mal damit: > > TCCR1A=0x00; > interrupt [TIM1_OVF] void timer1_ovf_isr(void) > { > TCNT1H=0x0B; > TCNT1L=0xDB; > //hier den pin setzen/löschen > } > > das ergebnis sollte ein timer1 ovfl interrupt jede sekunde sein. > wenn du das unbedingt im compare a interrupt brauchst - einfach OCR1A > mit einem wert höher als dem reload wert des timer1 laden. So sollte man Timer nicht benutzen, weil das sehr ungenau ist! Man kann nämlich nicht genau kontrollieren, wann der Timer gesetzt wird. Für sowas gibt's den CTC-Modus wie der OP ihn auch verwendet hat! Zur Berechnung des OCR-Wertes geht sowas in der Richtung:
1 | // PoutputCompare für gewünschte Timer1 Frequenz
|
2 | OCR1A = (unsigned short) ((unsigned long) F_CPU / PRESCALE / IRQS_PER_SECOND-1); |
PRESCALE = eingestelltes Timer-Prescaling Um sicher zu gehen, daß die Werte immer passen, kann man das durch den Präprozessor kontrollieren lassen:
1 | #if F_CPU / PRESCALE / IRQS_PER_SECOND-1 > 0xffff
|
2 | #error F_CPU zu groß oder PRESCALE zu klein oder zu viele IRQs pro Sekunde
|
3 | #endif
|
4 | |
5 | #if F_CPU % (PRESCALE *IRQS_PER_SECOND)
|
6 | #warning Timing ungenau
|
7 | #endif
|
@johann l hmm das musst du jetzt aber genauer erklären - wenn nur der timer1 läuft, warum sollte man nicht kontrollieren können wann der timer gesetzt wird? zum thema ungenau: hab das mal mit dem scope nachgemessen, und außer der quarztoleranz gibts da absolut nichts - also jitter oder ahnliches
Ich hatte es für den Mega644 so gemacht:
1 | TCCR1B |= _BV(WGM12)|_BV(CS12)|_BV(CS10); // Prescaler /1024, CTC - Mode |
2 | TCNT1 = 0; // Timer rücksetzen |
3 | OCR1A = 15624; // OCR1A = 15625 (16MHz / 1024 / 15625 = 1s) |
4 | TIFR1 |= _BV(OCF1A); // Flags löschen |
5 | TIMSK1 |= _BV(OCIE1A); // Interrupt freigeben |
OCR1A auf den Teiler-1, da das Register bei 0 zu zählen beginnt, nicht bei 1.
>hab das mal mit dem scope nachgemessen
Kannste vergessen. Dafür brauchst du einen Zähler mit z.B. 10s Torzeit,
die Frequenz sollte mindestens 4 Stellen nach dem Komma genau sein
(16.0000xxMHz).
Hi
>wenn nur der timer1 läuft...
Genau das ist halt häufig nicht der Fall. Abgesehen davon, warum soll
man etwas von Hand machen, was der Timer selbstständig kann. Für Timer
ohne OC-Register ist das Verfahren natürlich angebracht. Im gegebenen
Fall nicht.
MfG Spess
dr. boot wrote: > @johann l hmm das musst du jetzt aber genauer erklären - wenn nur der > timer1 läuft, warum sollte man nicht kontrollieren können wann der timer > gesetzt wird? Willst du wirklich von Hand ausrechenen, wieviel Ticks der ISR schon verstrichen sind, bis das OCR neu gesetze wird? Und ausserdem hast du einen Jitter, weil die IRQ-Latenz davon abhängt, während welcher Instruktion eine IRQ getriggert wird. Instruktionen auf AVR sind atomar, und es gibt Instruktionen die dauern 1 Tick, andere 2 (SBIW, BRxx, LDS, LDD, ...) andere 4 oder 5 (RET) ... Zudem: ein 16-Bit-Register setzt man anders, nämlich so:
1 | TCNT1 = ... |
es war ein vorschlag. aber sowas ist hier wohl nicht gern gesehen. abgesehen davon: in 2minuten läuft dieses verfahren sicher nicht um 2 sekunden hinterher.
Hallo zusammen, vielen Dank für die ganzen Antworten. Auch danke für die ganzen Antworten mit dem 1 Sekunden Timer, aber ich brauche schon einen Timer für 1 ms. Dass der Code jetzt nur jede Sekunde wirklich was macht, ist nur wegen dem Uhr Testprogramm. Dass der OCR1A Wert zu groß war, ist mir garnicht aufgefallen, aber das war auch nicht das Hauptproblem, aber trotzdem gut zu wissen :) Mein Problem lag wie von Falk Brunner aufgezeigt daran, dass ich immer Interrupts verschlucke. Ich bin davon ausgegangen, dass der Microcontroller dennoch Interrupts ausführt, auch wenn er sich von einem alten Interrupt noch in der SIGNAL Anweisung befindet. Leider macht er das nicht und dummerweise kannte ich http://www.mikrocontroller.net/articles/Interrupt nicht, obwohl ich versucht habe, alle Tutorials und Wiki/Forenartikel zu finden, die für mein Problem hilfreich sein könnten. Die Uhr läuft jetzt perfekt und ich kann das Programm wieder so umprogrammieren, so dass es das macht, was er soll. Nochmals danke!
dr. boot wrote:
> es war ein vorschlag.
Ja, aber leider war es kein sehr guter. Zu umständlich und ungenau,
deshalb nicht empfehlenswert.
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.