Hi,
ich habe 27 LEDs, welche ich per Multiplexing ansteuere. Konkret handelt
es sich um einen LED Cube nach diesem Schaltplan:
http://www.ledstyles.de/index.php?page=Attachment&attachmentID=5162&h=14fd27d042c0853320f7f045e11838b113fe8373
Als Mikrocontroller verwende ich einen ATmega88. Dieser bietet mir über
den Timer/Counter0 die Möglichkeit Hardware PWM zu realisieren. Jetzt
stellt sich mir die Frage, inwiefern es möglich wäre mit nur einer
Hardware PWM Leitung (nämlich Pin PB1) die Helligkeit aller LEDs zu
regulieren.
Ich dachte mir nun, dass ich einen weiteren Transistor einbaue, dessen
Basis an PB1 kommt und der über Kollektor und Emitter die anderen drei
Transistoren ein- und ausschaltet (bzw. dessen Stromfluss zwischen
Emitter und GND).
Prinzipiell müsste das klappen, aber jetzt stellt sich das Problem, dass
ich die Hardware PWM irgendwie mit dem Multiplexing synchronisieren
müsste, damit sich das PWM wirklich auf die aktuell leuchtende Ebene
bezieht und nicht noch Ein- bzw. Auszeiten von der Ebene "davor"
übernommen werden.
Wahrscheinlich könnte man die Hardware PWM Zeiten einfach klein genug
wählen, sodass der Effekt nicht so stark ausfällt, aber gefallen tut mir
das trotzdem nicht. Wie würdet ihr das Ganze lösen?
Ich hoffe, dass ich das Problem verständlich erläutern konnte.
Unlucky2012 schrieb:> Prinzipiell müsste das klappen, aber jetzt stellt sich das Problem, dass> ich die Hardware PWM irgendwie mit dem Multiplexing synchronisieren> müsste
Du nimmst einfach für beides den gleichen Timer, Mode: Fast-PWM.
Peter
Peter Dannegger schrieb:> Unlucky2012 schrieb:>> Prinzipiell müsste das klappen, aber jetzt stellt sich das Problem, dass>> ich die Hardware PWM irgendwie mit dem Multiplexing synchronisieren>> müsste>> Du nimmst einfach für beides den gleichen Timer, Mode: Fast-PWM.
Warum einfach, wenn es auch kompliziert geht ;). Danke!
Peter Dannegger schrieb:> Du nimmst einfach für beides den gleichen Timer, Mode: Fast-PWM.
Wäre es möglich das nochmals ein wenig genauer zu beschreiben? Welche
Interrupts soll/muss ich verarbeiten. Ich habe mir das Ganze jetzt so
vorgestellt:
Ich benutze Timer/Counter0 mit einem Prescaler, den ich so wähle, dass
der Bereich um 100 Hertz "optimal" eingestellt werden kann.
Interrupt-technisch reagiere ich jeweils auf Timer Overflows. In der
Interrupt Service Routine wechsle ich dann jeweils die aktuelle Ebene
(Multiplexing eben). Parallel dazu benutze ich den Output Compare
(OC0A), um über den Fast PWM Modus mein PWM zu implementieren. Dabei
setze ich OC0A zunächst auf MAX (bei nicht invertierter PWM Logik), um
ein konstantes Signal zu erhalten. Bei Bedarf senke ich diesen Wert
dann.
Ich wäre sehr erfreut, wenn das eben jemand bestätigen könnte, oder aber
mir aufzeigen könnte wo ich hier Denkfehler mache. Das Timer/Counter
Kapitel im Datenblatt ist wohl nicht das Einfachste ;).
Vielen Dank schon einmal!
Übrigens: Der Grund warum ich noch einmal nachfrage ist, dass meine oben
präsentierte Lösung, zumindest nach meinem Verständnis, noch nicht
"perfekt" ist.
Da ich zumindest noch einen anderen Interrupt verarbeiten muss, kann es
natürlich vorkommen, dass der Timer/Counter0 Interrupt nicht "sofort"
ausgeführt wird, sondern um ein paar Takte verzögert wird. Dadurch
würden die LEDs aber etwas länger an (bzw. bei invertierter Logik aus)
bleiben als gewollt. Mir ist bisher jedenfalls kein besserer Ansatz
eingefallen und ich vermute, dass dies aufgrund der fehlenden
Möglichkeit Interrupts zu verschalten, auch nicht möglich ist (ohne
jetzt auf irgendwelche "dreckigen" Hacks zurückzugreifen, bei welchen
man innerhalb einer ISR Interrupts wieder aktiviert, usw.).
Was sagt ihr dazu?
> Ich dachte mir nun, dass ich einen weiteren Transistor einbaue
Warum sollte man eine Lösung wählen,
die ein zusätzliches Bauelement kostet,
wenn man es auch komplett in Software erledigen könnte,
ganz ohne zusätzlichen Timer ?
Andere Leute schaffen das auch...
MaWin schrieb:> Warum sollte man eine Lösung wählen,> die ein zusätzliches Bauelement kostet,> wenn man es auch komplett in Software erledigen könnte,> ganz ohne zusätzlichen Timer ?
Das zusätzliche Bauelement kostet 4 Cent. Ich denke, dass man das (im
Hobbybereich) nicht unbedingt als Kosten bezeichnen kann.
Die Software Lösungen halte ich persönlich für "Ausweichlösungen" (und
so verstehe ich auch diverse Anleitungen und Wiki Artikel), wenn man
entweder mehr PWM Leitungen braucht als man zur Verfügung hat oder keine
Timer mehr zur Verfügung hat, weil diese mit anderen Dingen beschäftigt
sind.
Beides trifft bei mir jedenfalls nicht zu und wieso soll ich nicht etwas
verwenden, was der Mikrocontroller mir bietet.
Peter Dannegger schrieb:> Beitrag "Jumbo-LED Uhr"
Danke. Schaue ich mir in jedem Fall näher an.
An sich bietet sich hier eine Software-Lösung wirklich an. Die Frequenz
zum umschalten der LED ebenen ist relativ niedrig und entsprechend hat
man genug Zeit um PWM auch in Software zu machen. Dabei solle man PWM am
besten auch nur auf die Länge eines Pulses beziehen, also einfach die
Zeit variabel machen die die LEDs einer Ebene an sind.
Geht es darum alle LEDs einer Ebene gleich zu dimmen, variiert man
einfach die Zeit bis zum Umschalten auf die nächste Ebene und fügt dann
noch ein mehr oder weniger lange Pause ein. Für die Variable Zeit kann
da natürlich auch den Timer nutzen. Sollen alle LEDs einzeln gedimmt
werden, fehlen für HW PWM ohnehin die Kanäle - für Software PWM reicht
die Zeit schon recht weit, zumindest wenn man es effektiv in ASM
programmiert, aber selbst in C sollen bei so wenig LED noch 8 Bits
machbar sein.
Andererseits, der Baustein hat 4 HW-PWM, es würde genügen, die Pins
anders zu verteilen. Aber warscheinlich ist es der ähnliche Aufwand als
SW pwm zu
betreiben.
Ok, vielen Dank. Werde mich dann wohl doch zu der Lösung mittels
Software PWM entschließen. Gibt es eigentlich eine Möglichkeit im
Vorfeld zu überprüfen, ob der Mikrocontroller das Ganze mitmacht? Die
nächste Stufe wäre nämlich ein 8x8x8 Würfel, d.h. es wären jeweils 64
LEDs zu steuern (über entsprechende Latches natürlich).
Wie sieht es hier mit Software PWM aus. Oder ist ein ATmega32 hier schon
zu schwach auf der Brust?
> Die Software Lösungen halte ich persönlich für "Ausweichlösungen"
Andere dachten, daß Mikrocontroller genau dazu erfunden wurden.
Na so unterschiedlich sind die Menschen.
Michael Bertrandt schrieb:> Andere dachten, daß Mikrocontroller genau dazu erfunden wurden.
Das Problem das ich mit Software PWM habe ist halt, dass es nicht
wirklich 100% genau ist. Auf ein Interrupt kann auch mal verspätet (oder
im schlimmstem Fall gar nicht) reagiert werden. Beim Hardware PWM habe
ich solche Probleme i.A. nicht, wenn der Timer/Counter einmal
eingestellt ist wird da stets fröhlich vor sich her geschalten, ganz
unabhängig davon was im eigentlichen Programm gerade passiert.
Ich hoffe einfach mal, dass sich so etwas durch "vernünftige"
Programmierung in den Griff kriegen lässt.
So lange man nur eine Interruptquelle hat, und das sollte hier der Fall
sein, gibt es eigentlich keinen Grund einen Interrupt zu verpassen. Auch
die Verzögerungen halten sich normal in Grenzen. Man muss dabei auch
nicht mit Interupts arbeiten - es geht auch einfach über die Laufzeit,
zumindest wenn man in ASM Programmiert. Ohne ASM ist die Software PWM
Lösung auch deutlich langsamer. Den Code für 8 Bit Software PWM kriegt
man in ASM sehr gut hin - das ist eines der Beispiele wo ein C-Compiler
mit der Optimierung an die Grenzen stößt weil z.B. C keine 1 Bit
Variablen kennt.
So lange man nur jede Ebene als Ganzen Dimmen will, ist die
Softwarelösung auch bei 8x8x8 LEDs kein Problem. Will man da aber jede
LED einzeln dimmen wird es mit Soaftware PWM wohl zu kapp - da würde
vielleicht mit viel Anstrengung noch etwa 6-7 Bit an Auflösung
hinzukriegen sein.
Wenn man mit Software PWM alleine nicht mehr ganz hinkommt, gäbe es noch
eine halb analoge Methode um für eine nichtlineare Teilung der PWM
Schritte zu sorgen. Damit ist dann die begrenzte Zahl von
Helligkeitsstufen besser nutzbar und man könnte auch mit nur 4-5 Bit PWM
noch gut klar kommen, etwa so wie mit 8 Bit bei linearer Teilung.
Die Hardware Lösung ist auch nicht so einfach. Zum einen braucht man
viele HW Kanäle, schon bei der 3x3x3 Größe wären 9 Kanäle passend. Zum
anderen müsste man die Umschaltung der Ebenen mit dem PWM
synchronisieren - dafür würde man dann vermutlich auch so etwas wie
einen zeitkritischen Interrupt brauchen.
Zu der reinen Ansteuerung kommt dann immer auch noch das Auffrischen der
Bildinformationen. So ein kleiner µC hat einen doch relativ begrenzten
Speicher.
Ok, danke für deine Ausführung. Wie gesagt, werde jetzt einmal die
Software PWM Lösung anvisieren, mal sehen, ob ich damit zufrieden
zustellen bin ;).
Übrigens: Ich programmiere zur Zeit in Assembler, insofern passt das
ganz gut. Ob mein Code allerdings brauchbar und schnell genug ist, weiß
ich (noch) nicht :).
Da ich C aber halbwegs beherrsche, frage ich mich gerade wie die
folgende Aussage zu verstehen ist:
Ulrich schrieb:> weil z.B. C keine 1 Bit> Variablen kennt.
C bietet ja zumindest sog. Bitfelder an, welche hier
(https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Bitfelder)
auch beschrieben werden.
Im Endeffekt passiert dabei doch das selbe, wie wenn ich es zu Fuß per
Assembler mache. Einzelne Bits innerhalb eines Registers werden zu
"Variablen". Auch in Assembler ist ja das kleinste was ich so ansprechen
kann mindestens ein Byte lang. Gut es gibt die Instruktionen "sbi",
"sbr", usw. aber es hindert ja auch C (bzw. den Compiler) keiner daran
von diesen Gebrauch zu machen. Oder sehe ich hier etwas falsch? Das soll
jetzt keine "C vs. Assembler" Diskussion werden, sondern lediglich dem
Verständnis dienen.
Ich habe das eben durch gerechnet. Wenn ich keinen groben (Denk-)Fehler
drin habe, dann sehe ich absolut keine Möglichkeit das mittels Soft-PWM
umzusetzen.
Gehen wir mal von einem Takt von 16 MHz aus. Als Prescaler wähle ich 1.
Meine benötige Frequenz setzt sich ja wie folgt zusammen: 3 100 256.
Die 3 steht dabei für die Anzahl der Ebenen, die 100 für die
"Auffrischungsrate", d.h. das Bild soll 100 Mal pro Sekunde aufgebaut
werden und die 256 für meinen Software PWM Counter.
Laut Datenblatt berechnet sich das Ganze wie folgt:
f_OC0A = (f_clk / (2 N (1 + OCR0A)))
Wobei N für den Prescaler steht.
Stelle ich das Ganze um, ergibt sich:
OCR0A = ((f_clk / (f_OC0A 2 N)) - 1)
Setze ich alles ein, so ergibt sich ein Wert von 103,16. Das heißt ich
bekomme jeden 103. Takt einen Interrupt, welchen es zu bearbeiten gibt.
Bedenkt man, dass ich innerhalb meiner ISR dann 9 Ports vergleichen und
steuern muss und die Ebenen multiplexen muss, so komme ich zu dem
Schluss, dass das nicht machbar ist. Dazu kommt ja dann noch, dass ich
außerhalb der ISR gerne noch das nächste Bild berechnen würde ;).
Demnach taugt mein Ansatz nichts. Irgendwelche anderen Vorschläge ;)?
Ganz so schlimm ist das mit der Rechnung nicht. Das vergleichen der
Werte mit dem PWM Wert dauert in ASM nicht wirklich lange. Wenn ich mich
richtig erinnere waren es für den stark optimierten Code 5 oder 6 Zyklen
die man für jeden PWM-Kanal braucht, sofern die IO Ports günstig liegen,
also 8 Stück zusammen. Das heißt von den 103 Zyklen die man Zeit hat,
bleibt noch etwa die halbe Zeit über. Das geht aber nur mit wirklich gut
optimiertem Code. Einen Trick den man in ASM machen kann (aber kaum ein
C Compiler) ist die Ergebnisse aus den Vergleichen direkt per Shift
Befehl (z.B. ROL) in ein Byte zusammen zu fassen. Zum Weiterzählen des
Index gibt es dann auch Befehle wie LD R1, x+, und die "Schleife" für
die 9 LEDs einer Ebene kann man auschreiben.
Dazu kommt noch, das ich zumindest auf die Doppelt Rechenzeit komme die
man zur Verfügung hat, nämlich rund 200 Zyklen für einen PWM Schritt.
Es ist aber zu überlegen ob man da wirklich Interrupts nutzt, denn schon
für den Interrupt hat man einen Overhead von rund 16 Zyklen, selbst ohne
retten der Register außer dem Status. Das Umschalten der Ebenen bracht
fast keine Rechenzeit - da ist der Aufwand minimal, denn das kommt nur
einmal auf 255 Schritte.
Was man machen kann, ist die Zahl der Interruptsaufrufe zu reduzieren.
Mit einem Schritt der nur die halbe Länge aktiv ist kann man das
unterste Bit quasi direkt ausgeben, könnte also die Zahl der Aufrufe
fast halbieren.
> Das vergleichen der Werte mit dem PWM Wert dauert in ASM> nicht wirklich lange
Man muß überhaupt nicht vergleichen.
Man muß die darzustellenden Werte nur ordentlich strukturiert
im Speicher halten.
Wenn a mit den bits a7 a6 a5 a4 a3 a2 a1 a0
und b mit den bits b7 b6 b5 b4 b3 b2 b1 b0
und c ...
die Dimmwerte der LEDs sind,
dann legt man folgende Bytes im Speicher ab
Block1:
Zeile1: a0b0c0d0e0f0g0h0 i0j0k0l0m0n0o0p0
Zeile2: x0y0z0...
Block2:
Zeile1: a1b1c1d1e1f1g1h1 i1j1k1l1m1n1o1p1
Zeile2: x1y1z1...
:
Block7:
Zeile1: a7b7c7d7e8f7g7g7 i7j7k7l7m7n7o7p7
Zeile2: x7y7z7...
und schiebt Block1 in Zeile 1 raus,
macht kein Pause,
schiebt Block2 in Zeile 1 raus,
macht so lange Pause daß die Gesamtzeit doppelt so lange ist,
schiebt Block3 in Zeile1 raus,
macht eine 4 mal so lange Pause
...
und schiebt Block 7 raus
und macht eine 256 mal so lange Gesamtpause
(in der man jede Menge nützliches tun kann,
z.B. die Daten strukturieren).
dann geht's nach Umschalten des Spaltentreibers
zur Zeile 2 mit Block1.
Bei 70Hz Bildwiederholfrequenz und 8 Spalten im
Multiplex sind 1700us pro Zeile, bei 8 bit
Helligkeitsauflösung muss Block2 7us nach Block1
rausgeschrieben werden, ein AVR mit 16MHz kann 1
Byte in 187ns verschieben, schafft also 37 bytes
oder displays mit 300 x 8 = 2400 LEDs (4800 bei
7 bit Helligkeitsauflösung, 9600 bei 6 bit) in realtime
zu dimmen und ein Video ablaufen zu lassen welches
in 95% ungenutzer Rechenzeit berechnet werden kann
- wenn man die Hardware nicht vergurkt hat sondern
8 bit weise ausgeben kann und programmieren kann.
MaWin schrieb:> dann legt man folgende Bytes im Speicher ab>> Block1:> Zeile1: a0b0c0d0e0f0g0h0 i0j0k0l0m0n0o0p0> Zeile2: x0y0z0...> Block2:> Zeile1: a1b1c1d1e1f1g1h1 i1j1k1l1m1n1o1p1> Zeile2: x1y1z1...>> :> Block7:> Zeile1: a7b7c7d7e8f7g7g7 i7j7k7l7m7n7o7p7> Zeile2: x7y7z7...
Könntest du das vielleicht nochmal ausführlicher beschreiben? So ganz
schlau werde ich da nämlich nicht daraus. Was ist x, y bzw. z?
Ulrich schrieb:> Dazu kommt noch, das ich zumindest auf die Doppelt Rechenzeit komme die> man zur Verfügung hat, nämlich rund 200 Zyklen für einen PWM Schritt.
Wie hast du denn gerechnet? Habe das eigentlich schon öfter in den
Taschenrechner gehackt. Ich komme da immer auf einen Wert von 103 für
OCR0A.
Und selbst wenn es 200 Takte wären. Durch den Interrupt fallen einige
weg und die Berechnungen (bzw. der Tausch der Buffer) nimmt ja auch
nochmal ein paar Takte in Anspruch. Also wenn überhaupt, dann läuft das
Ding am "Limit".
Ich weiß nicht, ob es sich vor dem Hintergrund, dass jeder Befehl das
Aus sein könnte, so gut programmiert ;).
Ich habe so gerechnet:
Je Sekunde braucht man 3 * 100*256 Interruptaufrufe. Und bei Teilen von
16000000 durch diese Zahl komme ich auf 208,... .
Die Idee von Mawin ist auch möglich. Das ist die Idee mit dem halb
langen Intervall einfach weiter gedacht. Man erhält dann kein
klassisches PWM mehr, sondern eine Reihe von zunehmend länger werdenden
Intervallen, die je 1 Bit des PWM Wertes darstellen. Vielleicht hilft
ein Beispiel für da Verständniss: um einen PWM Wert von z.B. 19, in
Binärdarstellung 00010011 darzustellen fängt man mit einem kurzen
Intervall für Bit0 an, in dem die LED an ist. Dann kommt ein doppelt so
langes Intervall für Bit 1 , hier ist die LED auch wieder an. Dann
kommen weitere je doppelt so lange werdende Intervalle wo die LED dann 2
mal aus und ein mal an ist. Insgesamt muss die LED für den 8 Bit PWM
Wert nur maximal 8 mal umgeschaltet werden. Die Werte die man da jeweils
an die Ports ausgeben muss kann man schon vorher umrechnen - das braucht
auch nicht mehr Speicher als die PWM Werte in normaler Speicherung. Im
Interrupt muss man also nur noch vorberechnete Werte aus der Tabelle in
die Port übertragen und insgessamt kommen nur 24 (3 Ebenen mal 8 Bits)
Interrupts für die komplette Darstellung eines Bildes.
Der einige kleine Nachteil ist, dass es ein paar mehr Umschaltvorgänge
gibt und damit eventuell etwas mehr Störungen. Normal sollte das aber
nicht so schlimm sein.
> Könntest du das vielleicht nochmal ausführlicher beschreiben?
Nicht wirklich, war doch schon lang genug der Beitrag.
> So ganz schlau werde ich da nämlich nicht daraus.> Was ist x, y bzw. z?
Weitere Bytes und deren Bits y0 y1 ... x0 x1 ...
Im Speicher befinden sich Bytes hintereinander
a0b0c0d0e0f0g0h0
i0j0k0l0m0n0o0p0
:
mit den gemischten (hier niederwertigsten) bits der Dimmwerte,
in jenau der Anordnung in der die LEDs adressiert sind.
Bit a0 wird also so ausgegeben, daß LED_a dann leuchtet
wenn es 1 war, bit b0 lässt LED_b leuchten.
Hat sie lange genug geleuchtet, kommen die höherwertigen Bits
Bit a1 wird also wieder auf der Leitung von LED_a ausgegeben
bit b1 auf der Leistung von LED_b, und die LEDs leuchten dann
doppelt so lange, weil ja auch die Wertigkeit der bits 2^1 ist.
Eine LED a die 0x5C = 92/256tel an sein soll, ist also
a0=0 a1=0 a2=1 a3=1 a4=1 a5=0 a6=1 a7=0
Aus
1us warten
Aus
2us warten
Ein
4us warten
Ein
8us warten
Ein
16us warten
Aus
32us warten
Ein
64us warten
Aus
128us warten
zusammen 92us der 256us an, nicht in Form von einem Puls
der 92us dauert, sondern flimmernd, bitweise.
Ulrich schrieb:> Je Sekunde braucht man 3 * 100*256 Interruptaufrufe. Und bei Teilen von> 16000000 durch diese Zahl komme ich auf 208,... .
Ja, nur sagt das Datenblatt das man im CTC Modus noch durch 2 teilen
sowie 1 subtrahieren muss. Demnach müsste wohl 103 im Register landen.
Wenn ich den alternativen Vorschlag gerade richtig verstehe, dann
brauche ich anstatt von 256 Interrupts für das PWM "nur" noch 8. Das
hört sich prinzipiell schon ganz brauchbar an, wobei ich mir da noch
Gedanken machen muss, wie man das in Assembler am Besten umsetzt. So
recht weiß ich jedenfalls noch nicht wie ich da am Besten vorgehen
sollte.
MaWin schrieb:> zusammen 92us der 256us an, nicht in Form von einem Puls> der 92us dauert, sondern flimmernd, bitweise.
Ok, das Prinzip hab ich zwar verstanden, nur frage ich mich gerade wie
man das mit den Interrupts jetzt am Besten organisiert, da sich die
Wartezeit ja immer verdoppelt. Ich habe mir überlegt, dass man im CTC
Modus den OCR0A Wert immer verdoppeln könnte. Nur müsste man dann dafür
Sorge tragen, dass der ursprünglich kleinergleich 15 bleibt. Und das ist
wohl nicht immer möglich :(.
> nur frage ich mich gerade wie> man das mit den Interrupts jetzt am Besten organisiert
Gar nicht, das macht man in der Programm-Hauptschleife
wenn man das letzte bischen Geschwindigkeit rauskitzen
will weil dann der Interrupt-Overhead entfällt,
obwohl es technisch kein Problem ist, im Interrupt den
nächsten Interruptzeitpunkt neu festzulegen.
Die ganz kurzen Zeiten (die ersten 2 Werte) macht man ggf. direkt per
Warteschleife (sind ggf. nur ca. 200 Zyklen), es geht aber auch per
Interrupt. Die Längeren Zeiten kann man durch verändern der Wartezeit
für den Interrupt machen. Zumindest mit einem 16 Bit Timer ist das kein
größeres Problem: die Zeiten für den Interrupt kann man vorab berechnen.
Mit einem 8 Bit Timer geht es auch, vor allem wenn der Prescaler (z.B.
256) passt und die erste Zeit per Warteschleife kommt. Bei der genauen
Frequenz muss man ggf. ein paar Abstriche machen, aber die genaue
Wiederholfrequenz ist meist nicht so wichtig.