C WS2812 LED Treiber

Janiiix3

Aktives Mitglied
28 Sep 2013
1.329
10
38
Hannover
Sprachen
ANSI C, C#
Einen wunderschönen guten Morgen.

Bin gerade dabei ein paar LED´s mit dem oben erwähnten Treiber zum leuchten zu bewegen. Klappt zwar auch, nur nicht so wie ich will.
Kein Wunder. Bekomme auch nicht wirklich mit den "C" Routinen das Timing hin, was laut Datenblatt vorgegebn ist.

Um eine "0" zu senden möchte der Treiber folgendes Timing.:


CodeBox C
void ws2812Low(uint8_t port, uint8_t bit)
{
   BS(PORTK,PK0); // high
   _delay_us(0.35); // high for 0.35µS
   BC(PORTK,PK0); // low
   _delay_us(0.80); // 0.8µS
}


Um eine "1" zu mögen, möchte er.:


CodeBox C
void ws2812High(uint8_t port, uint8_t bit)
{
   BS(PORTK,PK0); // high
   _delay_us(0.70); // high for 0.35µS
   BC(PORTK,PK0); // low
   _delay_us(0.60); // 0.8µS
}


Mit beiden Funktionen, bin ich um ettliche Nanosekunden daneben. Gibt es eine alternative?
In meiner jetzigen CPU wackelt ein 16MHz Quarz..
 

Dirk

Administrator
Teammitglied
28 Jan 2007
4.308
150
63
Mittelhessen, Giessen
Sprachen
ANSI C, C++, C#, Java, Kotlin, Pascal, Assembler, PHP
Das Timing wirst du mit C nicht hinbekommen!

Das Übertragungsprotokoll musst du maschinennah in Assembler programmieren.

Inzwischen gibt es sicher einige Lösungen für AVR Mikrocontroller im Bereich Arduino. Hier würde ich mal schauen.

Hier mal eine Spielerei von mir mit Mega2560 und Mega32, C mit Assembler ...

 

Janiiix3

Aktives Mitglied
28 Sep 2013
1.329
10
38
Hannover
Sprachen
ANSI C, C#
Hey Dirk!
Sieht cool aus ;) Kann man sich den Code irgendwo ansehen?
Ich habe von Asm überhaupt keine Ahnung.
 

Dirk

Administrator
Teammitglied
28 Jan 2007
4.308
150
63
Mittelhessen, Giessen
Sprachen
ANSI C, C++, C#, Java, Kotlin, Pascal, Assembler, PHP
Meinen Source habe ich jetzt nicht hier. Ich schau bei Gelegenheit mal danach.
 

addi

Mitglied
2 Sep 2013
118
4
18
Hamminkeln
Sprachen
BascomAVR, ANSI C, Assembler
Hmmm...eventl. 20mhz quarz rein?...falls der prozessor das mitmacht.
Addi
 

LotadaC

Sehr aktives Mitglied
22 Jan 2009
3.426
62
48
Marwitz
Sprachen
BascomAVR, Assembler
Mit beiden Funktionen, bin ich um ettliche Nanosekunden daneben.
???
0 -> 200ns < Hi < 500ns, 750ns < Lo < 1050ns
1-> 750ns < Hi < 1050ns, 200ns < Lo < 500ns

1100ns < Hi+Lo < 1400ns

Bei der Null paßt's doch, bei der Eins bist Du eigentlich im nicht definierten Zustand. Eigentlich, weil ein Takt bei 16MHz 62,5ns dauert.
Schmeißt der Compiler bei dem Delay eigentlich keine Warnung? 350ns kann er aus 16MHz doch gar nicht treffen. 5 Takte sind 312,5ns - 6 Takte sind 375ns. Die nächste Instruktion ist erst einen weiteren Takt später dran - das ist aber keine Makro (oder?) sondern ein Funktionsaufruf (RCALL oder CALL, je nach Controller - 2 bis 5Takte (auch vom Flash abhängig)).
Aber die 600ns sind eigentlich zu lang.

Das Timing wirst du mit C nicht hinbekommen!
Wäre doch 'ne Herausforderung...
Hmm...
Wie siehts mit der Verwendung der Hardware aus?
Prinzipiell sollte sich doch'n Timer im fastPWM nutzen lassen. Das Compare-Register wird beim Überlauf (genauer Bottom) aktualisiert, müßte also beim Compare-IRQ bereits für das nächste Bit gesetzt werden. Beim letzten Bit muß dann der Timer gestoppt werden (und für die nächste Transmission bereitgemacht...).

