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.
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 | }
|
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.