Timer (Uhrzeit) ist ungenau

Gast #215103
Lesenswert?

Hi, ich verwende einen MEGA8535.

Der Timer Interrupt sieht bei mir so aus:

// Timer 0 overflow interrupt service routine
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
 if (zehntelsek < 9) ++zehntelsek;
  else
     {
     zehntelsek = 0;
     if (sekunden < 59) ++sekunden;
     else
        {
        sekunden = 0;
        if (minuten < 59) ++minuten;
        else
           {
           minuten = 0;
           if (stunden < 23) ++stunden;
           else
              {
              stunden = 0;
              if (wochentag < 6) ++wochentag;
              else wochentag = 0;
              if ((monat == 1)||(monat == 3)||(monat == 5)||
                  (monat == 7)||(monat == 8)||(monat == 10))
                  {
                  if (tag < 31) ++tag;
                  else
                     {
                     tag = 1;
                     ++monat;
                     }
                  }
              else if (monat == 12)
                  {
                  if (tag < 31) ++tag;
                  else
                     {
                     tag = 1;
                     monat = 1;
                     ++jahr;
                     }
                  }

              else if (monat == 2)
                  {
                  if (((jahr%4==0) && (jahr%100!=0)) || (jahr%400==0))
                     {
                     if (tag < 29) ++tag;
                     else
                        {
                        tag = 1;
                        ++monat;
                        }
                     }
                  else
                     {
                     if (tag < 28) ++tag;
                     else
                        {
                        tag = 1;
                        ++monat;
                        }
                     }
                  }
              else
                  {
                  if (tag < 30) ++tag;
                  else
                     {
                     tag = 1;
                     ++monat;
                     }
                  }
              }
           }
        }
     }
TCNT1H=0xCF;
TCNT1L=0x2C;
}

void main(void)
{
TCCR1A=0x00;
TCCR1B=0x03;
TCNT1H=0xCF;
TCNT1L=0x2C;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

TIMSK=0x04;

#asm("sei")

while (1)#
 {
  Ausgabe....
 }
}
Mein Board ist mit einem 8Mhz Quarz bestückt.
Wie kann ich meine Uhr genauer machen, ohne dass ich einen anderen
Timer Interrupt verwenden muss?
Gast #215104
Lesenswert?

Hi,

also ich würde in der ISR nur ein Flag setzen, und die Auswertung nicht
in der ISR ausführen, sondern mittels einer Funktion die aus der
main-loop aufgerufen wird. Denn es werden vorm Reloaden der
Timerregister alle Befehle abgearbeitet, was Zeit kostet und die Uhr
ungenau macht.

Oder du zählst die verbrauchten Takte in der ISR für die Abfrage und
ziehst diese von den Reloadwerten ab.

Grüße,

Der2te_Michael
Gast #215105
Lesenswert?

Jede Codezeile benötigt Zeit für die Ausführung !!!

Deshalb muß, einen Timer zu setzen nach einer rein willkürlichen
Ausführungszeit, zwangsläufig ungenau sein.

Hinzukommen noch bei größeren Programmen Verzögerungen durch andere
Interrupts.

Und genau deshalb haben die AVR-Entwickler die "Clear on Compare
Match" Funktion eingebaut. Nutze sie !


Außerdem sind MC-Quarze in der Regel etwas schneller als die
Nennfrequenz, d.h. entweder genau messen oder ne Weile laufen lassen,
ausstoppen und dann den Comparewert korrigieren.


Peter
Persönliche Seite #215106
Lesenswert?

ich wuerde evtl auch hin gehen und einen 2ten externen quarz nehmen,
wenn es eine uhr werden soll,,

ich hatte sowas mal in asm gemacht mit einem LCD Display..

im anhang findest du das ASM programm etwas kommentiert nur achte bitte
nicht darauf, wie ich die bits gesetzt habe
war meuin erstes ASM programm seit 5 jahren ;)

Gruss Jens
Angehängte Dateien:
Gast #215110
Lesenswert?

@Christoph:

TCNT1H=0xCF;
TCNT1L=0x2C;

Diese zwei Teile DIREKT als ERSTES in der Interrupt-Routine
aufrufen.

Dann wird der Timer IMMER mit einer definierten und gleichbleibenden
Verzögerung neu gesetzt.
Zum Abgleich dann TCNT1L tunen.

Gruss
Jadeclaw.
Gast #215111
Lesenswert?

Etwas grundsätzliches zu der Interrupt-Routine:

da die Bedingungen, die abgearbeitet werden müssen, nicht konstant
sind, fütter den Timer am Anfang der Routine mit den Reload-Werten. Und
bei 10 Einsprüngen in der Sekunde soll die Abarbeitungsgeschwindigkeit
schon reichen.