Hmm2...
Ihr kennt ja meine ... ähm ... unkonventionellen Vorschläge...
Wie siehts mit dem UART aus?
Die Datenleitung soll im idle low sein, oder?
Mist, wäre ganau andersrum...
Ok, könnte man mit einem Transistor/NOT-Gatter (SOT23) invertieren...
Allerdings spuckt einem da das Oversampling für den Empfang in die Suppe. Mit doubeled Transmission Speed wären bei 16MHz 2Mbaud drin. Wir bräuchten etwa 3.33Mbaud.
Hmm3...
USART im SPI-Mode oder SPI selbst?
Dann sollte jedes Nibble ein LED-Bit repräsentieren können.
SPI-Bitzeit auf ca. 300ns festlegen, dann dauert ein Nibble 1200ns.
idle = 0hex = 0000bin wäre idle oder eben nichts senden,
"0" = 8hex = 1000bin die gesendete Null,
"1" = Ehex = 1110bin die gesendete Eins.
Beim echten SPI hatte ich damals keinen lückenlosen Transfer hinbekommen, beim gepufferten UART hingegen schon.
USART-SPI hab ich noch nie getestet...
 

Dirk

Administrator
Teammitglied
28 Jan 2007
4.308
150
63
Mittelhessen, Giessen
Sprachen
ANSI C, C++, C#, Java, Kotlin, Pascal, Assembler, PHP
Eigentlich, weil ein Takt bei 16MHz 62,5ns dauert.
Schmeißt der Compiler bei dem Delay eigentlich keine Warnung? 350ns kann er aus 16MHz doch gar nicht treffen.
Da kann man sich drehen wie man will. Leider geht auch nicht _delay_ns(1) ;-) IO Port Zugriffe benötigen auch mehr als 0ns.
Eventuell geht es auch "unkonventionell" via Hardwaremodule. Ich denke aber die einfachste Lösung geht über konventionellen Assemblercode.
 

LotadaC

Sehr aktives Mitglied
22 Jan 2009
3.426
62
48
Marwitz
Sprachen
BascomAVR, Assembler
...Leider geht auch nicht _delay_ns(1) ;-)...
Nein, aber delay_ns(62.5) müßte genau ein NOP sein - falls es entsprechend implementiert wurde. Ein purer schleifengenerator, der auch noch erst mit call/rcall und ret aufgerufen/beendet wird, versagt bei so kurzen Zeiten, und da würde ich zumindest einen Hinweis des Compilers erwarten...
Da kann man sich drehen wie man will ... IO Port Zugriffe benötigen auch mehr als 0ns
Mit Set/Reset (SBI/CBI) einen Takt, mit direktem Schreibzugriff (OUT) auch einen, da der Wert vorher geladen werden muß (LDS/LDI mindestens einen weiteren, bei ReadModifyWrite-Zugriffen dann entsprechende Takte auch für Read und Modify...
Diese Verzögerungen (Aufruf und Ausführung des Bein-setzens) verlängern die Phasen. deswegen wird der eigetlich zu kurze Hi-Teil trotzdem als 1 erkannt.
Meiner Meinung nach ist die Länge des Hi-Pulses relevant, die Low-Länge sollte weniger kritisch sein.
Ich denke aber die einfachste Lösung geht über konventionellen Assemblercode
Hmm... kommt drauf an, was der Controller nebenbei noch macht - da bleibt ja nicht viel Zeit im Hintergrund.
Wenn der Pin zu Fuß gesetzt werden soll, brauchst Du zwei Zugriffe pro LED-Bit (also ca alle 1,2µs).
Läßt Du das irgendein Hardware-Modul machen, übernimmt das einen Teil der Zugriffe im Hintergrund - mein Vorschlag erschlägt zwei LED-Bits mit einem gesendeten "Byte", wäre also nur noch ein Zugriff alle 2,4µs. Insbesondere keinen kritischen mit 350ns-Timing.
So unkonventionell scheint die Idee nicht zu sein, allerdings schafft der AVR@16MHz keine 3+Mbaud. Link
Weiter runterblättern ..bei reply #3 steht dazu etwas (arduino
Hm..
zumindest im dazugehöriigen Bild sieht man auch hier 'ne Lücke zwischen den Bytes, und die wollen wir ja nicht. Woher die nun kommt, geh ich da jetzt nicht mehr durch - Arduinesisch/C tu ich mir jetzt nicht mehr an...
beim echten SPI hab ich das mal in diesem Thread durchgespielt.
Das SPI Data Register ist schreibtechnisch nicht gepuffert.
Reagiert man auf den Transfer Complete IRQ, verzögert es sich durch den IRQ auf mindestens 4 Takte für den automatischen Sprung in die IVT (mit Returnadresse pushen), dort dann üblicherweise (aber nicht zwingend nötig) ein weiterer Sprung in die ISR. Sieben Takte, bevor man mit beschreiben des SPDR das nächste Byte losschicken kann.
Etwas schneller gehts im Polling.
Als letztes hatte ich dann die benötigten Takte gezählt - da mußten es interessanterweise nicht 16 Takte sein sondern 17. Beschreibt man nach 16 Takten das SPDR, werden zwar CLKs generiert, aber es wird immer eine 0x00 rausgetaktet.

Beim echten UART meine ich mich hingegen an ein lückenloses senden erinnern zu können - das UDR ist ja schreibtechnisch (einfach) gepuffert. Ob das aber auch im USART-SPI-Mode so gilt, weiß ich nicht...
 

Dirk

Administrator
Teammitglied
28 Jan 2007
4.308
150
63
Mittelhessen, Giessen
Sprachen
ANSI C, C++, C#, Java, Kotlin, Pascal, Assembler, PHP
Nein, aber delay_ns(62.5) müßte genau ein NOP sein - falls es entsprechend implementiert wurde...
Dieses ...
...Leider geht auch nicht _delay_ns(1) ;-)...
hatte ich eigentlich nicht besonders ernst gemeint, deswegen auch das ;-) :D
Eigentlich wäre es etwas für den nächsten April. ;)

