Forum: Projekte & Code WS2812 Ansteuerung in AVR-Assembler (ohne SPI o.ä., beliebige Pins verwendbar)


von Thomas T. (knibbel)


Angehängte Dateien:

Lesenswert?

Hallo!

Etwas AVR-Hardcore-Assembler gefällig?


Vor ein paar Tagen habe ich meine LED-Stripes bekommen, bestückt mit den 
WS2812-LEDs. Ich habe mich daraufhin am Wochenende mal aufgerafft eine 
AVR-Routine zur Ansteuerung zu schreiben, die möglichst exakt die 
Vorgaben des Datenblatts einhält und dennoch flexibel ist.

Die Vorgaben des Datenblatts von Worldsemi habe ich wie folgt umgesetzt:

* Jede Bitübertragung dauert exakt 1.25 Mikrosekunden. Das entspricht 20 
Taktzyklen bei 16 MHz Takt.

* Ein "1"-Bit teilt sich auf in 625 us "1"-Pegel (10 Zyklen) und 625 us 
"0"-Pegel (10 Zyklen)

* Ein "0"-Bit teilt sich auf in 375 us "1"-Pegel (6 Zyklen) und 875 us 
"0"-Pegel (14 Zyklen)


Die Vorteile meiner Routine:

* Sehr kurz (nur 29 Zeilen für beliebig viele LEDs)

* Reines Bit-Banging, ohne Hardwareunterstützung (wie SPI, UART, etc.)

* Jeder beliebige I/O-Pin verwendbar


Die Bitzeit von 1.25 Mikrosekunden ist schon sehr anspruchsvoll für 
einen 16 MHz AVR. Dies ist so ohne Tricks nicht lösbar ...

Mein angehängtes Beispielprogramm ist für einen Atmel 90CAN128 
geschrieben, weil der gerade greifbar war. Andere AVRs gehen natürlich 
auch. Voraussetzung sind: 16 MHz Takt und gleiche Ausführungszeiten der 
Befehle. "Reduced Core TinyAVR" gehen nicht, da muss nachgearbeitet 
werden, da z.B. CBI und SBI dort schneller ausgeführt werden.

Bei anderen AVRs muss man natürlich die Initialisierung anpassen 
(MCU-Kern, Stack, RAM, PORT und Timer-Interrupt).

Ich habe als "Test-Streifen" 30 LEDs angeschlossen. Ausgebender Port ist 
Bit 0 von Port E.

Alle die beim Lesen des Quelltextes aufschreien "Ahhh, 
Interrupt-Routinen müssen so kurz wie möglich sein!!!": Legt euch wieder 
hin!

Mein Programm läuft KOMPLETT im Interrupt!

Es gibt einen Initialisierungsteil, der die MCU, den Stack und die Ports 
initialisiert. Dann wird ein Speicherbereich eingerichtet, der zu jeder 
Zeit ein Abbild der anzusteuernden LEDs vorhält und zwar für jede LED 
drei Bytes:

1. Byte:  LED 1: Helligkeit Grün
2. Byte:  LED 1: Helligkeit Rot
3. Byte:  LED 1: Helligkeit Blau
4. Byte:  LED 2: Helligkeit Grün
5. Byte:  LED 2: Helligkeit Rot
6. Byte:  LED 2: Helligkeit Blau
7. Byte:  LED 3: Helligkeit Grün

usw.

Es folgt die Initialisierung des Timers 1, damit der Timer-Interrupt 
alle 125 Millisekunden aufgerufen wird (8 Hz). Die Aufruffrequenz lässt 
sich natürlich anpassen ...

Dann noch den Interrupt freigeben und danach als Hauptprogramm ein "JMP" 
auf sich selbst immer und immer wieder ausführen.

Sollte der Timer-Interrupt nicht funktionieren, wäre hier das Ende ...

Alle 125 ms wird aber die Interrupt-Routine aufgerufen. Diese macht 
folgendes:

Teil 1) Den Bitstrom für die WS2812-LEDs erzeugen
(Hinweis: NOP verzögert 1 Takt, RJMP PC+1 verzögert 2 Takte ...)


Teil 2) Das LED-Speicherabbild für die nächste Übertragung vorbereiten.
(Im Beispiel ist ein einfaches Lauflicht programmiert, welches einfach 
das LED-Speicherabbild verschiebt und eine neue Farbe ans Ende dranhängt 
...)

Teil 3) für eigene Erweiterungen ;-)


Bei diesem Programmierstil muss man natürlich Sorge tragen, dass die 
Ausführungszeit der Interrupt-Routine nicht länger ist als die 
Aufruffrequenz.

Viel Spaß damit ...

Kommentare oder Fragen? Immer her damit ...

Gruß,
Thomas Tahsin-Bey

von Claus M. (energy)


Lesenswert?

Was sind das für Stripes und woher hast du die? Verstehe ich recht, jede 
LED hat einen eigenen Controller den du mit deiner Software ansprichst?

von Jürgen B. (hicom)


Lesenswert?

Habe ich auch im Einsatz, coole Teile:

http://shop.led-studien.de/index.php/cat/c30_Flexband-digital.html

Gruß
Jürgen

von Thomas T. (knibbel)


Lesenswert?

