Forum: Mikrocontroller und Digitale Elektronik ATtiny45: Seltsamer Effekt nach Löschen eines Interrupt-Flags


von Edi R. (edi_r)


Angehängte Dateien:

Lesenswert?

Bei einem (funktionierenden) Stromregler für eine LED mit dem ATtiny45 
wollte ich zur Helligkeitseinstellung eine Dimmfunktion per Software-PWM 
einbauen, was aber nicht klappte.

Zur Beschreibung hier im Forum habe ich die Software stark vereinfacht, 
der beobachtete Effekt ist aber noch erhalten geblieben. Die Beschaltung 
des ATtiny45 ist sehr einfach: Außer der Versorgung durch ein 
5-V-Netzteil, einem 100-nF-Stützkondensator an der Versorgung und den 
ISP-Anschlüssen ist nur noch mein Logikanalysator (Intronix LogikPort) 
angeschlossen. Der Effekt kommt also nicht von einer äußeren 
Beschaltung.

Der Stromregler nutzt den Fast-PWM-Modus von Timer1 des ATTiny45. In der 
tatsächlichen Schaltung wird der Timer1 mit dem 64-MHz-PLL-Takt 
betrieben. Weil das zur Veranschaulichung nicht erforderlich ist, habe 
ich hier den Timer1 mit dem internen Clock von 8 MHz getaktet.

Für den Stromregler wird der ADC gestartet, während der PWM-Ausgang auf 
HI ist. Dazu verwende ich den Output-Compare-Interrupt B. Im Quelltext 
wird aber nicht der ADC gestartet, sondern nur ein kurzer Impuls auf PB3 
augegeben, weil ich klären wollte, ob der Effekt mit dem ADC zu tun hat. 
(Hat er aber nicht.)

Normalerweise wäre der Ablauf so: Wenn der ADC fertig ist, wird der 
Timer-1-Compare-B-Interrupt aktiviert und das dazugehörige 
Interrupt-Flag gelöscht. Beim nächsten Timer-1-Compare-B-Interrupt (das 
wäre kurz nachdem der PWM-Ausgang auf HI gegangen ist) wird der ADC 
wieder gestartet, und der Timer-1-Compare-B-Interrupt deaktiviert sich 
in seiner ISR selbst. Das läuft so im endlosen Wechsel.

Zum Dimmen wollte ich die PWM im richtigen Verhältnis ein- und 
ausschalten. Als Zeitbasis für den Dimmvorgang verwende ich den 
freilaufenden Timer0. Beim Overrun-Interrupt wird die PWM aktiviert, 
beim Output-Compare-A-Interrupt wird der PWM-Ausgang auf LO geschaltet. 
(Das Dimmen erfolgt also mit einer langsameren sekundären Software-PWM, 
aber ich vermeide hier diese Bezeichnung, damit keine Verwirrung mit der 
Stromregler-PWM entsteht, und schreibe stattdessen nur vom Dimmen.) 
Dabei konnte es vorkommen, dass - verursacht durch eine andere ISR - die 
Timer-1-Compare-B-ISR (zum Starten des ADC) verspätet ausgeführt wird. 
Das macht Probleme, weil der Zeitpunkt der Messung innerhalb der 
PWM-Phase verschoben wird. Um dieses Problem zu umgehen, habe ich am 
Ende jeder ISR das Timer-1-Compare-B-Interrupt-Flag gelöscht. Falls es 
schon gesetzt war, wäre der nächste ADC-Start einfach einen PWM-Zyklus 
später gekommen, dann allerdings zum richtigen Zeitpunkt innerhalb des 
PWM-Zyklus.

Zur Bestimmung des Dimmfaktors brauche ich noch einen weiteren 
Interrupt, der in regelmäßigen Abständen kommen soll. Dazu habe ich den 
Timer0-Output-Compare-B-Interrupt eingesetzt. Das 
Output-Compare-Register wird in der ISR immer ein Stückchen weiter 
gesetzt, so dass eine einigermaßen regelmäßige Folge von Interrupts für 
die Abfrage des Dimmfaktors entsteht.

Zu meiner Überraschung zeigte sich ein Effekt, den ich mir nicht 
erklären kann: Die Erzeugung dieser Abfrage-Interrupts ist abhängig vom 
Dimmfaktor, aber nur, wenn man - wie weiter oben erwähnt - immer wieder 
die Interruptflags des Timer1-Output-Compare-Interrupts löscht! Wenn man 
die betreffenden Zeilen (im Quelltext mit !!! markiert) auskommentiert, 
laufen die Timer0-Output-Compare-Interrupts problemlos. Das heißt 
konkret: Wenn das Interrupt-Flag laufend gelöscht wird und der 
Dimmfaktor geradzahlig ist (0, 2, 4, ... 254), dann werden die 
Timer0-Output-Compare-B-Interrupts nicht ausgelöst!

Ich sehe überhaupt keinen Zusammenhang. Kann mir da vielleicht jemand 
weiterhelfen?

Damit man die Sache von außen verfolgen kann, setze ich die Portpins 
ein:
PB0 (gelbe Linie): Regelmäßige Interrupts (die ggf. ausbleiben)
PB1 (obere grüne Linie): PWM-Ausgang
PB2 (rote Linie): Dimmen (PWM ein oder aus)
PB3 (untere grüne Linie): simulierter ADC-Start

Meine Konfiguration: AVR-gcc aus WinAVR-20090313 unter AVR-Studio 
4.15.623; ISP-Programmer: AVR Dragon

von Edi R. (edi_r)


Lesenswert?

Dass das Thema nicht so einfach ist, ist schon klar, und dass hier 
keinen Ansturm von Antworten gibt, war auch klar. Aber hat wirklich 
keiner eine Idee, was da vor sich geht? Ich lösche ein Interrupt-Flag 
von Timer 1, und es wirkt sich auf Timer 0 aus, und niemand kann mir 
sagen, warum?

von (prx) A. K. (prx)


Lesenswert?

Vielleicht ging es den anderen so wie mir und sie haben noch in der 
ersten Halbzeit deines Textes den Faden verloren.

von ETLER (Gast)


Lesenswert?

Naja die problematik ist zu wirr geschrieben.............

von (prx) A. K. (prx)


Lesenswert?

Es könnte helfen erst einmal damit anzufangen, was da eigentlich 
passieren soll. Also ob da vielleicht in Abhängigkeit vom ADC die PWM 
gesteuert werden soll. Oder was auch immer.

von Edi R. (edi_r)


Lesenswert?

Das hab ich befürchtet. Aber ich weiß nicht, wie ich das alles kürzer 
beschreiben soll. Es wäre nicht mehr alles an Informationen enthalten, 
die man braucht.

Wenn sich jemand auf dieses Abenteuer einlassen will: ATtiny45 aufs 
Steckbrett, mit 5V versorgen, das beigefügte Programm brennen und 
ausprobieren - erst mit "Dimmfaktor = 3", dann mit "Dimmfaktor = 4". 
Danach die Zeilen mit "!!!" auskommentieren, und wieder "Dimmfaktor = 
3", "Dimmfaktor = 4". Jeweils PB0 beobachten.

von (prx) A. K. (prx)


Lesenswert?

Sorry, aber bislang bleibt das für mich ein klassischer Fall von "vor 
lauter Bäumen den Wald nicht sehen", weil mir das eigentliche Ziel 
verborgen bleibt.

von Karl H. (kbuchegg)


Lesenswert?

So

  TIFR |= (1<<OCF1B);  // Interrupt-Flag löschen !!!

löscht du nicht nur das eine interessierende Bit, sondern alle Bits im 
TIFR. Wenn du nur das OCF1B Bit löschen willst, dann machst du

  TIFR = (1<<OCF1B);  // Interrupt-Flag löschen !!!

also eine stink normale Zuweisung.

Zur Erklärung:
Es werden alle Bits gelöscht, bei denen du bei einer Zuweisung ein 1 Bit 
hast.
Bei

    TIFR = TIFR | (1<<OCF1B);

sind aber alle Bits auf 1, die sowieso in TIFR schon gesetzt sind und 
zusätzlich noch das OCF1B Bit. Das willst du aber nicht. Du willst, dass 
die anderen Bits in Ruhe gelassen werden! Also muss an deren Bitposition 
eine 0 sein.

Das ganze ist absichtlich deswegen so gemacht worden, damit man einzelne 
Bits zurücksetzen kann, ohne vorher das Register auslesen zu müssen. Ein 
atomarer Zugriff ist daher viel einfacher zu realisieren.


