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
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?
Vielleicht ging es den anderen so wie mir und sie haben noch in der ersten Halbzeit deines Textes den Faden verloren.
Naja die problematik ist zu wirr geschrieben.............
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.
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.
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.
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.
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?
@ 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 :-)
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.
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.
> 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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.