Forum: Mikrocontroller und Digitale Elektronik Denkfehler bei Timer-Synchronisation?


von Uwe B. (boerge) Benutzerseite


Lesenswert?

MoinMoin,

ich baue gerade an der 12843.Nixie-Uhr rum. Hardware funktioniert, die 
Software auf einem Mega8 eigentlich auch.... bis auf ein kleines 
i-Tüpfelchen.

Via TWI ist ein DCF77-Modul (http://bralug.de/wiki/BLIT2008-Board-DCF77) 
angeschlossen, über welches stündlich eine Synchronisation der 
Uhrzeit/Datum erfolgt. Dieses Modul liefert auch den genauen 
Sekundentakt auf einer Extraleitung, welche an INT0-Pin des Mega8 
angeschlossen ist. Ziel ist es nun, dass auch der interne Timer des 
Mega8 mit diesem Signal stündlich synchronisiert wird. (Am Mega8 ist ein 
Uhrenquarz angeschlossen.)

Für diese Sekundentaktsynchronisation habe ich sinngemäß folgendes 
implementiert (der vollständige Quelltext ist hier 
http://bralug.de/wiki-common/images/9/9f/Nixie.tar.gz):
1
#define INT0_ON    GICR  |= (1<<INT0)
2
#define INT0_OFF  GICR  &= ~(1<<INT0)
3
4
ISR(TIMER2_OVF_vect)
5
{
6
    TCNT2 = 0;
7
    click = 1;
8
}
9
10
ISR(INT0_vect)
11
{
12
    TCNT2 = 255;
13
    INT0_OFF;
14
}  
15
16
17
int main(void)
18
{
19
20
    ASSR   = (1<< AS2);
21
    TCNT2  = 0;
22
    TCCR2 |= (1 << CS22) | (1 << CS20);
23
    TIMSK |= (1 << TOIE2);
24
    MCUCR |= (1<<ISC00) | (1<<ISC01);
25
    sei();
26
27
   //....
28
29
   while (1) {
30
      if (click) {
31
         add_time_date();
32
         if (synchronisieren()) INT0_ON; // ---> stark vereinfacht...
33
      }
34
35
      //....
36
   }
37
}

Idee ist es also, ab und zu mit Auslösen des "Sekundentakt-Interrupts" 
den Timer-Preloader auf 255 zu setzen, um beim nächsten Takt den 
entsprechenden Interrupt auszulösen und die Zeit weiterzuzählen.

Wenn ich mir das Ergebnis anschaue, scheint es nicht zu funktionieren, 
warum?

Darf man überhaupt einen Interrupt innerhalb der eigenen 
Interrupt-Routine ausschalten (INT0)?

Mache ich irgend etwas mit der Wertzuweisung mit TCNT2 falsch?

Macht man die Sache überhaupt so, wie ich es mir dachte, oder doch 
vollkommen anders?

Grüße & Danke Uwe
von Uwe B. (boerge) Benutzerseite


Lesenswert?

...hat keiner eine Idee zu dem Thema :-(
von (prx) A. K. (prx)


Lesenswert?

Wenn ich das richtig sehe, dann betreibst du den Timer asynchron. Dafür 
gibt im Manual ein paar Zeilen, die man lieber nicht ignorieren sollte.
von Oliver J. (skriptkiddy)


Lesenswert?

Uwe Berger schrieb:
> ISR(TIMER2_OVF_vect)
> {
>     TCNT2 = 0;
>     click = 1;
> }
Schau dir mal den CTC-Modus an.
von Karl H. (kbuchegg)


Lesenswert?

Grundregel NUmmer 1 bei Uhren:

Lass den Timer arbeiten!


ISR(TIMER2_OVF_vect)
{
    TCNT2 = 0;
    click = 1;
}

Was soll der Quatsch? Nach einem Overflow ist der Timer sowieso auf 0. 
Und das was du zurücksetzen wolltest, nämlich die Teilerstufe, die dir 
den 32kHz Takt erst mal runterteilt, hast du damit erst recht nicht 
zurückgesetzt. Wenn dein Uhrenquarz so ungenau ist, dass du nach 1 
Stunde schon eine merkbare Abweichung hast, dann schmeiss ihn weg.

> Darf man überhaupt einen Interrupt innerhalb der eigenen
Interrupt-Routine ausschalten (INT0)?

Wer will dich daran hindern?
von Uwe B. (boerge) Benutzerseite


Lesenswert?

Oliver J. schrieb:
> Schau dir mal den CTC-Modus an.
>
ja werde ich mal machen... Wenn ich es richtig verstehe wird hier der 
Timer-Wert mit einem Register (OCR1A), welches ich von der Applikation 
her einmal setze, verglichen und bei Gleichheit der entsprechende 
Interrupt ausgelöst, korrekt?

Karl Heinz Buchegger schrieb:
> Was soll der Quatsch? Nach einem Overflow ist der Timer sowieso auf 0.
> Und das was du zurücksetzen wolltest, nämlich die Teilerstufe, die dir
> den 32kHz Takt erst mal runterteilt, hast du damit erst recht nicht
> zurückgesetzt.
>
...ok, ich erahne, was du damit meinst...

Karl Heinz Buchegger schrieb:
> Wenn dein Uhrenquarz so ungenau ist, dass du nach 1
> Stunde schon eine merkbare Abweichung hast, dann schmeiss ihn weg.
>
naja, ganz so drastisch würde ich es nicht sehen. Über eine stündliche 
Synchronisierung des Sekundentakts kann man sich streiten. Vom "Gefühl" 
her verschiebt sich der Sekundentakt bei meinem Aufbau um weniger als 
0,5s pro Tag. Da würde dann wohl auch eine tägliche Synchronistation 
ausreichen.

Aber mindestens beim Einschalten der Uhr sollte man synchronisieren, um 
den Zeitpunkt des Auslösen des Timer-Interrupts mit der DCF77-Flanke 
abzugleichen und dafür suche ich eine sinnvolle Verfahrensweise...


Hmm, und da bin ich wieder eigentlich am Ausgangspunkt angekommen: wie 
löse ich einen Timerinterupt zu einem externen Ereignis so aus, dass er 
danach, ohne dieses einmalige externe Ereignis, weiterhin im 
Sekundentakt eintrifft...? Dazu muss ich doch irgendwie den internen 
Timerzähler manipulieren, oder?

Grüße & Danke Uwe
von Lutz (Gast)


Lesenswert?

Uwe Berger schrieb:
> Hmm, und da bin ich wieder eigentlich am Ausgangspunkt angekommen: wie
> löse ich einen Timerinterupt zu einem externen Ereignis so aus, dass er
> danach, ohne dieses einmalige externe Ereignis, weiterhin im
> Sekundentakt eintrifft...?

Bin mir jetzt nicht sicher, ob ich das Problem verstanden habe. Aber man 
kann in der ISR für das externe Ereignis den Interrupt daür nach 
(einmaligem) Aufruf ausschalten, den Timerinterrupt einschalten und 
einmalig manuell auslösen.
von Uwe B. (boerge) Benutzerseite


Lesenswert?

Lutz schrieb:
> und einmalig manuell auslösen.
>
wie und werden dabei die internen Timerzähler automatisch entsprechend 
zurückgesetzt?

Wenn das geht, könnte ich mir da eine Lösung vorstellen...

Grüße & Danke Uwe
von Stefan E. (sternst)


Lesenswert?

Uwe Berger schrieb:
> Hmm, und da bin ich wieder eigentlich am Ausgangspunkt angekommen: wie
> löse ich einen Timerinterupt zu einem externen Ereignis so aus, dass er
> danach, ohne dieses einmalige externe Ereignis, weiterhin im
> Sekundentakt eintrifft...? Dazu muss ich doch irgendwie den internen
> Timerzähler manipulieren, oder?

Der Ansatz im ersten Post ist schon nicht schlecht, allerdings 2 
Probleme:

1) Du musst vor dem Aktivieren des INT0-Interrupts das entsprechende 
Flag löschen, sonst kommt der Interrupt ja sofort und nicht erst beim 
nächsten Sekundensignal.

2) Der Code im INT0-Interrupt ist etwas zu simple gedacht. So kannst du 
deine interne Uhr nur beschleunigen, aber nicht verlangsamen. Wenn deine 
interne Uhr etwas vor geht, hast du durch die Synchronisierung zwei 
Sekunden-Ereignisse direkt hintereinander.
von Uwe B. (boerge) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> 1) Du musst vor dem Aktivieren des INT0-Interrupts das entsprechende
> Flag löschen, sonst kommt der Interrupt ja sofort und nicht erst beim
> nächsten Sekundensignal.
>
welches Flag meinst du?

