Hallo allerseits. Ich hab mal ne einfache Uhr in Codevision geschrieben, aberleider geht sie um die 5 Sekunden zu langsam bei ner Stunde. Hier der Code. Ich weiß nicht so recht woran es liegen könnte. Ist meine erst Erfahrung mit Timern, ich hab den Timer halt vorgeladen. Da der Chip mit 8MHZ läuft, muss er 8000 Zyklen für ne Milisekunde machen. Also läuft der Timer 7999 Zyklen pluzs den "nullten" am Anfang. Und bitte nicht durch die ganzen Ifs verwirren lassen, manche sind dazu da, damit die einer und zehner in die richtige Stelle aufs Display kommen. Ich weiß auch nciht, ob die Bedingung mit "if (milisekunde == 999)" richtig ist, ob das nicht eigentlich 1000 sein müssen. Aber dann geht die Uhr halt noch langsamer. /***************************************************** This program was produced by the CodeWizardAVR V1.24.6 Professional Automatic Program Generator © Copyright 1998-2005 Pavel Haiduc, HP InfoTech s.r.l. http://www.hpinfotech.com e-mail:office@hpinfotech.com Project : TimerLCD Version : Date : 18.08.2006 Author : F4CG Company : F4CG Comments: Chip type : ATmega8 Program type : Application Clock frequency : 8,000000 MHz Memory model : Small External SRAM size : 0 Data Stack size : 256 *****************************************************/ #include <mega8.h> #include <stdlib.h> // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x12 ;PORTD #endasm #include <lcd.h> unsigned int milisekunde = 0; unsigned int sekunde = 0, minute = 0, stunde = 0, tag = 0; char *char_sekunde, *char_minute, *char_stunde, *char_tag; // Timer 1 overflow interrupt service routine interrupt [TIM1_OVF] void timer1_ovf_isr(void) { // Reinitialize Timer 1 value TCNT1H=0xE0; //Timer Macht immer 8000 Schritte mit 8Mhz, macht also 1 milisekunde TCNT1L=0xC0; // Place your code here milisekunde++; } void main(void) { // Declare your local variables here // Input/Output Ports initialization // Port B initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTB=0x00; DDRB=0x00; // Port C initialization // Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTC=0x00; DDRC=0x00; // Port D initialization // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped TCCR0=0x00; TCNT0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 8000,000 kHz // Mode: Normal top=FFFFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer 1 Overflow Interrupt: On // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x00; TCCR1B=0x01; TCNT1H=0xE0; TCNT1L=0xC0; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Timer/Counter 2 initialization // Clock source: System Clock // Clock value: Timer 2 Stopped // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x00; TCCR2=0x00; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off MCUCR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x04; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; // LCD module initialization lcd_init(20); // Global enable interrupts #asm("sei") lcd_clear(); while (1) { if (milisekunde == 999) { milisekunde = 0; sekunde++; if (sekunde < 10) { lcd_gotoxy(1,0); } else { lcd_gotoxy(0,0); }; itoa(sekunde, char_sekunde); lcd_puts(char_sekunde); }; if (sekunde == 60) { sekunde = 0; minute++; if (minute < 10) { lcd_gotoxy(1,2); } else { lcd_gotoxy(0,2); }; itoa(minute, char_minute); lcd_puts(char_minute); }; if (minute == 60) { minute = 0; stunde++; if (stunde < 10) { lcd_gotoxy(1,1); } else { lcd_gotoxy(0,1); }; itoa(stunde, char_stunde); lcd_puts(char_stunde); }; if (stunde == 24) { stunde = 0; tag++; if (tag < 10) { lcd_gotoxy(1,3); } else { lcd_gotoxy(0,3); }; itoa(tag, char_tag); lcd_puts(char_tag); }; if (tag == 99) { tag = 0; }; }; } /* void lcd_clear(void) clears the LCD and sets the printing character position at row 0 and column 0. void lcd_gotoxy(unsigned char x, unsigned char y) sets the current display position at column x and row y. The row and column numbering starts from 0. void lcd_putchar(char c) displays the character c at the current display position. void lcd_puts(char *str) displays at the current display position the string str, located in SRAM. void lcd_putsf(char flash *str) displays at the current display position the string str, located in FLASH. */
> Da der Chip mit 8MHZ läuft
Aber schon mit externem 8 MHz Quarz (Fuses auch richtig gesetzt?) und
nicht mit dem internen Oszillator?
Auch Quarze haben Toleranzen. Nur weil da 8 Mhz draufsteht, heist das noch lange nicht, dass er exakt 8000000 Schwingungen in der Sekunde macht.
Ich habe jetzt nicht den Überblick, aber ein Durchschnittsquarz sollte schon besser als 1389 ppm sein, oder?
Übrigens hast du einen schweren Fehler im Programm. Du schreibst: char *char_sekunde, *char_minute, *char_stunde, *char_tag; und dann weiter: itoa(sekunde, char_sekunde); lcd_puts(char_sekunde); Jetzt mal eine Frage: itoa möchte sein Ergebnis, den String, im Speicher ablegen. Dazu wird itoa ein Pointer auf den Speicher übergeben in den itoa den String schreiben kann. Nur: Wo ist bei dir dieser Speicherbereich. Alles was du bis jetzt hast ist ein Pointer. Dieser Pointer ist (da es sich um eine globale Variable handelt) mit 0 vorbelegt, aber: da wurde niemals Speicher reserviert. itoa schreibt daher in Speicher, der niemals vom Programm als für diesen Zweck reserviert markiert wurde. char char_sekunde[3], char_minute[3], char_stunde[3], char_tag[3]; behebt dieses Problem. Jetzt wird wirklich Speicher (in Form eines Arrays) reserviert, in das itoa schreiben kann. Die Array-Größe 3 ergibt sich ganz einfach daraus, dass die Sekunden nur maximal 2-stellig sein können, der umgewandelte String also samt abschliessendem '\0' Zeichen 3 Zeichen umfassen.
> 1389 ppm
Ich denke schon. Allerdings wissen wir nicht wie seine
Aussenbschaltung des Quarzes aussieht. Mit den Kondensatoren
kann man den Quarz auch in der Frequenz ziehen.
Ich will auch nicht ausschliessen dass in seinem Programm
auch noch Timing-Fehler drinn sind. Mal durchdenken ob der
Preload Wert so stimmen kann.
Worauf es mir aber ankommt: Zu glauben, dass man so mir nichts,
dir nichts eine Uhr bauen kann die in einem Jahr nur um 2 Sekunden
falsch geht, nur weil da ein Quarz dranhängt, ist ein Trugschluss.
Schon klar, dass es so einfach nicht ultrapräzise wird. Aber statt 5 Sekunden pro Stunde sollten es doch locker 5 Sekunden am Tag sein (ca. 50 ppm).
Wenn man das so macht, ist es auch kein Wunder, dass es größere Abweichungen gibt. Wenn in der ISR das TCNT1 nachgeladen wird, sind ja seit dem Überlauf schon wieder einige Takte verstrichen (das alte Problem, gabs hier in letzter Zeit öfter mal Threads zu). Deshalb nimmt man für solche Sachen ja auch den CTC-Modus des Timers. Da braucht man nämlich nix nachzuladen und das ganze ist sehr präzise (natürlich immer nur so präzise wie der Systemtakt...)
Der Einsprung in den Interrupt-Vektor dauert beim Mega8 4 Taktzyklen. Dann kommen 3 Zyklen für den Sprungbefehl in die ISR dazu. Dann werden Register gesichert, was auch noch mal 10-15 Zyklen dauern dürfte. Dann erst wird das TCNT1 beschrieben (wobei man übrigens zur Sicherheit TCNT1H und TCNT1L nicht separat schreiben sollte, sondern die von der lib angebotene 16-Bit-Variable, da man sich dann keine Gedanken über die Schreibreihenfolge machen muss...). Das bedeutet, dass der Timer nach jedem Nachladen bis zu ca. 20 Zyklen "nachgeht". 20 Zyklen von 8000 sind 2,5 Promille, das würde pro Stunde 9 Sekunden Abweichung ausmachen! Da Du nur 5 Sekunden misst, sind es wahrscheinlich etwas weniger Zyklen. BTW: Demnächst Deinen Code bitte als Anhang posten, so wie es auch über dem Editor-Fenster steht!
Meine Uhr (in einem Wetterlogger) läuft leider immer nur so um die 8 Stunden am Stück , deswegen kann ich bis jetzt noch keine wirklich gute Aussage zur Genauigkeit machen. (Die Uhr selbst würde wohl auch länger laufen, nur wird ihr irgendwann manuell der Strom abgestellt...)
Zum "Nachlade-Problem": Das ist beim 8051 sehr beliebt, weil man dort die PWM-Modi (noch) nicht hatte. Da hat man das Problem aber umschifft, indem man Timer = Timer+Nachladewert eingestellt hat. Man hat also vor Verlassen der ISR den Timer-Wert ausgelesen - damit hat man dann die Dauer der ISR. Der Nachladewert wird also entsprechend korrigiert (beim Nachladewert müsste man noch die "Rückkehr" aus der ISR mit einrechnen). Sowas ist eher was für Assembler-Programmierer, weil man da weiß, was man hat/braucht... Wenn man den Timer aber auch noch für andere Sachen braucht und ihn konstant durchlaufen lassen will/muß, dann bietet sich an, in ein OCR einen Vergleichswert zu schreiben. Da dieser Wert aber vermutlich nicht mit dem des Timerüberlauf zusammenpasst, muß man bei jedem OC-Interrupt den Wert dieses Registers mit einer Konstanten addieren. Beispiel: Timerüberlauf alle 65536µs OC soll aber alle 1000µS (1ms) auftreten. ==> Beim OC-Interrupt OCR=OCR+1000 Dann sollte das nächste OC-Interrupt eigentlich 1000µs nach diesem auflaufen...
@Rahul: Hatten einige 8051er nicht sogar ein Reload-Register für den Timer, das bei Überlauf automatisch ins Timerregister übernommen werden konnte? Ich meine, mich erinnern zu können, dass z.B. der 80C537 so einen Auto Reload Modus hatte...
kann sein... Das wäre dann ja schon eine Pufferung.
"Hatten einige 8051er nicht sogar ein Reload-Register für den Timer," Ja, der T2 hat ein Reload bei Überlauf. Bzw. das PCA macht einen Compareinterrupt und da kann man dann auch den gewünschten Zeitabstand zum Comparewert addieren für den nächsten Interrupt. T0 und T1 können zwar auch ein Reload machen, sind dann aber nur 8-Bittig (High-Byte = Reload-Wert). Die Interrupts kommen dann eben etwas fix, was aber durch Interuptprioritäten kein großes Problem ist. Peter
Danke für die vielen Antworten. Also mal alles abarbeiten ^^: - Alles klar, hab ich überlesen, Source Code kommt künftig als Anhang. - Quarz ist richtig beschaltet, auch die Fusebits so weit ich das alles beurteilen kann. - Das mit den Taktzyklen klingt für mich logisch. Bin noch nicht lange und viel am Proggen bei den AVRs. Die 5 Sekunden hab ich nur pi mal Daumen gestoppt. - Über den CTC Modus hab ich bischen was gelesen, aber noch nicht alles ganz durchschaut. Deshalb wollte ich mal zuerst das mit dem Vorladen probieren. - Danke auch für den Tip mit den Piontern, wusste nicht mehr wie das ging mit dem Platzreservieren. Super schnelle Hilfe hier, vielen Dank nochmal! Grüße, Przemo
Dirty Trick: // Reinitialize Timer 1 value TCNT1H=0xE0; //Timer Macht immer 8000 Schritte mit 8Mhz, macht also 1 milisekunde TCNT1L=0xC0; Schreibe halt einfach 7980 (0xE0D4) rein, das gleicht den Zeitverlust wieder aus. So kannst Du auch ohne Trimmer die Uhr abgleichen. Das geht noch genauer, wenn Du den Zähler länger zählen läßt, also z.B. 5 ms (ca. 40000 -> 0x63C0)
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.