Forum: Mikrocontroller und Digitale Elektronik Atmega 32, Quarz, Uhr, ungenau


von feig (Gast)


Lesenswert?

Hallo zusammen,

ich versuche gerade, Timer1 eines Atmega 32 Microcontrollers zu 
programmieren, so dass dieser jede Millisekunde einen 
SIG_OUTPUT_COMPARE1A Interrupt auslöst. Der Controller ist mit 16 Mhz 
getaktet.

Ich probiere im Moment eine kleine Uhr zu programmieren (naja, 
eigentlich nicht ... aber ich musste mein Programm in ein Uhrenprogramm 
umwandeln, nachdem ich Unstimmigkeiten festgestellt habe).

Mein Plan war folgender: Ich lasse den Controller jede Millisekunde den 
Interrupt triggern und zähle dann in der Interrupt Routine bis 1000, um 
eine Sekunde zu bekommen. Doch leider musste ich feststellen, dass meine 
Sekundenanzeige schon nach 2 Minuten Laufzeit um 2 Sekunden von der 
Funkuhr abweicht und das ist definitiv nicht normal. Dass der Quarz 
korrekt/genau funktioniert habe ich festgestellt, als ich Timer0 
verwendet hatte bzw einen anderen "Weg". Da ging die Uhr nach 10 Std 
Laufzeit um 2 Sekunden nach, was natürlich kein Problem ist.

Aber hier mal mein Code:

void
timer_init(void)
{
  /* 0,001s * 16.000.000 Hz = 16000; 16000 / 64 => Prescaler = 250 to 
get an interrupt every 1ms */
  OCR1A = 250;

  /* Set timer 1 to compare mode */
  TIMSK = _BV(OCIE1A);

  /* Reset the timer */
  TCNT1 = 0;

  /* Prescaler set to 64 & reset to 0 after triggering the interrupt */
  TCCR1B = _BV(CS10) | _BV(CS11) | _BV(WGM12);

  sei();
}


SIGNAL(SIG_OUTPUT_COMPARE1A)
{
  extern void timer_clock(void);
  extern void display_print(void);
  static volatile uint16_t counter;

  if(++counter == 1000)
  {
    counter = 0;
    timer_clock();
    display_print();
  }
}

Da das eigentlich kein Uhrenprogramm sein sollte und ich es ohne viele 
Handgriffe Umsticken wollte, bitte nicht wundern über den komischen Code 
;)

Was hier passieren soll: Alle 1ms Interrupt auslösen bei 16 Mhz heißt 
bei einem Prescaler von 64, dass ich bis 250 hochzählen muss, bevor ein 
Interrupt ausgelöst werden darf. Leider stimmt nach 2 Minuten der Wert 
schon um 2 Sekunden nicht mehr.

timer_clock erhöht einfach einen Sekundenzähler und berechnet Std/Mins 
bzw deren Überläufe bei 60 und 24. display_print ... naja, zeigt mir die 
Uhr auf nem Display an.

Im Grunde kann ich mit der Verzögerung leben. Mich würde aber dennoch 
interessieren, ob ich nicht eher einen Fehler im Quellcode habe (oder 
einen Denkfehler bei der Berechnung vom OCR1A Wert). Mich wundert diese 
riesen Abweichung innerhalb von 2 Minuten. Oder liegt es vielleicht 
dadran, dass jede ms Interrupts auslösen nicht so wirklich praktikabel 
ist?

Mit der Forensuche habe ich schon geschaut, aber Fragen zu "Hilfe meine 
Uhr weicht in 200 Stunden um 1,535 Sekunden ab" treffen leider nicht so 
ganz auf meine Problembeschreibung zu, aber vielleicht habe ich auch 
einen hilfreichen Thread übersehen :)

Wie gesagt, mit der Verzögerung kann ich leben, aber es würde mich 
einfach nur interessieren, ob ich nicht doch was falsch mache im Code.

von Michael G. (linuxgeek) Benutzerseite


Lesenswert?

Benutz doch mal einen Timer-Interrupt. Und wenn Du einen 16-Bit-Timer 
verwendest kannst Du ihn gleich so konfigurieren dass er jede Sekunde 
ausloest, passender Quarz vorausgesetzt.

von spess53 (Gast)


Lesenswert?

Hi

