Hi!
Ich beobachte gerade bei meiner Discolicht-Binäruhr-Kombination einen
interessanten Konflikt zwischen den Timern 0 und 1.
Bevor ich hier den ganzen Code poste möchte ich mein Problem erstmal so
schildern:
Timer0 läuft mit Vorteiler 8. Parallel dazu läuft der Timer1 mit
Vorteiler 64. Zuerst wird Timer0 gestartet und dann gleich Timer1, sie
dürften also nahezu synchron zueinander laufen.
Der Timer0 OVF ISR habe ich das Attribut ISR_NOBLOCK gegeben, so dass
sie die ISRs des Timer1 nicht behindert. Hier wird ein bisschen
Rechenzeit für das LED-Lauflicht verwendet.
Die Timer1 OVF ISR setzt PORT und DDR fürs Charlieplexing, danach wird
sie durch ein sei(); entschärft und führt dann weitere unwichtige
Codeteile aus. D.h. sie braucht auf jedenfall einige Takte am Anfang für
sich und gibt andere Interrupts erst danach wieder frei.
Die Timer1 COMPA ISR löscht PORT und DDR fürs Charlieplexing wodurch ich
die LEDs dort dimmen kann. In der niedrigsten Stufe bleiben zwischen der
OVF-ISR und der COMPA-ISR gerademal 64 Takte Zeit, aber das ist soweit
schaffbar. Wenn ich Timer0 deaktiviert habe, klappt alles.
Nun das komische: Sobald ich Timer0 aktiviere fangen die Uhr-LEDs bei
runterdimmen in der dunkelsten Stufe nochmal an aufzublitzen.
Hier mal eine Art Timeline: (+ = OVF-ISR, # = COMP-ISR)
Timer1: +#--- ----- ----- ----- ----- ----- ----- ----- +#---
Timer0: +---- +---- +---- +---- +---- +---- +---- +---- +----
Ich kann mir das nun nicht richtig erklären. Dass die LEDs aufblitzen
deutet darauf hin, dass die COMPA-ISR (in der die LEDs ja abgeschaltet
werden) übersprungen wird und der Timer eine Runde voll durchläuft. Das
kann aber eignetlich nicht sein, denn das Flag ist ja gesetzt. Timer1
hat eine höhere Priorität als Timer0 und in der Timer0 OVF-ISR dauert es
bis zum erneuten Setzen des I-Flags eigentlich nur 4 Takte. Ohne mir
wirklich ernsthafte Gedanken über das Timing zu machen habe ich einfach
mal versucht den Timer0 mit dem Wert 127 vorzuladen wodurch er nun nicht
mehr synchron mit dem Timer1 läuft sondern um die hälfte versetzt. Nun
flackert nix mehr und alles klappt.
Timer1: +#--- ----- ----- ----- ----- ----- ----- ----- +#---
Timer0: --+-- --+-- --+-- --+-- --+-- --+-- --+-- --+-- --+--
Das ist zwar schön und gut, aber ich würde gerne nachvollziehen können
warum ;-)
lg PoWl
Ich denke es hängt damit zusammen, dass alleine durch den Aufruf der ISR
ja zig Takte benötigt werden (der Interrupt, der Sprung in die ISR, die
register die in der ISR gesichert werden, die Register die am Ende der
ISR wieder zurückgeschrieben werden).
Ich würde das ganze jetzt erstmal step-by-step durchdebuggen und gucken
wo er sich da verhaspelt.
du meinst durch den aufruf der Timer0 OVF-ISR? Nein, das habe ich im
C-Code so implementiert:
1
ISR(TIMER0_OVF0_vect,ISR_NOBLOCK)
2
{
3
...
d.h. die Interrupts werden sofort nach dem Ansprung der ISR wieder
aktiviert. In Assembler sieht das dann so aus:
1
00000464 <__vector_6>:
2
464: 78 94 sei
3
466: 1f 92 push r1
4
468: 0f 92 push r0
5
46a: 0f b6 in r0, 0x3f ; 63
6
46c: 0f 92 push r0
7
46e: 11 24 eor r1, r1
8
470: 2f 93 push r18
9
...
gleich am Anfang der ISR steht somit das sei wodurch hier höchstens
ein paar Takte verloren gehen (4? 5?).
Es ist noch zu beachten, dass der Timer0 vor Timer1 gestartet wird, d.h.
er läuft ein paar Takte vor (wenn ich TCNT0 nicht vorlade) und die
Timer0 OVF-ISR wird vor der Timer1 OVF-ISR ausgeführt. Das erklärt aber
immernoch nicht ganz, was da passiert. Ich zeig euch mal die relevanten
Codeteile der Timer1-ISRs:
Hier wird PORTA und DDRA fürs Charlieplexing gesetzt und OCR1A mit dem
neuen Wert geladen (in diesem Fall müsste das ja dann 1 sein, also
blieben 64 Takte bis zur Ausführung). Wenn OCR1A rechtzeitig neu gesetzt
wird und die OVF-ISR einfach nur zu lange dauert müsste das COMPA
Interrupt Flag trotzdem gesetzt werden und die ISR gleich danach
ausgeführt werden. Ich probier da wohl gleich mal etwa rum.
So, das viele blabla hätte ich mir sparen können. Es war so wie ich mir
gedacht habe ;-) Der Timer0-OVF hat den Aufruf der Timer1-OVR wohl
gerade ein paar Takte blockiert, so dass OCR1A zu spät neu gesetzt wurde
und der Timer1 einmal die Runde gedreht hat.
So klappt es nun auch ohne Vorladen des TCNT0 registerts.
Vorsichtshalber habe ich nun mal noch den Start des Timer0 hinter den
des Timer1 gelegt.
1
ISR(TIMER1_OVF1_vect)
2
{
3
// OCR1A gleich am Anfang neu besetzen, damit die CMPA-ISR nicht übersprungen wird
4
OCR1A=OCRvalue;
5
6
if(OCRvalue>0)
7
{
8
PORTA=port|0b10000000;
9
DDRA=phase_DDR[phase]|port;
10
}
11
12
sei();
13
[...]
14
}
Danke für die Hilfe!
Heiei, das ist echt kompliziert. Noch vor ein paar Wochen wär ich an
sowas verzweifelt!
lg PoWl
>Heiei, das ist echt kompliziert.
Naja, du machst es vielleicht nur unnötig kompliziert. Sind
unterbrechbare ISR's usw. wirklich erforderlich?
Zum Beispiel in einer deiner letzten Anfragen mit Code:
Beitrag "In ISR wird pointer nicht gescheit gesetzt?"
Da wird die aktuelle Szene in der ISR gewechselt. Damit fängst du dir
einen Haufen Probleme ein, angefangen von den atomaren Zugriffen. Zwar
kannts du das im Hauptptogramm mit cli()/sei()-Klammerungen lösen, daber
dann ist so ziemlich die ganze Hauptschleife ununterbrechbar.
Ich vermute, eine ISR der Form
1
ISR(TIMER1_OVF1_vect)
2
{
3
szenenwechsel=TRUE;
4
}
mit Abfrage des Flags und Durchführung des Szenewechsels im
Hauptprogramm, an einer genau definierten Stelle, erfüllt die
Anforderungen ans Programm genauso, und löst alle Probleme mit atomaren
Zugriffen und sämtliche Timingprobleme.
Oliver
Hm, entweder ich deaktiviere den Timer0 ganz oder ich mache die ISR
unterbrechbar, denn sonst behindert sie den Timer1 und dessen Timing ist
ziemlich knapp kalkuliert.
In der Timer0 OVF-ISR wird nur der nächste Schritt der Lichtszene
ausgeführt. In der Timer1 OVF-ISR wird der Szenenwechsel durchgeführt.
Beide sind jedoch zu diesem Punkt unterbrechbar. Praktisch wird es nicht
passieren, dass der Timer0 OVF genau in den schreib-befehl des
Szenenadresszeigers fällt, aber theoretisch könnte das natürlich
passieren. Das ließe sich wiederum entweder ganz einfach durch ein
cli(); und sei(); vor und nach dem Schreibbefehl lösen oder indem ich
sowohl die Routine für den Szenenwechsel als auch die für den nächsten
Szenenschritt in das Hauptprogramm packe.
Praktisch ist es eigentlich egal wie ich es mache. Ich war eben etwas
auf den Lerneffekt aus und wollte mal gucken wie es mit den beiden
Timern läuft. Wenn ich alles in den mainloop packe und sequentiell
ausführe, müsste ich nur ein paar "Vorteiler" für die ADC-Messungen,
Abfrage des Tasters (Prellen) usw.. einbauen, damit diese nur alle paar
ms ausgeführt werden, macht eigentlich auch nix. Je nachdem wie viel
Code darin durch die ganzen ifs ausgeführt wird läuft der loop mal
schneller, mal langsamer, verglichen zu den 100µs Wartezeit am
Schleifenende ist das jedoch an den LEDs in der Praxis nicht
festzustellen. Es funktioniert also beides und du hast natürlich recht
damit, dass es somit komplizierter wird. Ich dachte auch schon daran den
Szenenwechsel durch den Timer0 nur zu triggern.
Im Anhang mal meinen Code, falls noch grobe Fahrlässigkeiten entdeckt
werden kann ich die gerne ändern. Danke, dass ihr euch mit meinem
Anliegen beschäftigt habt!
lg PoWl