Forum: Mikrocontroller und Digitale Elektronik Uhr geht bischen lahm; Codevision; timer vorladen


von Przemo (Gast)


Lesenswert?

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

von Andreas Bombe (Gast)


Lesenswert?

> Da der Chip mit 8MHZ läuft

Aber schon mit externem 8 MHz Quarz (Fuses auch richtig gesetzt?) und
nicht mit dem internen Oszillator?

von Karl H. (kbuchegg)


Lesenswert?

Auch Quarze haben Toleranzen.
Nur weil da 8 Mhz draufsteht, heist das noch lange nicht,
dass er exakt 8000000 Schwingungen in der Sekunde macht.

von Andreas Bombe (Gast)


Lesenswert?

Ich habe jetzt nicht den Überblick, aber ein Durchschnittsquarz sollte
schon besser als 1389 ppm sein, oder?

von Karl H. (kbuchegg)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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

von Andreas Bombe (Gast)


Lesenswert?

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

von johnny.m (Gast)


Lesenswert?

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

von inoffizieller WM-Rahul (Gast)


Lesenswert?


von johnny.m (Gast)


Lesenswert?

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!

von inoffizieller WM-Rahul (Gast)


Lesenswert?

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

von inoffizieller WM-Rahul (Gast)


Lesenswert?

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

von johnny.m (Gast)


Lesenswert?

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

von inoffizieller WM-Rahul (Gast)


Lesenswert?

kann sein... Das wäre dann ja schon eine Pufferung.

von Peter D. (peda)


Lesenswert?

"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

von Przemo (Gast)


Lesenswert?

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

von Profi (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.