> 2) Der Code im INT0-Interrupt ist etwas zu simple gedacht. So kannst du
> deine interne Uhr nur beschleunigen, aber nicht verlangsamen. Wenn deine
> interne Uhr etwas vor geht, hast du durch die Synchronisierung zwei
> Sekunden-Ereignisse direkt hintereinander.
>
ok, um zu verstehen, wie ich es eigentlich gedacht hatte, müsste man den 
vollständigen Code lesen (siehe Link in meinem 1.Post), das Fragment 
oben spiegelt es nicht ganz wieder, stimmt...

* zuerst wird zu jeder vollen Stunde die aktuelle Uhrzeit vom 
DCF77-Modul geholt, der entsprechende Sekundentakt sollte da schon 
vorbei sein --> es wird 13:00:00 gelesen.

* wurde eine valide Zeit gelesen, wird INT0 eingeschaltet und damit auf 
die nächste Flanke gewartet, die für 13:00:01 stehen muss (13:00:00 war 
ja schon...)

* in INT0 soll nun, durch setzen von TNTC2 auf 255, der Timer dazu 
provoziert werden, beim nächsten Takt einen Timer-Interrupt auszulösen, 
bei dem dann die (interne) Zeit hochgezählt wird 13:00:00 --> 13:00:01

So war der (gesamte) Plan...