In diesem Thread gibt es auch eine Menge Infos:

Beitrag "[Mitbestellung] SMD5050 RGB-LED mit integriertem 8-bit PWM Controller"


Und ja, jede LED hat ihren eigenen Controller ...


Gruß,
Thomas

von Thomas T. (knibbel)


Lesenswert?

In diesem Beitrag gibt es Links auf die LED-Stripes:

Beitrag "Re: [Mitbestellung] SMD5050 RGB-LED mit integriertem 8-bit PWM Controller"


Ich habe hier ein paar Meter mit jeweils 60 LEDs pro Meter. Zum testen 
für das Programm habe ich mir einfach einen halben Meter abgeschnitten 
und mit drei Adern an den Controller gehängt (Vcc, Data, Gnd).

Die LEDs sind einzeln ansteuerbar, wobei jeder Farbkanal (Rot, Grün und 
Blau) 256 unterschiedliche Helligkeitswerte annehmen kann.

Gruß,
Thomas

von Bart V. (bartv)


Lesenswert?

Hallo Thomas !
Tolles Programm !

und jetzt die Frage ;-)

Assembler ist mir zu hoch gegriffen zur Zeit. Ich bin dabei C zu lernen. 
Kannst du ein Beispiel erzeugen wie ich das in C aufrufen/verwenden kann 
?

MfG

Bart

von Thomas T. (knibbel)


Lesenswert?

bart verberne schrieb:
> Assembler ist mir zu hoch gegriffen zur Zeit. Ich bin dabei C zu lernen.
> Kannst du ein Beispiel erzeugen wie ich das in C aufrufen/verwenden kann
> ?

Und C liegt mir so gar nicht. :-(

Aber vielleicht helfen dir die folgenden Tipps (oder ein anderer Leser 
kann hier weiterhelfen (wie übergebe ich in C einen Pointer?):

A)
Du kannst im Prinzip die Senderoutine (Txd_WS2811) ohne Probleme 
übernehmen. (Der gesamte "Teil 1")

B)
Die C Routine muss einen Pointer auf einen genügend großes 
eindimensionales Byte-Array bereitstellen, in dem die Helligkeitswerte 
drin sind. Meine Routine erwartet diesen Pointer im Y-Register.

C)
Anzahl der LEDs kannst du im Quelltext direkt reinschreiben 
(multipliziert mit 3). Siehe die ersten beiden Zeilen im "Teil 1".

D)
Interrupts während der Ausführung meiner Routine verbieten, da sonst die 
Zeiten nicht mehr exakt eingehalten werden.

E)
Benutzte Register sichern. Ich kann dir nicht sagen, welche Register du 
in deinem C-Programm während der Ausführung einer Assemblerroutine 
einfach so ändern darfst. Deswegen würde ich am Anfang alle benutzen 
Register auf dem Stack sichern und am Ende wieder zurückholen (auch das 
Statusregister!). Eventuell rettet C selbst alle Register, bevor die 
Ausführung an das Assemblerprogramm übergeben wird?

F)
Das Ende meiner Routine wäre dann bei "Update_Table". Hier sollte dann 
das Rückstellen der Register und der Rücksprung zum C-Programm stehen.

G)
Das Byte-Array kann dann im C-Programm beliebig geändert werden und bei 
Bedarf wird dann meine Routine aufgerufen. Eventuell sogar per 
Interrupt...


Ich hoffe, dies hilft erstmal wenigstens etwas weiter!

Gruß,
Thomas

von Stephan B. (matrixstorm)


Lesenswert?

Hallo Alle.

Gaebe es von eurer Seite aus Bedarf fuer eine WS2812 Ansteuerung, die
lediglich den internen 8MHz benoetigen wuerde?

Ich haette eine Roh-Implementierung und wuerde gern wissen ob sich
aufraeumen lohnt, ob es jemand anderes gebrauchen koennte?

Klarer Vorteil duerfte wohl sein, das man preiswerte "ATmegas" ohne
grossartig externe Komponenten verwenden koennte...


MfG

von Carl c. (carl125)


Lesenswert?

ich hab' jetzt grad meinen strip mit WS2812er rgb-leds bekommen:
http://www.ledlightinghut.com/144-led-m-ws2812-digital-intelligent-rgb-led-strip-light.html
lieferung war in ordnung, freitag abend bestellt, donnerstag angekommen.
die WS2812 sind 5050er SMD-RGB-LEDS mit eingebautem WS2811-controller. 
es gibt nur eine leitung vom arduino zum strip. keine clock-leitung, 
deswegen ist das timing sehr wichtig, aber das erledigt diese 
bibliothek:
http://code.google.com/p/fastspi/
den strip anschließen, im beispielsketch die anzahl der leds angeben und 
bei der auswahl des chipsets
FastSPI_LED.setChipset(CFastSPI_LED::SPI_WS2801); auswählen und 
ausbessern auf:
FastSPI_LED.setChipset(CFastSPI_LED::SPI_WS2811);

sketch hochladen und das testprogramm läuft. wär nur alles so einfach...

von Abraham (Gast)


Lesenswert?

Bei do_nothing müsste man noch ein Sleep reinmachen können um noch etwas 
Strom zu sparen.

Gruss Abraham

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.