Mit Set/Reset (SBI/CBI) einen Takt, mit direktem Schreibzugriff (OUT) auch einen ...
Das ist klar, weder dies noch ggf. Aufruf der Routinen werden im Code ganz oben berücksichtigt, mal ganz abgesehen davon, dass man ein NOP nicht beliebig "teilen" kann. Es funktioniert so auf keinen Fall!
 

Janiiix3

Aktives Mitglied
28 Sep 2013
1.329
10
38
Hannover
Sprachen
ANSI C, C#
Hey Ho!
Habe jetzt eine Assembler / C Version im Netz gefunden die Funktioniert. Sehr schade das man mit C keinen knackig kurzen / schnellen Code erzeugen kann :/
 

Dirk

Administrator
Teammitglied
28 Jan 2007
4.308
150
63
Mittelhessen, Giessen
Sprachen
ANSI C, C++, C#, Java, Kotlin, Pascal, Assembler, PHP
Hallo Jan,

super dass es nun funktioniert :cool:

Wenn du lust hast, mach doch mal ein Video oder Bilder von deinem Projekt.

Ist es die Platine mit den kreisförmig angeordneten RGB-LEDs, wo du das Layout unter jeder LED ebenfalls gleichmäßig gedreht haben wolltest?
 

LotadaC

Sehr aktives Mitglied
22 Jan 2009
3.426
62
48
Marwitz
Sprachen
BascomAVR, Assembler
Hab ich auch schon vermutet...

Da die eigentliche Frage gelöst zu sein scheint (leider ohne die Lösung hier zu zeigen), können wir ja ein wenig rumspammen...

So ein LED-Bit kann man sich ja in drei Phasen teilen: Hi, Data, low.
Jede der Phase soll zwischen 375ns und 467ns dauern, um das Timing des Datenblattes einzuhalten.
Zur Verwendung des konventionellen UART:
Ein UART-"Byte beginnt mit einem Startbit(=0), es folgen 5..9 Datenbits LSB first, anschließend ggf ein Paritätsbit und ein Stopbit (=1).
Das Paritätsbit wird inhaltspezifisch automatisch gebildet - man müßte also jedesmal die Datenbits betrachten, und dann zwischen odd/even wählen - oder es eben nicht nutzen.
Start=0 und Stop=1 erzwingen eine externe Invertierung. Effektiv kann man so drei LED-Bytes pro UART-"Byte" übertragen - mit sieben Datenbits (+ Start- und Stopbit).

