Understanding about led strip WS2811 without using Fastled lib question

Hello,

I just started with WS2811, I found 2 libraries to control it which are FastLed and Adafruit Neonpixel.

I want to know how the data moves from a led to another but when reading these two libraries, I cannot figure it out.

Can anyone help me with writing a short code with using only digitalWrite, analogWrite, delay function to set color RGB (127, 50, 70) for 10 leds. Initial states of these 10 leds are all white color. The delay time for each setting is 0.5s.

Thanks for you help!

Best regard.

You need to use the FastLed or Adafruit Neonpixel library.

These come with examples for you to learn from.

Yes, but I want to understand the theory of it because I want to control the led strip WS2811 by another MCU not Arduino. I want to rewrite the library FastLed & Neonpixel to reduce the size of codes.

Thanks for your replies.

"digitalWrite, analogWrite, delay function"

These are far too slow to handle the required timing for these LEDs.

You have to get down to the assembler level to get the timing accurate enough to make the LEDs operate.

I want to understand the theory of it because I want to control the led strip WS2811 by another MCU

The library code would be a good place to start

The LEDs needs very fast pulses at less than 1us in width, variations in the width defines if it 0 or 1. One chip needs 24 pulses, that is the same as 24 bits (8 for each color). When you have more chips in series you need more pulses, 24 for each chip. That means with 10 led you need to generate 240 pulses each better than with us precision.
The datasheets for each chip lists the precise timing requirements, but is is usual about 400 and 800us for 0 and 1, the low uses the opposite time, but is usually not very critical. To signal the end of bits you simply hold the dataline low for some time (50us) and all the chips will update the led output.

On the Atmega chips it is fairly tricky to write the code because it can only do up to 16 assembler instructions in a us, on faster processor it gets easier, but watch out for interrupts!

daonguyen3154:
Yes, but I want to understand the theory of it because I want to control the led strip WS2811 by another MCU not Arduino. I want to rewrite the library FastLed & Neonpixel to reduce the size of codes.

Thanks for your replies.

I’m not actually sure which type of 3 colour serially fed LEDs tape I used but I did this on a PIC using the SPI register to output the data. Set the SPI clock speed such that the time for it to send a complete byte is the same duration that the LEDs need to receive 2 bits. I encoded 2 bits to be sent as 8 bits to load into the SPI register as follows:
For a 0 I put 0001 into the register and for a 1 I put 0111 into the register. As each bit to transmit is encoded as 4 bits to the SPI Tx register you send 2 bits at a time. However, timings are tight and you have to have the next byte ready to load into the SPI register as soon as the last one has shifted out.

This works because using 0001 and 0111 gives the different pulse widths the LEDs see as 0 and 1.

Here’s the function that generates the data for the SPI register, which I am offering ‘as is’ with no support. This was written for a PIC18F46K22, but I would think the same or similar would work for others. Of course you’ve not said which micro-controller you are using (or I missed it), so this might not be what you need, but it will at least give you something to think about.

void LED_output() {
    unsigned char i;
    unsigned char SPI_byte;
    
    for (i = 0; i < LED_number; i++) {
        if (LED[i].G & 0x40) SPI_byte = 0x0e;                                //Byte order is GRB because that is what the LEDs require
        else SPI_byte = 0x08;                                                //Bit order is 6, 7, 4, 5, 2, 3, 0, 1
        if (LED[i].G & 0x80) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].G & 0x10) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].G & 0x20) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].G & 0x04) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].G & 0x08) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].G & 0x01) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].G & 0x02) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].R & 0x40) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].R & 0x80) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].R & 0x10) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].R & 0x20) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].R & 0x04) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].R & 0x08) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].R & 0x01) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].R & 0x02) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].B & 0x40) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].B & 0x80) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].B & 0x10) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].B & 0x20) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].B & 0x04) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].B & 0x08) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
        
        if (LED[i].B & 0x01) SPI_byte = 0x0e;
        else SPI_byte = 0x08;
        if (LED[i].B & 0x02) SPI_byte |= 0xe0;
        else SPI_byte |= 0x80;  
        SSP2BUF = SPI_byte;
        __delay_us(SPI_delay_us);
    }
    //INTCONbits.GIE = 1;
    //PIE1bits.RC1IE = 1;
}

