www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Timer (Uhrzeit) ist ungenau


Autor: Christoph Hess (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Der2te_Michael (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Peter Dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jens D. (jens) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: Jens D. (jens) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
okok
bevor ihr mich nun schlagen tut =)

ist auch fast alles im int handler :(
aber genau lief sie..
3 tage laufen lassen mit dem gong einer uhr gestartet und immer mit dem
gong auf 0:0:0 gewesen;)

Autor: Christoph Hess (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie und wo muss ich in meiner oben aufgeführten Interrupt Routine was
verändern?

Autor: Peter Dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
"bevor ihr mich nun schlagen tut =)"

Ja wollen wir.
Was sollen wir denn mit einem Hex-File anfangen ?


Peter

Autor: Jadeclaw (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Michael Wilhelm (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Niels Huesken (monarch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Besser noch ist es die Autoreload-funktion des Timers zu nutzen, als den
wert "händisch" in der Int-routine zu setzen. Bis der erste Codeteil
der Introutine ausgeführt wird, vergehen immerhin 40 microsekunden.

Autor: Jens D. (jens) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
ooooooooopSS
verzeihung =)

eins zu tief geklickt

hier nun die ASM datei

Autor: Niels Huesken (monarch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zu dem Quellcode hab ich ne Frage bezüglich der Interrupt Vektoren:

Ist es nicht so, das ein reti ein ein-byte-Befehl ist und ein rjmp ein
zwei-byte-befehlt?

Ist nicht demnach die Vektortabelle absolut korrupt?

Autor: Jens D. (jens) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Niels Huesken (monarch)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: TravelRec. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jochen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ich hab gedacht der timer läuft über -> interrupt -> timer läuft aber
gleich wieder weiter (verwende allerdings kein AVR)

Autor: Jochen (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ach jetzt da, hier wird keine autoreload genommen oder??

Autor: Niels Huesken (monarch)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nein, kein Autoreload.

Und das retten der register per push und pop ist IMO auch nicht ganz
korrekt platziert....

Autor: Christoph Hess (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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
  }
}

Autor: Christoph Hess (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.