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.

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
87
#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, 0xFF, 0x00}, // Red
    {0xFF, 0x00, 0x00}, // Green
    {0x00, 0x00, 0xFF}, // Blue
    {0xFF, 0xFF, 0x00}, // Yellow
    {0x00, 0xFF, 0xFF}, // Purple (Magenta)
    {0xFF, 0x00, 0xFF}, // Cyan
    {0xFF, 0xFF, 0xFF}, // White
    {0xA5, 0xFF, 0x00}, // Orange
    {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;

    ws_reset();

    while (1) {
        for (uint8_t i = 0; i < NUM_COLORS; i++) {
            cli();
            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);
            ws_reset();
            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.