Have a look at the Adafruit_NeoPixel library, the assembly language is fairly straightforward and well documented, with options for various CPU speeds. The FastLED library gets a bit more cryptic, unless you are extremely familiar with preprocessor directives for the compiler, and is spread out amount multiple files making it harder to follow.

The data movement from one LED to another is fairly simple, each LED needs 24 bits for itself, so it takes the first 24 bits of data it receives as its own data, then any additional data is passed through to its output data pin. If you look at the data stream on a logic analyzer you would see the data at each successive LED is 24 bits shorter than the previous LED, until the last LED in the string generates no data on its output.

HKJ-lygte:
The LEDs needs very fast pulses at less than 1us in width, variations in the width defines if it 0 or 1. One chip needs 24 pulses, that is the same as 24 bits (8 for each color). When you have more chips in series you need more pulses, 24 for each chip. That means with 10 led you need to generate 240 pulses each better than with us precision.
The datasheets for each chip lists the precise timing requirements, but is is usual about 400 and 800us for 0 and 1, the low uses the opposite time, but is usually not very critical. To signal the end of bits you simply hold the dataline low for some time (50us) and all the chips will update the led output.

On the Atmega chips it is fairly tricky to write the code because it can only do up to 16 assembler instructions in a us, on faster processor it gets easier, but watch out for interrupts!

Thank you, I am now fully understand it

*********************************************
 * WS2812 data line driver.
 *********************************************/
void lsd( ) {
    /*
     * I/O9 on RedBoard (and presumably any UNO compatabla device) is output.
     *
     * X        :: r26,r27      color_buff pointer
     * color_in :: r25          read via X, current color transmitting
     * buff_cnt :: r24          count 30 colors
     * bit_cnt  :: r23          count 8 bits
     */

asm volatile (
    "sbi    0x04, 5"                    "\n\t"  // DDRB[5] == 1; Port B.5 is output: TEST LED

    "ldi    r24, 30"                    "\n\t"  // color-reg counter
    "ldi    r23, 8"                     "\n\t"  // bit counter
    "ldi    r26, lo8((color_buff))"     "\n\t"  // X -> color_buff
    "ldi    r27, hi8((color_buff))"     "\n\t"  //      "
    "ld     r25, X+"                    "\n\t"  // get color data

    // No other interrupts running
    //"bclr 7"              "\n\t"  // disable global interrupts during transmission:
                                    // Arduino "built-ins" run various timer ISRs.
                                    // They will cause intermittent flashing of the LEDs.

    // color-loop (16MHZ Clk):
"1:                          \n\t"
    "lsl    r25"            "\n\t"  // new bit into C
    "sbi    0x05, 0x00"     "\n\t"  // set LED line high
    "brcc   2f"             "\n\t"  // jif data bit is "0"
    "nop"                   "\n\t"  // "1" high period:
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "cbi    0x05, 0x00"     "\n\t"  // "1" low period:
    "rjmp   3f"             "\n\t"

"2:                          \n\t"
    "nop"                   "\n\t"  // "0" high period:
    "nop"                   "\n\t"
    "cbi    0x05, 0x00"     "\n\t"  // "0" low period:
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"

"3:                          \n\t"
    "nop"                   "\n\t"
    "nop"                   "\n\t"
    "dec    r23"            "\n\t"  // test 8-bit counter
    "brne   1b"             "\n\t"  // jif more bits to send to "color-loop"ode]

    // next-color:
    "dec    r24"            "\n\t"  // test color counter
    "breq   4f"             "\n\t"  // jif color-counter done (EXIT)

    // next-led     
    "ld     r25, X+"        "\n\t"  // get next color data
    "ldi    r23, 8"         "\n\t"  // bit counter
    "jmp    1b"             "\n\t"  // goto "color-loop"

    // quit:
//"4:    bset   7"              "\n\t"  // enable global interupts
"4:  nop"                   "\n\t"  // enable global interupts
);

}