Ob das dein Problem ist, kann ich nicht sagen (hab nicht analysiert was 
sonst noch so abgeht). Ist mir beim Drüberlesen aufgefallen.

von Edi R. (edi_r)


Lesenswert?

Ich versuche nochmal zu beschreiben, was ich erreichen will:

Mit Timer1 wird eine PWM an PB1 erzeugt. Wenige µs nachdem PB1 von LO 
auf HI gegangen ist, soll der ADC gestartet werden. Dazu verwende ich 
den Output-Compare B des Timer1, in dessen ISR der ADC angestoßen wird. 
Die PWM-Frequenz ist ca. 125 kHz.

(Soweit ist das auch problemlos gelaufen.)

Parallel dazu baue ich mit dem bisher unbenutzten Timer0 über seinen 
Overflow-Interrupt und dem Output-Compare A eine Software-PWM, mit der 
ich die Hardware-PWM von oben (Timer1) ein- und ausschalte. Die 
PWM-Frequenz ist ca. 400 Hz (weiß ich jetzt nicht so genau).

(Auch das hat funktioniert.)

Durch die zusätzlichen ISRs, die von Timer0 dazugekommen sind, kann es 
passieren, dass die "wenigen µs" plötzlich deutlich mehr werden, wenn 
gerade eine der ISRs ausgeführt wird. Also habe ich mir gedacht: Kein 
Problem, dann lösche ich das Interrupt-Flag von Timer1, dann kommt der 
Interrupt eben beim nächsten Zyklus von Timer 1 - das ist ja nur 8 µs 
später. Der ADC wird dann etwas später gestartet, aber der Abstand der 
ADC-Messung zur Lo-Hi-Flanke passt wieder.

(Auch das geht noch.)

Jetzt brauche ich aber noch einen regelmäßigen Interrupt, den ich mit 
Output-Compare B von Timer0 aufbauen wollte.

Und das geht nicht, wenn ich die Interrupt-Flags von Timer1 in den ISRs 
lösche. Was hat das Interrupt-Flag von Timer1 mit dem Output Compare von 
Timer 0 zu tun?

von Edi R. (edi_r)


Lesenswert?

@ Karl heinz Buchegger (kbuchegg):

Mein letzter Beitrag hat sich mit Deinem Beitrag überschnitten. 
Wahrscheinlich ist das die Lösung zum Problem. Ich danke Dir vielmals 
für den Hinweis.

Jetzt werde ich das gleich mal ausprobieren und das Ergebnis hier 
mitteilen :-)

von (prx) A. K. (prx)


Lesenswert?

Wenn ich dich richtig verstehe, dann willst du alle 8µs eine ADC-Messung 
durchführen. Mit einem ADC, der für eine Messung mindestens 65µs 
benötigt, stelle ich mir das schwierig vor.

von (prx) A. K. (prx)


Lesenswert?

Muss der Tiny sonst noch was erledigen, oder ist das alles was er tun 
soll?

Denn da den AVRs priorisierte Interrupts fehlen, du aber ein Gewirr von 
Interrupts mit teils ausgesprochen kurzen Zeiten fabrizierst bei denen 
mangels Gigaherz auch mal welche verschütt gehen können, kommt mir ein 
gänzlich anderes Modell in den Sinn.

Beschränke dich in deinen Interrupts auf den Teil wo dir die Reaktion 
offenbar wichtig ist, d.h. auf die von der PWM abgeleitete ADC 
Steuerung. Und mach den zeitlich weit weniger kritischen Rest ohne 
Interrupts im Hauptprogramm, ggf. auf gepollte Timer-Flags wartend.

von Edi R. (edi_r)


Lesenswert?

>   TIFR |= (1<<OCF1B);  // Interrupt-Flag löschen !!!
> löscht du nicht nur das eine interessierende Bit, sondern alle Bits im
> TIFR. Wenn du nur das OCF1B Bit löschen willst, dann machst du
>   TIFR = (1<<OCF1B);  // Interrupt-Flag löschen !!!
> also eine stink normale Zuweisung.

*Das war's!*

Vielen Dank an kbuchegg für die Lösung, aber auch an alle anderen, die 
sich zumindest das Problem angeschaut haben, speziell an prx und ETLER 
(Gast) für ihre Beiträge  :-)

@ prx:

> Wenn ich dich richtig verstehe, dann willst du alle 8µs eine ADC-Messung
> durchführen.