Grüße & Danke Uwe
von Oliver J. (skriptkiddy)


Lesenswert?

Uwe Berger schrieb:
> Wenn ich es richtig verstehe wird hier der
> Timer-Wert mit einem Register (OCR1A), welches ich von der Applikation
> her einmal setze, verglichen und bei Gleichheit der entsprechende
> Interrupt ausgelöst, korrekt?
Genau so ist es. Also ist der CTC-Modus für eine RTC-Anwendung geradezu 
prädestiniert.
Aber das was du schreibst, ist nur die halbe Wahrheit. Bei Gleichheit 
(TCNT1==OCR1A) wird TCNT1 zurückgesetzt (einen Zähltakt später) und 
zählt dann erneut bis zum Erreichen von OCR1A hoch. Wenn du also OCR1A 
mit 999 beschreibst, dann wird aller 1000 Zähltakte ein Interrupt 
ausgelöst.

Der Zusammenhang zwischen dem OCR0A Register und den gewünschten 
Zähltaktzyklen ist hier dargestellt:

OCR1A = Takte - 1;


Gruß Oliver
von Stefan E. (sternst)


Lesenswert?

Uwe Berger schrieb:
> welches Flag meinst du?

Das INT0-Interrupt-Flag.
Vielleicht nochmal nachlesen, wie Interrupts auf dem AVR funktionieren?

Uwe Berger schrieb:
> * in INT0 soll nun, durch setzen von TNTC2 auf 255, der Timer dazu
> provoziert werden, beim nächsten Takt einen Timer-Interrupt auszulösen,
> bei dem dann die (interne) Zeit hochgezählt wird 13:00:00 --> 13:00:01

Aber ich denke, dass soll sich dann periodisch wiederholen, oder? Und 
wenn deine interne Uhr vor geht, überspringst du damit quasi eine 
Sekunde, und sie geht noch mehr vor. Im Falle des "Vorgehens" müsstest 
du mit TCNT2 = 0 arbeiten, um die aktuelle Sekunde zu verlängern, statt 
sie zu verkürzen.
von Uwe B. (boerge) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> Aber ich denke, dass soll sich dann periodisch wiederholen, oder? Und
> wenn deine interne Uhr vor geht, überspringst du damit quasi eine
> Sekunde, und sie geht noch mehr vor. Im Falle des "Vorgehens" müsstest
> du mit TCNT2 = 0 arbeiten, um die aktuelle Sekunde zu verlängern, statt
> sie zu verkürzen.
>
ich übernehme zuerst die aktuelle Zeit in die interne Uhr. Im Fall des 
"Vorgehens" wird die interne Zeit also zurückgestellt (wenn es mehr als 
eine Sekunde sein sollte). Dann erwarte ich den Sekundentakt für die 
nächste Sekunde und zähle (durch das zu diskutierende 
"Interrupt-Rumgeschalte") die interne Uhrzeit weiter.