Dein Wert für OCR1A ist falsch. Muss 249 sein.

MfG Spess

von Falk B. (falk)


Lesenswert?

@ feig (Gast)

>Interrupt ausgelöst werden darf. Leider stimmt nach 2 Minuten der Wert
>schon um 2 Sekunden nicht mehr.

Ganz schön viel.

>timer_clock erhöht einfach einen Sekundenzähler und berechnet Std/Mins
>bzw deren Überläufe bei 60 und 24. display_print ... naja, zeigt mir die
>Uhr auf nem Display an.

Wenn die Routine länger als 2ms dauert, verschluckst du jede Sekunde 
einen Interrupt.

>Im Grunde kann ich mit der Verzögerung leben.

Na, DAS würde ICH nicht.

>riesen Abweichung innerhalb von 2 Minuten. Oder liegt es vielleicht
>dadran, dass jede ms Interrupts auslösen nicht so wirklich praktikabel
>ist?

Doch, dass passt.

Wie Spess53 schon sagte, es muss 249 sein. Macht einen Fehler von 1/249, 
das ist aber nur 1/2 Sekunde pro zwei Minuten. Wahrscheinlich ist deine 
Displayausgabe zu langsam. Siehe Artikel Interrupt. Oder [[AVR - Die 
genaue Sekunde / RTC]]

MFG
Falk

von Thilo M. (Gast)


Lesenswert?

Hast du die möglichkeit, die Quarzfrequenz zu messen?
Die HC49 - Teile werden hier gerne mit 2x 22pF belastet, was nicht 
richtig ist (im Zweifelsfall im Datenblatt de Quarzes nachsehen). Ich 
benutze für Uhren einen 33pF an XTAL2 und einen Trimmer 10..60pF an 
XTAL1. Damit lässt sich die Frequenz sauber auf 16MHz ziehen.

von dr. boot (Gast)


Lesenswert?

probiers mal damit:

TCCR1A=0x00;
TCCR1B=0x04;
TCNT1H=0x0B;
TCNT1L=0xDB;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

TIMSK=0x04;


interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
      TCNT1H=0x0B;
      TCNT1L=0xDB;
      //hier den pin setzen/löschen
}

das ergebnis sollte ein timer1 ovfl interrupt jede sekunde sein.
wenn du das unbedingt im compare a interrupt brauchst - einfach OCR1A 
mit einem wert höher als dem reload wert des timer1 laden.

von spess53 (Gast)


Lesenswert?

Hi

Das mir dem Preload ist unnötig. Mit Vorteiler 1024 und einem OC-Wert 
von
$3D08 gibt auch eine Sekunde.

MfG Spess

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

dr. boot wrote:
> probiers mal damit:
>
> TCCR1A=0x00;

> interrupt [TIM1_OVF] void timer1_ovf_isr(void)
> {
>       TCNT1H=0x0B;
>       TCNT1L=0xDB;
>       //hier den pin setzen/löschen
> }
>
> das ergebnis sollte ein timer1 ovfl interrupt jede sekunde sein.
> wenn du das unbedingt im compare a interrupt brauchst - einfach OCR1A
> mit einem wert höher als dem reload wert des timer1 laden.

So sollte man Timer nicht benutzen, weil das sehr ungenau ist!
Man kann nämlich nicht genau kontrollieren, wann der Timer gesetzt wird.

Für sowas gibt's den CTC-Modus wie der OP ihn auch verwendet hat!

Zur Berechnung des OCR-Wertes geht sowas in der Richtung:
1
    // PoutputCompare für gewünschte Timer1 Frequenz
2
    OCR1A = (unsigned short) ((unsigned long) F_CPU / PRESCALE / IRQS_PER_SECOND-1);

PRESCALE = eingestelltes Timer-Prescaling

Um sicher zu gehen, daß die Werte immer passen, kann man das durch den 
Präprozessor kontrollieren lassen:
1
#if F_CPU / PRESCALE / IRQS_PER_SECOND-1 > 0xffff
2
#error F_CPU zu groß oder PRESCALE zu klein oder zu viele IRQs pro Sekunde
3
#endif
4
5
#if F_CPU % (PRESCALE *IRQS_PER_SECOND) 
6
#warning Timing ungenau
7
#endif

von dr. boot (Gast)


Lesenswert?

