0
\$\begingroup\$

I'm trying to create project where I can light up WS2812B whatever color I want and specific RGB color code is delivered through UART. So when I receive the code I'm trying to send data to WS2812B but it seems to be working only every 2nd time when it actually shouldn't and it doesn't work at all when it should.

I'm using Atmel's Atmega328P-PU and its CPU speed is set to 8MHz. This code actually worked fine when I 1st wrote it but I had to change some stuff in UART section so had to compile and flush it again but this time it was done from Linux using avrdude command line tools instead of Windows 7(which was on my friends PC and got deleted).

This is data timing diagram for WS2812B

enter image description here

Everything in code works fine except for sending '1' and '0' to LED and it happens in function set_leds_color. Weird thing is that if I leave the __builtin_avr_delay_cycles(); values as they were before my UART code updates (and as are shown in code) it always sets LED's color to white kind of like it would always send only '1' to led (255,255,255). Even if I set delay for '0' to __builtin_avr_delay_cycles(1) nothing changes - any color is displayed as white. Now the interesting part is that if I remove this delay for '0' so code become like this

//send 0
else{              
   DATA_PORT = high_data;
    DATA_PORT = low_data;
}

when I set the same color multiple times in a row - LED actually sets my desired color 1 time but 2nd time sets white again. This goes on. I have no idea what the problem is. I tested that __builtin_avr_delay_cycles() works as expected and my CPU frequency is indeed 8Mhz. I also sent rgb_array[] values from mcu to my PC thought UART and they are fine as well. Can anybody give me some suggestions what might be wrong? Thanks.

Here is my final code

#include <avr/io.h>
#include <util/delay.h>

#define DATA_PORT PORTD
#define DATA_PIN PD2
#define DATA_DDR DDRD

/*
UART stuff
*/

void fill_array(unsigned int* arr, unsigned int color)
{
    for(int i = 0; i < 8; ++i)
    arr[i] = color & (1 << (7 - i));
}

void set_color(unsigned int* arr, unsigned int red, unsigned int green, unsigned int blue)
{
    fill_array(arr, green);
    fill_array(&arr[8], red);
    fill_array(&arr[16], blue);
}

void set_leds_color(unsigned int red, unsigned int green, unsigned int blue)
{
    //prepare 24 color bits to send to LED
    unsigned int rgb_array[24];
    set_color(rgb_array, red, green, blue);

    int high_data = DATA_PORT | (1 << DATA_PIN);
    int low_data = DATA_PORT & (~(1 << DATA_PIN));

    for(int i = 0; i < 24; ++i){
        //send 1
        if(rgb_array[i]){   
            DATA_PORT = high_data;
            __builtin_avr_delay_cycles(6);    //1 clock cycle is 0.125us 
            DATA_PORT = low_data;
        }
        //send 0
        else{              
            DATA_PORT = high_data;
            __builtin_avr_delay_cycles(3);  //1 clock cycle is 0.125us 
            DATA_PORT = low_data;
        }
    }
}

int main(void)
{
    DATA_DDR = (1 << DATA_PIN);
    DATA_PORT &= ~(1 << DATA_PIN);

    initialize_UART();

    while(1)
    {
        int color_code[3];
        if(receive_color_from_uart(color_code))
        {
            set_leds_color(color_code[0], color_code[1], color_code[2]);
        }
    }

} 
\$\endgroup\$
  • \$\begingroup\$ 0.4µs (T0H in the timing diagram) is 3.2 clock cycles at 8MHz. So the ATmega is barely fast enough to create the WS2812B signal with C code. Other libraries, e.g. the Adafruit NeoPixel library use highly optimized assembler code. And if I'm not mistaken, they block all interrupts. Have you checked with a DSO that the your signal meets the required timing? \$\endgroup\$ – Codo Feb 7 '18 at 19:35
  • \$\begingroup\$ Yea but its 0.4 +- 0.15us and its more than enough gap to make it happen and it actually was working excellent with this exact code. But thanks for the suggestion. \$\endgroup\$ – etrusks Feb 7 '18 at 19:45
  • \$\begingroup\$ I'm surprised you're using the value 3 for __builtin_avr_delay_cycles. That's almost 0.4µs. But DATA_PORT = ... also takes time. Similar for the value 6. Try to lower these values and turn off interrupts. Overall, your code is good because it focuses on T0H and T1H timings. Many people have found that these timings are relevant while T0L and T1L can much higher. The just need to be lower than Treset, which is specified as at least 50µs. \$\endgroup\$ – Codo Feb 7 '18 at 19:57
  • \$\begingroup\$ Thanks man but I already tried values like 5,2 and other. Something starts to happen only when delay value for send zero is 0 and it just doesn't make sense. Also I dont have interrupts enabled, only UART is running \$\endgroup\$ – etrusks Feb 7 '18 at 20:03
0
\$\begingroup\$

So the weirdest thing fixed the issue. I created 2 more functions

void send_one_bit(int high, int low){
    DATA_PORT = high;
    __builtin_avr_delay_cycles(5);
    DATA_PORT = low;
    __builtin_avr_delay_cycles(1);
}

void send_zero_bit(int high, int low){
    DATA_PORT = high;
    __builtin_avr_delay_cycles(2);
    DATA_PORT = low;
    __builtin_avr_delay_cycles(1);
}

and used these to send zero or one byte

for(int i = 0; i < 24; ++i){
    //send 1
    if(rgb_array[i]){   
        send_one_bit(high_data, low_data);
    }
    //send 0
    else{              
        send_zero_bit(high_data, low_data);
    }
}

If I take code out of these functions and put directly inside of if/else statement kind of inlining it I always get white color. I would be happy if someone could explain this weird thing.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.