UPDATE - BAM - Bit-Angle-Modulation - Dimmung vieler LEDs mit einem µC

    UPDATE - BAM - Bit-Angle-Modulation - Dimmung vieler LEDs mit einem µC

    So, ich habe mal ne Bascom-Routine für BAM-Dimmung geschrieben.
    Die erste Version kann nicht viel:
    - 8 LEDs an PortA eines Mega32 8-Bit dimmen
    - Kleines Demo-Lauflicht

    Weitere Planungen:
    - Mehrere Ports (M32 könnte 4x8 LEDs steuern)
    - Mehr Bit für die BAM
    - Umwandlung in eine Include-Datei
    - ggfs. Umwandlung in eine Bascom-Lib (wer mag den Assembler Teil machen? :rolleyes: )

    Anmerkungen, Verbesserungen sind SEHR willkommen!
    Im speziellen für:
    - ISR
    - Array-Umwandlung (dank Bascoms Marotte, dass Arrays mit (1) anfangen recht umständlich)

    Mich würde vor allem interessieren, was die anderen Spezis dazu sagen - vonwegen CPU-Cycles und so...

    Kleines Demo-Video:
    aPqVawtnEDQ

    Mehr zur Theorie dahinter und weitere Einzelheiten später...

    Code v0.2 als .BAS im Anhang!

    Edit: Aus Platzgründen hab ich das Listing mal rausgenommen, von jetzt ab poste ich nur noch die Code-Änderungen...
    Dateien

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Stefan_Z“ ()

    Klar, den M32 hatte ich nur grad im Board stecken :rolleyes:
    Nur nen PortA hat der Tiny nicht.. aber das solltest du hinbekommen, oder?
    Ggfs. müsste man die Timer-Einstellung ändern, aber theoretisch sollte selbst das stimmen.
    Ich schau gleich mal...

    Aber bau mal auf, sieht bestimmt cool aus...

    Ich war bisher zu faul mehr als 8 LEDs anzuklemmen :thumbup:
    Läuft zur Zeit auf einer Spalte einer LED-Matrix - auch ohne Widerstand *schäääm*
    Aber der Duty ist ja nicht soo derbe...
    Werds Anfang der Woche einstellen, morgen gehts erstmal übers Wochenend nach Hamburg :) - Wenn ich wieder hier bin ist hoffentlich mein Programmiergerät auch da, das Pollinboard hab ich ja gegrillt.
    Wenn du nichts dagegen hast stell ichs hier in den Thread dann ein - dafür was eigenes aufmachen lohnt ja nicht.

    Grüße,
    und danke :)
    mit den losen Kontakten Auf nen Kühlkörper liegen gehabt...

    Da ich an meinem Laptop eh kein LTP / Serial hab - und somit fürs Pollin noch ne alte Kiste in der Ecke stehen hab - hab ich mir endlich mal nen USB-Progger gegönnt.
    Und weil ichs löten leid bin direkt auch mal ein Steckbrett :thumbsup:

    Ein Bekannter baut zur Zeit eine "Qualle" - mit LEDs in den Armen/Tentakeln, da wird das dann zum Einsatz kommen wenns klappt :)
    Sieht gut aus Stefan :) . Ein kleines Problem sehe ich allerdings, wenn mal schnellere Animationen implemetiert werden, d.h wenn sich die Werte für die LEDs schneller und möglicherweise auch in grösseren Sprüngen ändern. Da du 'nur' ein BAM-Array verwendest und die ISR bei jedem BAM-Schritt darauf zugreift, könnte es unschöne Effekte geben, wenn sich die Werte des BAM-Arrays während eines noch laufenden BAM-Gesamtzyklus ändern. Da können dann die ersten BAM-Schritte eines Zyklus noch die Bits des vorhergehenden Wertes und die weiteren BAM-Schritte des gleichen Zyklus dann schon die Bits des neuen Wertes ausgeben.
    Um dieses Problem zu umgehen, könnte man ein Ping-Pong-BAM-Array implementieren. Dabei werden jeweils BAM-Wertänderungen in das momentan gerade nicht aktuell ausgelesene Array geschrieben. Ist die BAM-ISR mit einem ganzen Zyklus fertig, dann setzt sie ein entsprechendes Flag-Byte, und wechselt zum Lesen auf das andere Array. Das Hauptprogramm bzw. die "Array_wandlung"-Routine prüft das bzw. die Flags und weiss dann aufgrund des jeweils gesetzen Flags, in welches BAM-Array gerade geschrieben werden kann. Ping-Pong hat gegenüber einem einfachen Doppel-Array den Vorteil, dass lästige und zeitintensive Array-Kopiervorgänge so umgangen werden können.

    Gruss
    Neni
    Danke für die Manöverkritik!

    Das mit den zwei Arrays macht Sinn, muss ich zugeben. Gerade zur Performanceoptimierung.
    Ohne zusätzlichen Array ginge es auch, man könnte die Rechnerei einfach in die lange 8. BAM-Phase legen.
    Also Umschichtung nur dann, wenn genügend Zeit (255 Zyklen x Prescale 64 sollte reichen, oder?) vorhanden ist.
    Danach ist er ja dann eh wieder schön beim ersten Schritt...

    Ist wie gesagt die erste Implementation, soll nocht toller werden, so dass alle was davon haben...
    Ja, das könnte man auch, aber dann müsste man die Umwandlung direkt in die ISR packen, denn nur so könnte man sicher gewährleisten, dass die Umwandlung synchron mit dem Beginn des 8. Schrittes ausgeführt wird und damit dann auch in den Zeitabschnitt passt. Allerdings wird dann die Umwandlung immer gemacht, auch wenn sich keine Werte geändert haben (und damit immer ev. kostbare Hauptprogramm-Zeit geklaut), was man wiederum mit einem Flag bei Wertänderung lösen könnte. Unklar ist auch, wie schnell die Rechenzeit für die Umwandlung bei steigender Anzahl LED-Ausgänge dann an die Grenzen des 8. Zeitabschnittes stösst.

    Das ist übrigens alles nur als konstruktive Kritik gedacht bzw. als Hinweis, worauf man alles achten sollte. Deine Vorarbeit ist ja schon mal sehr gut :) .

    Gruss
    Neni
    Ich pack hier mal die generellen Infos zur BAM in einen eigenen Post, keinen bock den großen oben kaputt zu editieren...

    Bit Angle Modulation - Theorie

    BAM basiert auf der Tatsache, dass im binären Zahlensystem das jeweils nachst höhere Bit immer doppelt soviel "Wert" ist als das vorherige.
    Beispiel binäre Schreibweise:
    00000000 = 000
    00000001 = 001
    00000010 = 002
    00000100 = 004
    00001000 = 008
    00010000 = 016
    00100000 = 032
    01000000 = 064
    10000000 = 128
    11111111 = 256


    Wichtig: das Bit 0 sitzt normalerweise ganz rechts!

    Der Trick besteht jetzt darin, dass die binäre Scheibweise des Dimmungswertes direkt zur Dimmung benutzt wird!

    Schauen wir uns dazu erstmal die "normale" PWM an (in diesem Fall natürlich auf Soft-PWM bezogen):
    Hier wird der Pegel HIGH jeweils solange gehalten, bis der Zähler erreicht ist. Beim Wert 158 dann z.B. nach 158 Zyklen.

    Da man viele Pins bedienen möchte, aber für jeden möglichen Wert den Pin neu setzen müsste, kann man mit der BAM sehr viel CPU sparen!

    Dazu bedient man sich der höheren Wertigkeit höherer Bits, wie oben schon gesagt.
    Bit 0 entspricht einem Zyklus
    Bit 1 entspricht zwei Zyklen
    Bit 2 entspricht vier Zyklen
    Bit 3 entspricht acht Zyklen
    usw...

    In der Grafik unten erkennt man dann sehr schön, dass 128+16+8+4+2 immer noch 158 ergeben.
    Die Gesamte AN-Zeit des Pins ist also über den Zyklus gesehen gleich zur normalen PWM!


    Praktische Umsetzung:
    - Wie üblich haben wir einen Array mit je einem Byte pro Pin/LED das die Dimmung enthält
    - Diese werden umgelegt - und zwar so, dass am Ende ein zweites Array steht in dem die acht Stufen der BAM abgebildet sind.
    Dazu kopiert man jeweils alle Bit 0 in den ersten Schritt, dann alle Bit 1 in den zweiten usw...

    - Danach versetzt man den Timer0 (8-Bit-Timer, hat quasi jeder AVR) in den CTC Modus - dazu muss man die Register manuell setzen:
    Tccr0 = &B00001011
    - Diesen Timer läd man dann in seiner Interrup-Routine in jedes BAM-Stufe mit einem neuen Wert, so dass die Intervalle der BAM entsprechend der Wertigkeit der in ihr enthaltenen Bits geregelt wird. Noch den Port entsprechend setzen, fertig.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Stefan_Z“ ()

    synvox schrieb:

    Ja, das könnte man auch, aber dann müsste man die Umwandlung direkt in die ISR packen, denn nur so könnte man sicher gewährleisten, dass die Umwandlung synchron mit dem Beginn des 8. Schrittes ausgeführt wird und damit dann auch in den Zeitabschnitt passt. Allerdings wird dann die Umwandlung immer gemacht, auch wenn sich keine Werte geändert haben (und damit immer ev. kostbare Hauptprogramm-Zeit geklaut), was man wiederum mit einem Flag bei Wertänderung lösen könnte.

    Das sehe ich als recht problemlos an, so wir die Umwandlung noch etwas glatter hinbekommen. Das mit dan Bascom-Arrays ist schon doof. Sollte mit ASM deutlich einfacher sein, oder?

    Unklar ist auch, wie schnell die Rechenzeit für die Umwandlung bei steigender Anzahl LED-Ausgänge dann an die Grenzen des 8. Zeitabschnittes stösst.

    Selbes Problem


    Das ist übrigens alles nur als konstruktive Kritik gedacht bzw. als Hinweis, worauf man alles achten sollte. Deine Vorarbeit ist ja schon mal sehr gut :) .


    Schon klar, nur weiter so!
    Schön, dass das mal jemand fertig programmiert hat! :thumbup:

    Bei mir läuft's noch nicht so richtig, Problem mit dem Timer, dann keine Lust mehr gehabt, liegt seit Wochen auf Eis...

    Das mit dem Umwandeln ist in ASM übrigens nicht *einfacher*, ist ne rechte Bitschieberei ;) - sind schon ein paar Zeilen Code mehr als bei Dir... schneller sollte es halt gehen, je nachdem wie Bascom das übersetzt... also 32 Kanäle im längsten Segment "übersetzen" ist in asm garantiert kein Problem!

    wofür brauchst Du eigentlich den CTC..? - Du nutzt doch den Compare Match gar nicht, Du setzt den Startwert des Zählers, und die ISR wird immer beim Überlauf ausgelöst...

    und das ist gar nicht so blöd, ich habe es nämlich andersrum gemacht, ISR bei Compare Match und das Compare Register gesetzt, da brauche ich dann den CTC - und da ist irgendwo der Wurm drin, evtl. auch, weil das bei mir mit 12 Bit laufen soll (auf dem 16-Bit-Timer)... werde ich mal auf Deine Methode umstellen

    übrigens, zur Theorie: durch Deine Helligkeitsanpassung wird der Verlauf unten aber etwas wellig - Du hast ja als unterste Stufen:

    1 dann
    3 dann
    1+3 = 4 dann
    7 dann
    7+1 = 8 dann
    7+3 = 10 dann
    7+3+1 = 11

    also erst nen Schritt von 2, dann 1, dann 3, dann wieder 1, dann 2 und dann wieder 1 - also keine gleichmäßige Zunahme... gut, bei dem schnellen Faden sieht man das nicht, aber lass' doch mal ne LED mit Deiner Routine ganz langsam hochfaden, dann wirst Du sehen, dass das ganz am Anfang nicht gleichmäßig geht...

    das ist eben so ein Problem bei 8 Bit (sowohl PWM als auch BAM), dass halt die untersten Stufen immer zu hell sind - deswegen will ich das ja mit 12 Bit machen, und eben BAM, weil SW-PWM mit 12 Bit viel zu langsam wird...
    It's only light - but we like it!

    Da es sich in letzter Zeit häuft: Ich beantworte keine PNs mit Fragen, die sich auch im Forum beantworten lassen!
    Insbesondere solche von Mitgliedern mit 0 Beiträgen, die dann meist auch noch Sachen fragen, die bereits im entsprechenden Thread beantwortet wurden.
    Ich bin keine private Bastler-Hotline, technische Tipps etc. sollen möglichst vielen Lesern im Forum helfen!
    Ja das ist jetzt von den Zyklen noch etwas rumprobiert... Rote Schrott-Matix von Po**** mit 5V ohne Widerstand.. die waren mir einfach zu hell unten rum. 8o

    Den CTC nutze ich, weil ich es so verstanden hatte, dass der normale Timer nicht automatisch neu startet... oder sowas... wie gesagt, da ist noch viel Spielraum für Verbesserung :thumbsup:

    Edit: OK, jetzt hab ich grad mal nachgelesen, warum ich den CTC nutze:
    Weil der normale Modus einfach stur weiterläuft, während der CTC stoppt, bzw. dann wieder losrennt, wenn man den Timer neu setzt.
    Das klaut mir zwar ein paar Takte, dafür ist aber sichergestellt, dass die LEDs nie zu lange an sind.
    Denn ansonsten würde der Timer z.B. bei Stufe 1+2 mehrfach durchlaufen während man noch indie ISR hüpft.

    The output compare unit can be used to generate interrupts at some given time. Using
    the output compare to generate waveforms in Normal mode is not recommended, since
    this will occupy too much of the CPU time.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Stefan_Z“ ()

    OK, hab doch noch eine einfachere Methode für die Umschichtung gefunden!
    Update auf v0.2 ist oben angehangen (am 1. Posting)
    Resultat ist gleich, Code ist kürzer.
    Ob er SCHNELLER ist muss ich noch mal testen...

    Die Routine selber sieht so aus:

    Quellcode

    1. Sub Array_wandlung_2:
    2. Local X As Byte
    3. Local Y As Byte
    4. Local Za As Byte
    5. Local Zb As Byte
    6. Zb = 0 'LOCALs müssen initialisiert werden!
    7. For X = 1 To 8
    8. Za = 0
    9. For Y = 1 To 8
    10. Bam_array(x).za = Led(y).zb
    11. Incr Za
    12. Next
    13. Incr Zb
    14. Next
    15. End Sub
    Sollte auf jeden Fall schneller sein, weil Du Dir ja die Zwischenberechnungen sparst :thumbup:

    wegen dem CTC: wie gesagt, Du nutzt den Compare Match ja gar nicht, sondern Timer Overflow... von daher das falsche Zitat aus dem Datenblatt.. ;)

    Der CTC bewirkt folgendes: wenn es einen Compare Match gibt (z.B. Timer ist 173, im Compare-Register steht auch 173), dann wird der Timer wieder auf 0 zurückgesetzt - ohne CTC würde er bis 255 weiterzählen... aber auch mit CTC wird er nicht gestoppt, er läuft weiter - wird halt dann noch mal auf 0 gesetzt, unnötig, weil der Timer in dem Moment ja eh' auf 0 steht (Overflow), kostet Dich aber auch keine Rechenzeit, weil das der Timer ja intern regelt, ist ja nur einmal ne Einstellung am Anfang...

    ich benutze den CTC z.B. in ner PWM, da mache ich eben keinen Overflow Interrupt, sondern Compare Match Interrupt bei 100, damit ich trotz nur 8 MHz Systemtakt ne genügend schnelle SW-PWM bekomme - ohne CTC würde der Interrupt nun jedesmal ausgelöst, wenn der Timer 100 erreicht, der Timer würde dann aber weiterzählen, also hätte ich trotzdem nur alle 256 Takte ne ISR... durch den CTC wird der Timer beim erreichen von 100 auf 0 zurückgesetzt, also alle 100 Takte ISR...

    mach' doch mal den CTC nuegierhalber raus - sollte sich nix ändern...
    It's only light - but we like it!

    Da es sich in letzter Zeit häuft: Ich beantworte keine PNs mit Fragen, die sich auch im Forum beantworten lassen!
    Insbesondere solche von Mitgliedern mit 0 Beiträgen, die dann meist auch noch Sachen fragen, die bereits im entsprechenden Thread beantwortet wurden.
    Ich bin keine private Bastler-Hotline, technische Tipps etc. sollen möglichst vielen Lesern im Forum helfen!
    Stefan, ich würde auch noch auf die Locals verzichten, denn deren Speicherplatz muss bei jedem SUB-Aufruf neu im Frame (Teil des Stack) reserviert und beim SUB-Ende wieder freigegeben werden. Wenn du für diese paar Parameter globale Variablen verwendest, dann bleibt zwar im RAM für diese der Speicher ständig reserviert, aber die Laufzeit der SUB wird deutlich kürzer, nur schon weil bei den Zugriffen nicht mehr mit Pointern auf die entsprechenden Frame-Speicherzellen jongliert werden muss.

    Gruss
    Neni
    Ja, mit Prescale von 64 wirds nix. Aber mit 8 könnte es klappen. Ich setze meinen Timerwert immer erst am Ende der ISR. Somit verschiebt es sich zwar etwas bei kleinen Werten, aber besser als 8bit PWM :P Muss ich mal ausrechnen. Oder dann doch nur 15 bit?

    Hat jemand mit 10bit PWM Erfahrung? Bekommt man da in den unteren Bereichen einen sauberes Dimmen?

    Gruß, Benny.