Eleganzer ist aber auf alle Fälle Peters Vorschlag mit dem Clear on
Compare. Wenn die Hardware es anbietet, warum nicht nutzen?

Und im Assemblerlisting nachschauen, wieviel Pops und Pushes in der
Routine vom Compiler eingesetzt werden, genauso die 4 (?) Takte, die
der Controller braucht, um die Routine anzusprechen.

MW
Persönliche Seite #215115
Lesenswert?

verwchselst du nicht zwichen 1 takt und 2 takt befehlen??

bei einem 8 BIT µC wird es kaum mehr als 256 Befehle geben, darum ist
jeder befehl 1 bit lang

womit sollte man sonnst aus der tabelle herrauskommen??
da steht nur mach das dahin die tabelle ist dafuer ausgelegt

schau dir mal hier das tutorial einfach an ;)

Gruss Jens
#215116
Lesenswert?

Während der reti-Befehl ohne parameter auskommt, ist er nur ein Byte
lang. Der rjmp-befehl braucht aber ein parameter, nämlich die
sprungweite. Daher besteht ein rjmp nun aus dem Machinencode für
"rjmp" + Parameter Sprungweite, ein weiteres Byte. Macht zwei byte.

So wars zumindest bei den 8031/51ern....

Ich konnte mir meine Frage schon zwischenzeitlich selbst beantworten.
 Bei den AVR RISC ist jeder befehl+eventueller parameter IMMER 16bit
breit. Auch ein reti.

Von daher ist die Vektortabelle auch valide.
Gast #215117
Lesenswert?

Einfachste Variante für eine genaue Uhr: Timer-Prescaler so einstellen,
daß sich aus Quarzfrequenz dividiert durch Prescaler eine ganze Zahl
ohne Kommastellen ergibt. Dann das Timer-Compare Register mit einem
möglichst hohen Wert laden, durch den die zuvor erhaltene Zahl
dividiert, wieder zu eine ganze Zahl ergibt. Dann den Timer auf "Clear
on Compare match" (CTC-mode) einstellen und den Interrupt bei compare
match auslösen. In dem Interrupt dann einen Zähler incrementieren und
auf die Restzahl hin vergleichen und wenn sie erreicht ist, den Zähler
zurücksetzen und so weiter. So erhält man eine sehr genaue Sekunde, nur
abhängig von der Quarzgenauigkeit und völlig unbeeindruckt vom Code.
Gast #215121
Lesenswert?

Hallo,

ich habe ein Beispiel gesehen von Peter Danneger:
SIGNAL (SIG_OUTPUT_COMPARE1A)
{
/*********************************************************************** 
*/
/*      Insert Key Debouncing Here      */
/*********************************************************************** 
*/

#if XTAL % DEBOUNCE                     // bei rest
  OCR1A = XTAL / DEBOUNCE - 1;    // compare DEBOUNCE - 1 times
#endif
  if( --prescaler == 0 ){
    prescaler = (uchar)DEBOUNCE;
    second++;        // exact one second over
#if XTAL % DEBOUNCE      // handle remainder
    OCR1A = XTAL / DEBOUNCE + XTAL % DEBOUNCE - 1; // compare once per
second
#endif
  }
}

Diesen Code habe ich an meine Applikation angepasst. Dabei habe ich die
If und Endif anweisungen entfernt. Stimmt dann dieser Code so:
SIGNAL (SIG_OUTPUT_COMPARE1A)
{
/*********************************************************************** 
*/
/*      Insert Key Debouncing Here      */
/*********************************************************************** 
*/

#if XTAL % DEBOUNCE                     // bei rest
  OCR1A = XTAL / DEBOUNCE - 1;    // compare DEBOUNCE - 1 times
#endif
  if( --prescaler == 0 ){
    prescaler = (uchar)DEBOUNCE;
    second++;        // exact one second over
#if XTAL % DEBOUNCE      // handle remainder
    OCR1A = XTAL / DEBOUNCE + XTAL % DEBOUNCE - 1; // compare once per
second
#endif
  }
}
Gast #215122
Lesenswert?

Sorry mein Code sieht dann so aus:
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
 if (XTAL % DEBOUNCE)                     // bei rest
  OCR1A = XTAL / DEBOUNCE - 1;    // compare DEBOUNCE - 1 times

  if( --prescaler == 0 )
   {
    prescaler = DEBOUNCE;
    sekunde++;
    Uhrzeit(sekunde);        // exact one second over
if(XTAL % DEBOUNCE)      // handle remainder
    OCR1A = XTAL / DEBOUNCE + XTAL % DEBOUNCE - 1; // compare once per
second
   }
}

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren