mikrocontroller.net

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


Autor: Edi R. (edi_r)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: A. K. (prx)
Datum:

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

Autor: ETLER (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Naja die problematik ist zu wirr geschrieben.............

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Edi R. (edi_r)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.