Forum: Compiler & IDEs ATtiny26, Timer0 und Timer1 kommen sich in die Quere


von Paul H. (powl)


Lesenswert?

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

von Timmo H. (masterfx)


Lesenswert?

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.

von Paul H. (powl)


Lesenswert?

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.
1
ISR(TIMER1_OVF1_vect)
2
{
3
  if(OCRvalue > 0)
4
  {
5
    PORTA = port | 0b10000000;
6
    DDRA = phase_DDR[phase] | port;
7
8
    OCR1A = OCRvalue;
9
  }
10
11
  sei();
12
13
  [...]
14
}
15
16
ISR(TIMER1_CMPA_vect)
17
{
18
  PORTA = 0b10000000;
19
}

von Paul H. (powl)


Lesenswert?

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

von Oliver (Gast)


Lesenswert?

>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

von Paul H. (powl)


Angehängte Dateien:

Lesenswert?

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

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.