Der UART verwendet bei doppelter Übertragungsgeschwindigkeit (U2X) acht samples pro Bit - ein 16MHz getakteter Controller käme also (mit UBRR=0) so auf 2Mbaud. Wären 500ns für die LED-bit-Phasen - 1500ns pro LED-bit.
Ist knapp außerhalb der Datenblatt-Timings (1400ns) - könnte aber trotzdem klappen.
Bei 20MHz Takt käme man auf 2,5Mbaud (400ns pro Phase, 1200ns pro LED-bit) - das ist sauber drin...

Pro LED wären acht "Bytes" zu übertragen (1 Uart-Byte = 9 UART-Bits entsprechen 3 LED-Bits. Acht folglich 24 LED-Bits).
Idee (ATtiny2313A):
  • U2X in UCSRA setzen (Doppeltempo)
  • UCSZ0 in UCSRC löschen (7 Databits)
  • TXEN in UCSRB setzen (Transmitter aktivieren)
  • D1 in DDRD setzen (TxD ist Ausgang)
Danach je UDRE in UCSRA pollen wenn es frei ist ein Byte nach UDR schreiben...
Die zu übertragenden Bytes sind (sollte eine LED Grün leuchten (100%) lassen):
0x12
0x12
0x52
0x5B
0x5B
0x5B
0x5B
0x5B

Mein 2312A taktet mit 8MHz (interner RC), entsprechend muß man sich die Timings angepaßt denken, die externe Invertierung ebenso...
uart_ws2812.png

@Dirk , @Janiiix3 ...
kann das mal wer von Euch ausprobieren?
 

Dirk

Administrator
Teammitglied
28 Jan 2007
4.308
150
63
Mittelhessen, Giessen
Sprachen
ANSI C, C++, C#, Java, Kotlin, Pascal, Assembler, PHP

Janiiix3

Aktives Mitglied
28 Sep 2013
1.329
10
38
Hannover
Sprachen
ANSI C, C#
Moin Jungs^^

Sorry das ich jetzt erst antworte, bin leider noch Krankgeschrieben und lag die ganze Zeit flach.
Also, es ist ja kein Projekt, habe auf nem Breadboard einfach mal son China teilchen gesteckt und wollte schauen ob das funktioniert. Sind einfach nur 8 x diese RGB Leds drauf.

Ich werde jetzt klein anfangen und mir mal den Assembler reinziehen.
 

LotadaC

Sehr aktives Mitglied
22 Jan 2009
3.426
62
48
Marwitz
Sprachen
BascomAVR, Assembler
Kannst Du trotzdem mal den Vorschlag aus #13 unter C testen?
Wenn Du den verwendeten Controller angibst, würde ich mal versuchen, daß C-Programm dazu zu ... stricken...
 

Janiiix3

Aktives Mitglied
28 Sep 2013
1.329
10
38
Hannover
Sprachen
ANSI C, C#
Du willst dich jetzt in C Versuchen?
Das ist ein 2560 er
 

LotadaC

Sehr aktives Mitglied
22 Jan 2009
3.426
62
48
Marwitz
Sprachen
BascomAVR, Assembler
Du willst dich jetzt in C Versuchen?
Ich bereu es jetzt schon...
zumindest compiliert es fehlerfrei...


CodeBox C
/*
 * GccApplication4.cpp
 *
 * Created: 23.10.2017 17:44:37
 *  Author: LotadaC
 */


#include <avr/io.h>
#include <util/delay.h>
uint8_t data[]={0x12,0x12,0x52,0x5b,0x5b,0x5b,0x5b,0x5b};

int main(void)
{
 UCSR0A|=(1<<U2X0);
 UCSR0C&=~(1<<UCSZ00);
 UCSR0B|=(1<<TXEN0);
 DDRE|=(1<<PE1);
 
    while(1)
    {
  for (int i=0; i<8;i++)
  {
   while(!(UCSR0A&(1<<UDRE0)))
   {
    asm("nop"); 
   }
   UDR0=data[i];
  }
  _delay_ms(500);
    }
}

Sollte den UART0 verwenden. TXD0 müßtest Du also über einen externen Transistor invertieren. Sollte die erste LED voll grün leuchten lassen. 16MHz sind allerdings von den Timings her grenzwertig, kannst es ja trotzdem mal versuchen.
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)