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