WS2812

Los LED’s RGB de tipo WS2811, WS2812B, WS2814, y WS2815 entre muchos otros que hay como el SK6812, son los denominados LED inteligentes o LED addressable. Básicamente, tienen un controlador integrado fabricado por Worldsemi. Estos funciona con 5V y un máximo de 60mA a su máximo brillo (20mA por LED y 3 colores), y tiene un protocolo comunicación propietario NRZ (Non-Return-to-Zero) para ir por un solo cable y sentido.

Note

Este código ha sido probado solo con WS2812B. Según el datasheet y el protocolo NRZ hace entender que puede funcionar en el resto de versiones, pero no he podido confirmarlo.

Para el proyecto usé este módulo WeAct WS2812 que se puede conseguir en AliExpress a buen precio y calidad. También puedes usar una tira de 20 mts con 200 LED’s WS2811B.

Hice una serie de pruebas sobre la tira de 200 LED’s que tiene el WS2811B para conocer su consumo real, además de lo que indica el datasheet. Se alimenta con 5v y funciona perfecto, con menos no funciona del todo bien, toda la tira con los LED’s apagados, pero conectados al VCC consume 120mA. Luego empecé a entender solo uno e ir jugando con los colores, básicamente cada color primario que hace uso de un solo LED interno que consume 10mA, con dos colores primarios al máximo para generar el color amarillo son 20mA y los tres colores primarios al máximo para generar el color blanco gasta 30mA.

El siguiente código se comunica con tres LED’s en serie por el pin PA0. Una nota curiosa, estos LED’s no son del orden RGB, sino GRB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

typedef struct {
    uint8_t g,r,b;
} color_t;

const color_t colors[] = {
    {0x00, 0x00, 0x00}, // Off
    {0xFF, 0xFF, 0xFF}, // White
    {0x00, 0xFF, 0x00}, // Red
    {0xFF, 0x00, 0x00}, // Green
    {0x00, 0x00, 0xFF}, // Blue
    {0xFF, 0xFF, 0x00}, // Yellow
    {0xA5, 0xFF, 0x00}, // Orange
    {0x00, 0xFF, 0xFF}, // Purple (Magenta)
    {0xFF, 0x00, 0xFF}, // Cyan
    {0x69, 0xFF, 0xB4}, // Pink
    {0xE0, 0x40, 0xD0}, // Turquoise
    {0x00, 0x94, 0xD3}  // Violet
};

#define NUM_COLORS (sizeof(colors)/sizeof(colors[0]))
#define NOP() __asm__ __volatile__("nop")

static inline void ws_send0(void) {
    PORTA.OUTSET = PIN0_bm; // +2c
    for (uint8_t i=0;i<6;i++) NOP(); // TH0 ~0.33-0.35us
    PORTA.OUTCLR = PIN0_bm; // +2c
    for (uint8_t i=0;i<23;i++) NOP(); // TL0 ~0.96us
}

static inline void ws_send1(void) {
    PORTA.OUTSET = PIN0_bm; // +2c
    for (uint8_t i=0;i<13;i++) NOP(); // TH1 ~0.625-0.67us
    PORTA.OUTCLR = PIN0_bm; // +2c
    for (uint8_t i=0;i<15;i++) NOP(); // TL1 ~0.62-0.63us
}

static inline void ws_send_byte(uint8_t v) {
    for (int8_t i = 7; i >= 0; i--) {
        if (v & (1 << i)) ws_send1(); else ws_send0();
    }
}

static inline void ws_send_color(uint8_t g, uint8_t r, uint8_t b) {
    ws_send_byte(g);
    ws_send_byte(r);
    ws_send_byte(b);
}

static inline uint8_t scale8(uint8_t v, uint8_t s) {
    return (uint16_t)v*s/255;
}

static inline void ws_send_color_scaled(color_t c, uint8_t s) {
    ws_send_color(scale8(c.g,s), scale8(c.r,s), scale8(c.b,s));
}

static inline void ws_reset(void) {
    _delay_us(60);
}

int main(void) {
    CCP = CCP_IOREG_gc;                         // Disable Configuration Change Protected register.
    CLKCTRL.OSCHFCTRLA = CLKCTRL_FRQSEL_24M_gc; // Configure to 24Mhz.

    double brightness = 3;

    // PA0:
    PORTA.DIRSET = PIN0_bm;
    PORTA.OUTCLR = PIN0_bm;

    while (1) {
        for (uint8_t i = 0; i < NUM_COLORS; i++) {
            cli();
            ws_reset();
            ws_send_color_scaled(colors[(i + 1) % NUM_COLORS], brightness);
            ws_send_color_scaled(colors[(i + 2) % NUM_COLORS], brightness);
            ws_send_color_scaled(colors[(i + 3) % NUM_COLORS], brightness);
            sei();
            _delay_ms(500);
        }
    }
}

X-mas

Si queremos hacer un efecto simple de las luces de navidad:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// ...
#define LFSR_SEED 0x5B
// ...

static uint8_t lfsr(void) {
    static uint8_t cnt = LFSR_SEED;
    return (cnt = (cnt >> 1) ^ (-(cnt & 1) & 0xB4));
}

uint8_t zero(uint8_t idx) {
    if (lfsr() & 1)
        return idx;
    return 0;
}

int main(void) {
    //...

    while (1) {
        cli();
        ws_reset();
        for (uint8_t y = 0; y < 55; y++) {
            ws_send_color_scaled(COLORS[ zero(1)], BRIGHTNES_MAX);
            ws_send_color_scaled(COLORS[ zero(2)], BRIGHTNES_MAX);
            ws_send_color_scaled(COLORS[ zero(3)], BRIGHTNES_MAX);
            ws_send_color_scaled(COLORS[ zero(4)], BRIGHTNES_MAX);
            ws_send_color_scaled(COLORS[ zero(5)], BRIGHTNES_MAX);
            ws_send_color_scaled(COLORS[ zero(6)], BRIGHTNES_MAX);
        }
        sei();

        _delay_ms(500);
    }
}

Compilar y subirlo

Para compilarlo deberá ejecutar el siguiente comando:

1
2
3
4
avr-gcc -mmcu=avr128da28 \
    -DF_CPU=24000000UL \
    -g -Os -std=gnu99 -Wall -o main.elf *.c
avr-objcopy -O ihex  main.elf main.hex

Para subir el programa al microcontrolador, deberá ejecutar el siguiente comando:

1
avrdude -c serialupdi -p avr128da28 -P /dev/tty.usbserial-2110 -e -F

Si todo fue bien, podrá disfrutar de los colores.