Wenn ich richtig gedacht habe, sollte das für beide Fälle (Vor- und 
Nachgehen) richtig sein. Das Bildchen hatte ich dazu gemalt:

Nachgehen:

           |----------13:00:00----v-----|----------13:00:01----------|
                                  V    INT0
     |----------12:59:59----------|-00--|----------13:00:01----------|


Vorgehen:

    |-----v----12:59:59----------|----------13:00:00----------|---
          V                     INT0
    ---59-|----------12:59:59----|----------13:00:00----------|---


1.Zeile jeweils DCF77, 2.Zeile interne Uhr. Die Stellem mit V sollen das 
Übernehmen der DCF77-Zeit in die interne Uhr anzeigen.

Habe ich da einen Denkfehler?

Grüße & Danke Uwe
von Uwe B. (boerge) Benutzerseite


Lesenswert?

JNochmal ich:

Stefan Ernst schrieb:
> Das INT0-Interrupt-Flag.
> Vielleicht nochmal nachlesen, wie Interrupts auf dem AVR funktionieren?
>
stimmt, wer lesen kann (und Gelesenes versteht...) ist klar im Vorteil. 
Im Mikrocontroller.net-Artikel Interrupt steht:

"Das heisst aber nicht, dass während der Zeit der inaktiven Interrupts 
diese verloren gehen. Vielmehr wird das jeweilige Interruptbit gesetzt, 
und wenn die Interrupts wieder freigegeben werden wird der Interrupt 
ausgeführt."

Das meinst du sicherlich?

Dann ist auch klar, warum mein Code nicht funktioniert, da der INT0 
sofort losfeuert und nicht auf die nächste externe Flanke wartet. ...Und 
damit die Uhr im "alten" Takt weiterläuft, als wäre nichts gewesen. Es 
fehlte also vor dem Einschalten von INT0 nur eine Anweisung:
1
GIFR  &= ~(1<<INTF0);

Wenn es so ist (werde ich gleich mal ausprobieren), danke für diesen 
Hinweis, wieder etwas dazugelernt.

Grüße Uwe
von Stefan E. (sternst)


Lesenswert?

Uwe Berger schrieb:
> ich übernehme zuerst die aktuelle Zeit in die interne Uhr.

Achso, du meinst jedes mal, das hatte ich missverstanden.

Du hast aber trotzdem ein potentielles Problem, wenn die interne Uhr vor 
geht. Das Sekundensignal kann zwischen die Übernahme der Zeit und das 
Freigeben des Interrupts fallen. Lässt sich aber leicht in den Griff 
kriegen, indem du das Flag vor der Übernahme löschst.
von Stefan E. (sternst)


Lesenswert?

Uwe Berger schrieb:
> Es
> fehlte also vor dem Einschalten von INT0 nur eine Anweisung:
> GIFR  &= ~(1<<INTF0);

Ähm, nein. Da ist nochmal Lesen angesagt. ;-)
Im Datenblatt die Beschreibung zu INTF0.
von Uwe B. (boerge) Benutzerseite


Lesenswert?

Stefan Ernst schrieb:
> Ähm, nein. Da ist nochmal Lesen angesagt. ;-)
> Im Datenblatt die Beschreibung zu INTF0.
>
"Alternatively, the flag
can be cleared by writing a logical one to it."

also umgekehrt:
1
GIFR  |= (1<<INTF0)
von Stefan E. (sternst)


Lesenswert?

Uwe Berger schrieb:
> also umgekehrt:
> GIFR  |= (1<<INTF0)

Das löscht jetzt zwar das Flag, aber nicht nur das, sondern gleich alle 
gesetzten Flags in dem Register.
von Uwe B. (boerge) Benutzerseite


Lesenswert?

ok, noch etwas weiter gelesen :-) und habe etwas von KHB gefunden. Also 
dann sollte es so richtig sein:
1
GIFR  = (1<<INTF0);
von Niffko _. (niffko)


Lesenswert?

jaja, das ist schon etwas tricky. Für's Protokoll, hier noch mal ein 
Link zur Erläuterung:

Beitrag "Re: ATtiny45: Seltsamer Effekt nach Löschen eines Interrupt-Flags"


//Niffko
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Mir fehlt irgendwie das Zurücksetzen von click , also die zeile
click = 0 ;
wenn die Synchronisierung fertig ist.
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.