Hallo, ich bin noch Anfänger auf dem Gebiet der Mikrocontroller, deshalb wende ich mich mit meinem Problem an euch. Und zwar programmiere ich gerade einen Wecker auf Basis des AtMega8, der mit einem externen 4MHz Quarz getaktet ist. Als Grundlage habe ich den Quellcode aus dem AVR GCC Tutorial genommen. Mein Problem besteht nun darin, dass die Uhr ziemlich ungebau ist. Im Abgleich mit einigen anderen Uhren beträgt die Ungenauigkeit nach 5 Minuten schon fast 10 Sekunden. Die Taktfrequenz ist im Quellcode richtig angegeben. Quellcode: ############################################### Pinbelegung: PD0 -- LCD D4 PD1 -- LCD D5 PD2 -- LCD D6 PD3 -- LCD D7 PD4 -- LCD RS PD5 -- LCD E */ #include <avr/io.h> #include <stdlib.h> #include <avr/interrupt.h> #include "lcd-routines.h" //LCD Routine von Mikrocontroller.net //############ variables ########### //Variablen für die Zeit volatile unsigned int millisekunden; volatile unsigned int sekunde; volatile unsigned int minute; volatile unsigned int stunde; volatile unsigned int tag; //Variablen für die Weckzeit volatile unsigned int w_sekunde; volatile unsigned int w_minute; volatile unsigned int w_stunde; //############## init ############## int init(void){ //Initialisierung des LCDs lcd_init(); //Portinitialisierung DDRB=0xFF; //Port B auf Ausgang setzen // Timer 1 konfigurieren TCCR1A = (1<<WGM11); // CTC Modus TCCR1B |= (1<<CS01); // Prescaler 8 // ((1000000/8)/1000) = 125 OCR1A = 125-1; // Compare Interrupt erlauben TIMSK |= (1<<OCIE1A); // Global Interrupts aktivieren sei(); while(1) { } return 0; } //############ timer-ISR ########### /* Der Compare Interrupt Handler wird aufgerufen, wenn TCNT1 = OCR1A = 125-1 ist (125 Schritte), d.h. genau alle 1 ms */ ISR (TIMER1_COMPA_vect) { //Zeit-Funktionen millisekunden++; if(millisekunden == 1000) { sekunde++; millisekunden = 0; if(sekunde == 60) { minute++; sekunde = 0; } if(minute == 60) { stunde++; minute = 0; } if(stunde == 24) { tag++; stunde = 0; } if(tag == 6) { tag = 0; } //Anzeige-Funktionen lcd_clear(); lcd_string("___ "); if(stunde<=9){lcd_data( '0' );} { char Buffer[20]; itoa( stunde, Buffer, 10 ); lcd_string( Buffer ); } lcd_data( ':' ); if(minute<=9){lcd_data( '0' );} { char Buffer[20]; itoa( minute, Buffer, 10 ); lcd_string( Buffer ); } lcd_data( ':' ); if(sekunde<=9){lcd_data( '0' );} { char Buffer[20]; itoa( sekunde, Buffer, 10 ); lcd_string( Buffer ); } lcd_string(" ___"); //Wochentag anzeigen lcd_setcursor(3,2); switch(tag){ case 0: lcd_string("Mo, "); break; case 1: lcd_string("Di, "); break; case 2: lcd_string("Mi, "); break; case 3: lcd_string("Do, "); break; case 4: lcd_string("Fr, "); break; case 5: lcd_string("Sa, "); break; case 6: lcd_string("So, "); break; } //Weckzeit anzeigen lcd_data( '#' ); if(w_stunde<=9){lcd_data( '0' );} { char Buffer[20]; itoa( w_stunde, Buffer, 10 ); lcd_string( Buffer ); } lcd_data( ':' ); if(w_minute<=9){lcd_data( '0' );} { char Buffer[20]; itoa( w_minute, Buffer, 10 ); lcd_string( Buffer ); } //Weck-Funktionen if(stunde == w_stunde && minute == w_minute && sekunde == w_sekunde){ lcd_setcursor(15,2); lcd_string("#"); //PORTB=(1 << PB2); } }//1ms... } //############## main ############## int main(void) { init(); }
Hi
>Die Taktfrequenz ist im Quellcode richtig angegeben.
Läuft dein ATMega auch wirklich mit dem externen Quarz?
MfG Spess
Hi Ansonsten würde ich mal schlicht behaupten, das deine LCD-Routinen im Interrupt zu lang sind. Noch etwas. Du schreibst: >der mit einem externen 4MHz Quarz getaktet ist. Im Programm steht aber: > // ((1000000/8)/1000) = 125 > OCR1A = 125-1; MfG Spess
> Im Abgleich mit einigen anderen Uhren beträgt die Ungenauigkeit nach 5 > Minuten schon fast 10 Sekunden. Erstens wie spess schon frug: läufst Du mit externem (hinreichend genauem!) Quarz/Takt/Oszi/..? Und zweitens sehe ich das richtig dass Du die ganzen LCD-Updates in der ISR machst? Wundert mich nicht dass da laufend 'millisekunden' verloren gehen (Hint: bei 10 Sekunden Abweichung auf 5 Minuten und 1 Display-Update pro Sekunde sind das gerade mal 10 Sekunden Abweichung auf 300 Updates oder etwa 33ms Abweichung pro Update - das ist für Display-IO durchaus haltbar). HTH
Alleine lcd_clear() wird um die zwei Millisekunden dauern, d.h. pro Sekunde gehen da schon mal mindestens 2 der 1ms-Interrupt flöten.
Danke für eure Antworten. Der externe Quarz läuft mit 4MHz und die Fuses sind auch entsprechend eingestellt. Ich hatte auch mal 1MHz (interner Quarz) angegeben, aber das resultierte in einem "Daten-Chaos". Im Tutorial steht, dass in die ISR keine aufwendigen LCD Ansteuerungen gehören, aber wie lagere ich die Aktionen so aus, dass das LCD jede Sekunde refresht wird und auch die anderen Aktionen (Weckzeitvergleich, Tastenabfrage) ausgeführt werden?
Edit: @Spess: Im Tutorial ist ein Quarz mit 3,xx MHz angegeben und der Wert für OCR1A wurde genauso berechnet, wie in meinem Code. Wenn man dort 4MHz einsetzt, bekommt man 500 als Wert für OCR1A. Wenn ich morgen in der Werkstatt bin, probiere ich mal den anderen Wert aus.
Lukas B. schrieb: > Im Tutorial steht, dass in die ISR keine aufwendigen LCD Ansteuerungen > gehören, aber wie lagere ich die Aktionen so aus, dass das LCD jede > Sekunde refresht wird und auch die anderen Aktionen (Weckzeitvergleich, > Tastenabfrage) ausgeführt werden? In der ISR setzt du zur vollen Sekunde nur ein Flag. In der Endlosschleife im Hauptprogramm wird dieses Flag geprüft und damit Vergleiche und Ausgabe gesteuert. Parallel können dann aber schon per ISR die ersten paar ms der neuen Sekunde gezählt werden, weil der IRQ den Ablauf unterbrechen kann.
Könntest du das eventuell etwas konkretisiseren, denn ich kann mir nicht vorstellen, wie ich das programmieren soll. Die Idee ist verständlich, aber die Umsetzung... Meinst du in etwa so etwas?
1 | ISR (TIMER1_COMPA_vect) |
2 | {
|
3 | //Zeit-Funktionen
|
4 | millisekunden++; |
5 | if(millisekunden == 1000) |
6 | {
|
7 | refresh_flag=1; |
8 | sekunde++; |
9 | millisekunden = 0; |
10 | //...
|
11 | }
|
12 | }
|
13 | |
14 | int main(void) |
15 | {
|
16 | init(); |
17 | |
18 | if(refresh_flag){ |
19 | //Anzeige-Funktionen
|
20 | //...
|
21 | }
|
22 | }
|
Den Teil mit dem hochzählen der Uhr lässt du im Interrupt, den Teil mit der Ausgabe verlagerst du in die Hauptschleife nach main. Damit die Hauptschleife weiß, dass es die Anzeige aktualisieren muss, setzt du eine globale Variable ein.
1 | ...
|
2 | volatile uint8_t updateTime = 0; |
3 | ...
|
4 | |
5 | ISR (TIMER1_COMPA_vect) |
6 | {
|
7 | //Zeit-Funktionen
|
8 | millisekunden++; |
9 | if(millisekunden == 1000) |
10 | {
|
11 | millisekunden = 0; |
12 | sekunde++; |
13 | |
14 | updateTime = 1; |
15 | |
16 | if(sekunde == 60) |
17 | {
|
18 | sekunde = 0; |
19 | minute++; |
20 | |
21 | if(minute == 60) |
22 | {
|
23 | minute = 0; |
24 | stunde++; |
25 | |
26 | if(stunde == 24) |
27 | {
|
28 | stunde = 0; |
29 | tag++; |
30 | |
31 | if(tag == 6) |
32 | {
|
33 | tag = 0; |
34 | }
|
35 | }
|
36 | }
|
37 | }
|
38 | }
|
39 | }
|
40 | |
41 | int main() |
42 | {
|
43 | char Buffer[20]; |
44 | ....
|
45 | |
46 | while( 1 ) { |
47 | if( updateTime == 1 ) |
48 | {
|
49 | updateTime = 0; |
50 | |
51 | // ... Anzeige aktualisieren
|
52 | sprintf( buffer, "%02d:%02d:%02d", stunde, minute, sekunde ); |
53 | lcd_setcursor(3, 0); |
54 | lcd_string( buffer ); |
55 | }
|
56 | }
|
57 | }
|
Und noch was: Mach deine Sekunden, Minten, Stunden und Tag Variable als uint8_t und nicht als unsigned int. Du musst deinem AVR keine 16 Bit Arithmetik aufzwingen, wenn deine Zahlen nie größer als 60 werden können. Dazu reichen 8 Bit locker aus. Das Hochzählen der Zeit in der ISR dauert ein paar Takte und ist insofern nicht zeitkritisch. Du willst die Zeit aber in der ISR komplett hochzählen, weil es dir nicht passieren darf, dass Sekunden nicht gewertet werden, weil das Hauptprogramm gerade längere Zeit mit etwas anderem beschäftigt ist. So läuft die Uhr unabhängig von allem anderen in der ISR autonom und richtig. Was du dann weiter mit der Uhrzeit machst, Ausgeben oder mit einer Weckzeit vergleichen beeinflusst die Uhr selber nicht mehr. Wobei: Das Auswerten der WEckzeit könnte man noch mit in die ISR packen. Denn auch das darf auf keinen Fall verloren gehen und soll klarerweise auch dann passieren, wenn der Benutzer zb gerade den Wochentag einstellt. ISR sollen so kurz wie möglich sein und nur das notwendigste machen. Du willst so schnell wie möglich wieder aus der ISR raus. Man muss aber auch nicht päpstlicher als der Papst sein. Ein bischen was darf man schon auch in der ISR machen. Vor allen Dingen dann, wenn es dich in Schwierigkeiten bringen kann, wenn du es nicht tust. Das erhöhen einer Uhrzeit gehört dazu. Die Anzeige aber nicht. Die Anzeige kannst du auch woanders machen oder zb überhaupt unterlassen, weil der Benutzer gerade die Weckzeit einstellt. Aber nur weil dein Benutzer die Weckzeit einstellt, darf ja die Uhr nicht aufhören zu laufen.
Danke dir, das hilft mir sehr weiter. Den Abgleich mit in die ISR zu packen, macht rein theoretisch keinen Sinn, da der Anwender kaum kurz vor dem Auslösen des Wecksignals, die Uhrzeit umstellen wird, es sei denn, er ist Schlafwandler. ;) Wenn es zeitlich allerdings unkritisch ist, kann man es ja sicherheitshalber machen.
Ich habe es jetzt mal ausprobiert, aber leider besteht immer noch dasselbe Problem und die Ungenauigkeit ist sehr groß. Mir ist aber aufgefallen, dass ich beim Anpassen der Register irgendetwas falsch gemacht habe. Denn im Tutorial ist der Code für einen Attiny2313 aufgeführt, allerdoings gab es hier beim Kompilieren Fehler, solange man in den Projekt-Optionen AtMega8 angegeben hat. Mein Code:
1 | // Timer 1 konfigurieren
|
2 | TCCR1A = (1<<WGM11); // CTC Modus |
3 | TCCR1B |= (1<<CS01); // Prescaler 8 |
4 | // ((4000000/32)/1000) = 125
|
5 | OCR1A = 125-1; |
6 | |
7 | // Compare Interrupt erlauben
|
8 | TIMSK |= (1<<OCIE1A); |
9 | |
10 | // Global Interrupts aktivieren
|
11 | sei(); |
Tutorial:
1 | TCCR0A = (1<<WGM01); // CTC Modus |
2 | TCCR0B |= (1<<CS01); // Prescaler 8 |
3 | // ((1000000/8)/1000) = 125
|
4 | OCR0A = 125-1; |
5 | |
6 | // Compare Interrupt erlauben
|
7 | TIMSK |= (1<<OCIE0A); |
8 | |
9 | // Global Interrupts aktivieren
|
10 | sei(); |
Ich denke, da stimmt irgendetwas nicht, oder? Da der Timer 0 beim Atmega8 kein CTC unterstützt habe ich die Register (versucht) an den Atmega8 anzupassen. Wenn ich in TCCR0A WGM01 setze, gibt mir der Compiler Fehler aus.
Hi Hör mal auf, dich an das Tutorial zu klammern. Maßgebend ist das Datenblatt. MfG Spess
Lukas B. schrieb: > Da der Timer 0 beim > Atmega8 kein CTC unterstützt habe ich die Register (versucht) an den > Atmega8 anzupassen. Offensichtlich aber ohne in das schon von Spess erwähnte Datenblatt zu schauen. Denn statt des CTC hast du einen PWM Modus eingestellt. PS: Und wo kommt in deiner Rechnung die 32 her?
Das Datenblatt habe ich gerade zu Rate gezogen und jetzt geht es. So muss es aussehen:
1 | |
2 | // Timer 1 konfigurieren
|
3 | TCCR1B |= (1<<WGM12); // CTC Modus |
4 | TCCR1B |= (1<<CS11); // Prescaler 8 |
5 | // ((4000000/8)/1000) = 500
|
6 | OCR1A = 500-1; |
Danke nochmal für eure Tipps, jetzt kann ich mich der Tasterabfrage widmen und wenn es fertig ist, poste ich noch einmal den gesamten Code.
Lukas B. schrieb: > Denn im Tutorial ist der Code für einen > Attiny2313 aufgeführt, allerdoings gab es hier beim Kompilieren Fehler, > solange man in den Projekt-Optionen AtMega8 angegeben hat. Und wenn der Compiler keine Fehler anzeigt, heißt das noch lange nicht, dass alles richtig ist. Guck mal, in welchem Register des ATmega8 der Teilerfaktor für den Prescaler vom Timer0 festgelegt wird.
Genau das war ja mein Fehler, die Register stimmten vorne und hinten nicht. Aber der Fehler ist ja behoben und auf 40 Minuten konnte ich keine Abweichung zu meiner Referenzuhr wahrnehmen. Ich schließe daraus, dass der Timer jetzt ordnungsgemäß im CTC-Modus mit korrektem Vorteiler und Obergrenze für den Interrupt initialisiert ist. Im Anhang ist der aktuelle Stand des Codes mit implemetierter Tasterabfrage zu sehen. Ich kann jetzt über einen Taster den Alarm ein und ausschalten und über einen weiteren zwischen fünf Modi (w_stunde; w_minute; tag; stunde; minute) toggeln und die entsprechenden Variablen mit zwei weiteren Tastern inkrementieren bzw. dekrementieren. Was noch nicht funktioniert, ist der Auto-Repeat bei längerem Drücken.
Hallo nochmal, ich bin jetzt fast komplett fertig und bei der Autorepeat-Funktion der Taster ist eine Aktuelaisierungsrate von 1Hz doch etwas unkomfortabel. Kann man die LCD Funktionen problemlos alle 100ms aufrufen oder läuft die ISR dann wieder zu ungenau?
@ Lukas B. (Gast) >Kann man die LCD Funktionen problemlos alle 100ms aufrufen Sicher, aber > oder läuft die ISR dann wieder zu ungenau? Nö, denn die LCD-Sachen ruft man NICHT in der ISR auf sondern in der Hauptschleife. Dort hat man (fast) alle Zeit der Welt. Hast du ja mittlerweile richtig in deinem Programm. MFG Falk
Wäre es dann auch möglich, den Programmteil in Main jede Millisekunde aufzurufen oder wird die ISR und damit die Uhr dann wieder zu langsam?
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.