@johann l hmm das musst du jetzt aber genauer erklären - wenn nur der 
timer1 läuft, warum sollte man nicht kontrollieren können wann der timer 
gesetzt wird?
zum thema ungenau: hab das mal mit dem scope nachgemessen, und außer der 
quarztoleranz gibts da absolut nichts - also jitter oder ahnliches

von Thilo M. (Gast)


Lesenswert?

Ich hatte es für den Mega644 so gemacht:
1
TCCR1B |= _BV(WGM12)|_BV(CS12)|_BV(CS10); // Prescaler /1024, CTC - Mode 
2
TCNT1 = 0;                                // Timer rücksetzen
3
OCR1A = 15624;                            // OCR1A = 15625 (16MHz / 1024 / 15625 = 1s)
4
TIFR1 |= _BV(OCF1A);                      // Flags löschen
5
TIMSK1 |= _BV(OCIE1A);                    // Interrupt freigeben

OCR1A auf den Teiler-1, da das Register bei 0 zu zählen beginnt, nicht 
bei 1.

von Thilo M. (Gast)


Lesenswert?

>hab das mal mit dem scope nachgemessen

Kannste vergessen. Dafür brauchst du einen Zähler mit z.B. 10s Torzeit, 
die Frequenz sollte mindestens 4 Stellen nach dem Komma genau sein 
(16.0000xxMHz).

von spess53 (Gast)


Lesenswert?

Hi

>wenn nur der timer1 läuft...

Genau das ist halt häufig nicht der Fall. Abgesehen davon, warum soll 
man etwas von Hand machen, was der Timer selbstständig kann. Für Timer 
ohne OC-Register ist das Verfahren natürlich angebracht. Im gegebenen 
Fall nicht.

MfG Spess

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

dr. boot wrote:
> @johann l hmm das musst du jetzt aber genauer erklären - wenn nur der
> timer1 läuft, warum sollte man nicht kontrollieren können wann der timer
> gesetzt wird?

Willst du wirklich von Hand ausrechenen, wieviel Ticks der ISR schon 
verstrichen sind, bis das OCR neu gesetze wird? Und ausserdem hast du 
einen Jitter, weil die IRQ-Latenz davon abhängt, während welcher 
Instruktion eine IRQ getriggert wird. Instruktionen auf AVR sind atomar, 
und es gibt Instruktionen die dauern 1 Tick, andere 2 (SBIW, BRxx, LDS, 
LDD, ...) andere  4 oder 5 (RET) ...

Zudem: ein 16-Bit-Register setzt man anders, nämlich so:
1
TCNT1 = ...

von dr. boot (Gast)


Lesenswert?

es war ein vorschlag. aber sowas ist hier wohl nicht gern gesehen.
abgesehen davon: in 2minuten läuft dieses verfahren sicher nicht um 2 
sekunden hinterher.

von feig (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank für die ganzen Antworten. Auch danke für die ganzen 
Antworten mit dem 1 Sekunden Timer, aber ich brauche schon einen Timer 
für 1 ms. Dass der Code jetzt nur jede Sekunde wirklich was macht, ist 
nur wegen dem Uhr Testprogramm.

Dass der OCR1A Wert zu groß war, ist mir garnicht aufgefallen, aber das 
war auch nicht das Hauptproblem, aber trotzdem gut zu wissen :)

Mein Problem lag wie von Falk Brunner aufgezeigt daran, dass ich immer 
Interrupts verschlucke. Ich bin davon ausgegangen, dass der 
Microcontroller dennoch Interrupts ausführt, auch wenn er sich von einem 
alten Interrupt noch in der SIGNAL Anweisung befindet. Leider macht er 
das nicht und dummerweise kannte ich 
http://www.mikrocontroller.net/articles/Interrupt nicht, obwohl ich 
versucht habe, alle Tutorials und Wiki/Forenartikel zu finden, die für 
mein Problem hilfreich sein könnten.

Die Uhr läuft jetzt perfekt und ich kann das Programm wieder so 
umprogrammieren, so dass es das macht, was er soll.

Nochmals danke!

von Simon K. (simon) Benutzerseite


Lesenswert?

dr. boot wrote:
> es war ein vorschlag.

Ja, aber leider war es kein sehr guter. Zu umständlich und ungenau, 
deshalb nicht empfehlenswert.

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.