Aber nein. Dass das nicht geht, weiß ich. Ich wollte nur immer eine 
winzige Zeit, nachdem Timer1 übergelaufen ist, den ADC starten. Beim 
Start wird der Messwert dann in relativ kurzer Zeit gesampelt, das 
Auswerten (Messen) kann dann ruhig dauern. Wenn der ADC in aller Ruhe 
fertig ist, warte ich wieder auf den "richtigen" Zeitpunkt, dann starte 
ich den ADC wieder. Die 8 µs sind nur das Zeitraster für den "richtigen" 
Startzeitpunkt.

> Muss der Tiny sonst noch was erledigen, oder ist das alles was er tun
> soll?

:-) Du wirst lachen, aber der schafft das alles! Ich bin selbst 
überrascht, was in dem kleinen Achtbeiner an Leistung steckt. Natürlich 
muss man gelegentlich ein wenig tricksen, aber trotzdem.

Ich weiß jetzt nicht, ob ich schon erwähnt habe, was ich damit vorhabe: 
Ich möchte einen Step-Down-Regler für eine Power-LED bauen. Der 
Hardwareaufwand und die Baugröße sollen sich in Grenzen halten, aber ich 
will eine Dimmung in mehreren Stufen haben. Mein erster Ansatz war ein 
Hardware-Schaltregler, der von einem ATtiny geschaltet wird, aber das 
waren mir schon zu viele Bauteile, und der ATtiny hat sich gelangweilt. 
Dann wollte ich probieren, ob ein ATtiny45 das alleine kann. Er kann :-)

Zum Messen des Stroms habe ich einen Widerstand zwischen Source des 
Schalt-MOSFETs und GND, an dem ich den Spannungsabfall aus dem Strom 
durch die Speicherdrossel ermittle. Dummerweise fließt dieser Strom 
natürlich nur, solange der MOSFET eingeschaltet ist, danach fließt der 
Strom durch die Schottky-Diode weiter. Deshalb hat die Messung nur dann 
einen Sinn, wenn sie während der ON-Phase gestartet wird. Außerdem ist 
der Strom natürlich nicht konstant, sondern (bei richtiger 
Drosselauslegung) dreieckförmig. Den mittleren Strom erhält man, wenn 
man in der Mitte der ON-Zeit misst. Deshalb wollte ich nicht dann 
messen, wenn der PWM-Ausgang auf HI geht, sondern kurz danach (abhängig 
von der Länge der ON-Zeit). Das übliche Verfahren mit der symmetrischen 
PWM wollte ich nicht verwenden, weil ich mit der höchsten möglichen 
Frequenz schalten will, um die Speicherdrossel klein zu halten.

> Beschränke dich in deinen Interrupts auf den Teil wo dir die Reaktion
> offenbar wichtig ist, d.h. auf die von der PWM abgeleitete ADC
> Steuerung. Und mach den zeitlich weit weniger kritischen Rest ohne
> Interrupts im Hauptprogramm, ggf. auf gepollte Timer-Flags wartend.

Keine Angst, das mache ich, sonst würde es nicht gehen. Interrupts 
verwende ich recht gerne, aber immer nur so kurz wie möglich. Die meiste 
Zeit hält sich der Program Counter im Hauptprogramm auf.

Wenn mein Projekt fix und fertig ist, stelle ich es gerne mit Schaltplan 
und Quelltext hier vor - als Beispiel, was man dem ATtiny alles 
aufbürden kann.

Beitrag #5954527 wurde vom Autor gelöscht.
von Rupert B. (mr_dojo0)


Lesenswert?

Karl H. schrieb:
> Zur Erklärung:
> Es werden alle Bits gelöscht, bei denen du bei einer Zuweisung ein 1 Bit
> hast.
> Bei
>
>     TIFR = TIFR | (1<<OCF1B);
>
> sind aber alle Bits auf 1, die sowieso in TIFR schon gesetzt sind und
> zusätzlich noch das OCF1B Bit. Das willst du aber nicht. Du willst, dass
> die anderen Bits in Ruhe gelassen werden! Also muss an deren Bitposition
> eine 0 sein.

Leck mich fett ich hab grad dank dir einen Fehler gefunden, den ich in
meinem Projekt hab, das nächste Woche an den Kunden soll.. VIELEN VIELEN
DANK :D

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.