viernes, 21 de noviembre de 2025

Control de LED WS2812

Control de LED inteligente WS2812

Fig1. Controlador Inteligente WS2812

Bienvenido a mi blog que tiene por objetivo compartir el interés en la electrónica y en especial la programación de microcontroladores PIC. Antes de empezar quiero recordarte que estoy atento a cualquier sugerencia o critica constructiva relacionada con esta publicación.

En esta ocasión veremos como controlar los leds inteligentes WS2812 con un microcontrolador PIC16F que utiliza un cristal oscilador de 20MHz.  Como sabrán los tiempo necesarios para la señalización de estos leds son muy cortos lo cual limita su uso en microcontroladores PIC16 de gama media, esta limitación es menor en PIC mas modernos la serie PIC16F188xx, o algunos que disponen del modulo CLC como es el caso del PIC16F1509 donde Microchip publica una nota aplicativa <AN1606> referido al tema. 

En la búsqueda de practicar la programación utilizando instrucciones del ensamblador PIC-AS, decidí implementar el control para estos led utilizando el PIC16F887. La programación del microcontrolador se lleva a cabo con el software  de diseño  MPLABX  y el compilador de lenguaje C para PIC XC8, ambos disponibles en la pagina de microchip de forma gratuita. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:   

<<MPLABX v6.20>>   

<<XC8 v3.0>>

Recordar al lector que en esta ocasión se necesitara de conocimientos mínimos de programación en lenguaje C, y el juego de instrucciones de ensamblador.

Controlador WS2812

Fig2. Tira de LEDS Inteligentes (WS2812)

Este dispositivo forma parte de un tipo de luces conocidos como LED's inteligentes o NEO pixel, y se caracteriza porque posee un circuito integrado (controlador) incorporado en el mismo encapsulado, con la que es posible controlar individualmente su color y brillo a través de un protocolo serial basado en la retransmisión de informacion en cadena. Estas luces están diseñadas para trabajar con 5V y existe una variante WS2812B que difiere físicamente como notara en la imagen inferior y representa una versión mejorada.

Fig3. WS2812 (Izquierda) y WS2812B (Derecha)

El funcionamiento se resume en los siguientes pasos:

  • Cada LED recibe un dato de 24-bit con el comando de color GRB compuesto por tres datos de 8-bit que controllan el brillo de la luz Verde, Rojo y Azul en un rango de 0(Apagado) a 255(Maximo brillo).

  • Todos los LED's se conectan en serie formando un arreglo de puntos (pixels), la información se envía a través de un solo pin de datos, que corresponde a la entrada (DI) del primer LED. 

  • Cuando un LED recibe y almacena su dato de 24-bit, este encamina la señalización por el pin DO al resto de los LEDS conectados.

  • Mediante este protocolo de comunicación de alta velocidad, se deben enviar consecutivamente datos de 24-bit para cada LED conectado al circuito.

En la figura 4 se puede observar la lógica para representar cada bit del protocolo, y la conexión en cascada para la única linea de comunicación, en cuanto a la alimentación, todos los diodos deberán conectarse a 5V (VCC) y tierra (VSS). 

Fig4. Señalización del protocolo serial

En cuanto a los tiempo de cada bit, la siguiente imagen nos muestra valores en el rango de los microsegundos, por lo que es importante mantener esta temporización dentro del rango de tolerancia permisible. 

Fig5. Temporización para el protocolo de control

Aquí notamos que los tiempos para validar un bit del protocolo son muy cortos, consideramos el uso de un Microcontrolador PIC de gama media como el PIC16F8xx. De acuerdo a las especificaciones técnicas del PIC16F8xxx este puede trabajar hasta los 20MHz utilizando un cristal oscilador HS, pero debemos tomar en cuenta que la arquitectura de este PIC requiere de cuatro pulsos de oscilador para completar un ciclo de instrucción o maquina (Tcy),  por lo que al final este tiempo sera Tcy=4/20MHz = 0.2us.

Fig6. Transición de nivel bit 0/1.

Note en la figura 6 que la diferencia de señal para el bit 0 y el bit 1, es un retardo en la transición de nivel alto a bajo de solo 0.45 us, este tiempo es importante porque equivale a por lo menos dos ciclos de instruccion que serán utilizados para establecer correctamente los nivel lógicos del protocolo serial. La estructura de los datos y el protocolo a enviar se observa en la siguiente figura.

Fig7. Protocolo serie para el control de luces

El dato de 24-bit corresponde a una trama GRB(Verde, Rojo, Azul) que controla la intensidad de cada color en un rango de 0 - 255. Por lo tanto si tenemos un circuito con 20 luces WS2812 conectados en cascada, se deben enviar 20 * 24-bit consecutivamente, en este punto debemos tomar en cuenta que el tiempo de espera entre cada dato GRB de 24-bit no debe ser mayor a 50us. 

Una tiempo de espero mayor o igual a 50us con la salida DO en nivel bajo, indica que ya no hay mas datos a transmitir, quedando las luces de cada ajustado a los valores enviados previamente. Si se desea apagar todas las luces, entonces se deben enviar los datos GRB con valores en 0.

Esquema del circuito PIC

Fig8. Esquema del circuito PIC16F
 Se observa el esquema del circuito que utilizaremos para controlar un arreglo de ocho luces WS2812 conectados en linea recta. Un pulsador conectado al pin RB0 nos permitirá controlar el encendido y apagado de las luces con un nivel de brillo determinado por las variables de color GRB.

Programación del PIC

La programación de los tiempos requeridos para el protocolo de control necesitara el uso de instrucciones nativas de ensamblador, porque tomaremos como base el ciclo de instrucción Tcy=0.2us. Se creara una rutina en ensamblador PIC-AS que se llamara wswrite, esta rutina emitirá por un pin del puerto la trama de 24-bit correspondiente al dato GRB.  

Para es caso particular la rutina wswrite dispone las instrucciones asm ajustadas a los tiempos de bit del protocolo, considerando que el PIC16F opera con cristal de 20Mhz. La siguiente figura ilustra como se relaciona la duración de un ciclo de instrucción con cada fracción de tiempo que requiere la señal de control de un dato GRB. 

Fig9. Temporización de bit 0 y 1

A continuación se describe el código de la rutina wswrite que sera llamado desde el programa principal main.c

Código del fichero wswrite.s

#include <xc.inc>
WSPORT equ PORTD   
;Puerto de Control LED
WSPIND equ 0       
;Pin asignado DO
WSADDR equ 0x20    
;Dirección RAM GRB 24-bits    
psect   barfunc,local,class=CODE,delta=2
;PIC10/12/16
global _wswrite    
;extern of bar function in C
_wswrite:
    BANKSEL(WSADDR)
    movlw WSADDR   
;Inicia el array
    movwf FSR      
;Carga registro indirecto
   
//bit7 G-LED primer byte del Dato de 24-bit
    bsf WSPORT,WSPIND  
; 0.2u coloca DO=HIGH
    btfss INDF,7        
;+0.2u verifica el bit 7
    bcf WSPORT,WSPIND  
;+0.2u coloca DO=LOW si bit=0
    nop                
;+0.2u
    bcf WSPORT,WSPIND  
;+0.2u coloca DO=LOW si bit=1
    nop                
;+0.2u
    nop                
;+0.2u
   
//bit6
    bsf WSPORT,WSPIND
    btfss INDF,6
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop            
    nop
   
//bit5
    bsf WSPORT,WSPIND
    btfss INDF,5
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit4
    bsf WSPORT,WSPIND
    btfss INDF,4
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit3
    bsf WSPORT,WSPIND
    btfss INDF,3
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit2
    bsf WSPORT,WSPIND
    btfss INDF,2
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit1
    bsf WSPORT,WSPIND
    btfss INDF,1
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit0
    bsf WSPORT,WSPIND
    btfss INDF,0
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    incf FSR
   
//bit7 R-LED segundo byte del Dato de 24-bit
    bsf WSPORT,WSPIND  ; 0.2u coloca DO=HIGH
    btfss INDF,7       ;+0.2u verifica el bit 7
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=0
    nop                ;+0.2u
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=1
    nop                ;+0.2u
    nop                ;+0.2u
   
//bit6
    bsf WSPORT,WSPIND
    btfss INDF,6
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop            
    nop
   
//bit5
    bsf WSPORT,WSPIND
    btfss INDF,5
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit4
    bsf WSPORT,WSPIND
    btfss INDF,4
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit3
    bsf WSPORT,WSPIND
    btfss INDF,3
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit2
    bsf WSPORT,WSPIND
    btfss INDF,2
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    n
op
    //bit1

    bsf WSPORT,WSPIND
    btfss INDF,1
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
    //
bit0
    bsf WSPORT,WSPIND
    btfss INDF,0
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    incf FSR

   
//bit7 B-LED tercer byte del Dato de 24-bit
    bsf WSPORT,WSPIND  ; 0.2u coloca DO=HIGH
    btfss INDF,7       ;+0.2u verifica el bit 7
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=0
    nop                ;+0.2u
    bcf WSPORT,WSPIND  ;+0.2u coloca DO=LOW si bit=1
    nop                ;+0.2u
    nop                ;+0.2u
   
//bit6
    bsf WSPORT,WSPIND
    btfss INDF,6
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop            
    nop
   
//bit5
    bsf WSPORT,WSPIND
    btfss INDF,5
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit4
    bsf WSPORT,WSPIND
    btfss INDF,4
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit3
    bsf WSPORT,WSPIND
    btfss INDF,3
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
    /
/bit2
    bsf WSPORT,WSPIND
    btfss INDF,2
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit1
    bsf WSPORT,WSPIND
    btfss INDF,1
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
   
//bit0
    bsf WSPORT,WSPIND
    btfss INDF,0
    bcf WSPORT,WSPIND
    nop
    bcf WSPORT,WSPIND
    nop
    nop
return

Observe que esta rutina utiliza el valor de tres posiciones consecutivas de memoria RAM a partir de la ubicacion definida en WSADDR, los valores de estas posiciones corresponden al brillo de luz GRB(Verde, Rojo, Azul). La referencia a esta ubicación tambien debe ser declarada en el programa principal con el fin de reservar el espacio de memoria. Así mismo debe definirse el puerto y pin para la salida de control ajustando las definiciones WSPORT y WSPIND.
El siguiente código muestra un programa principal que realiza el control de las luces del circuito descrito anteriormente, el ejemplo muestra como encender y apagar ocho luces WS2812 con un pulsador, cada vez que se presione el pulsador se cambiar el color de la luz.

Código de ejemplo fichero main.c

#pragma config FOSC=HS, WDTE = OFF, BOREN = OFF, LVP = OFF
#include <xc.h>
#define _XTAL_FREQ 20000000
uint8_t butstate, i, val=0;
//Variables de control de estado
typedef struct
{
    uint8_t green;
    uint8_t red;
    uint8_t blue;
} WS_t;
//datos de control GRB 24-bit
WS_t wsled
__at(0x20); //Reserva espacio RAM
extern uint8_t wswrite(void);
void main(void)
{
   
//Operacion FOSC=20MHz con Oscilador HS Tcy=0.2u
    ANSEL = 0;
//Deshabilita canales ANS0-7
    ANSELH = 0;
//Deshabilita canales ANS8-13
    TRISDbits.TRISD0 = 0;
//Salida de control WS2812
    PORTDbits.RD0 = 0;
    OPTION_REGbits.nRBPU = 0;
//Activa pull-ups del PORTB
    while(1)
    {
        if(butstate != PORTBbits.RB0)
//Si pulsador cambia de estado
        {
            butstate = PORTBbits.RB0;
//Actualiza estado de pulsador
            if(butstate)
//Si el pulsador esta liberado
            {
                wsled.red = 0; 
//Brillo luz roja
                wsled.green = 0;
//Brillo luz verde
                wsled.blue = 0;
//Brillo luz azul
                for(i=0;i<8;i++)
//Emite senal de control a ocho leds
                    wswrite();
//Apaga las luces
            }
            else
//Si el pulsador esta presionado
            {
                wsled.red = 0;
                wsled.green = 0;
                wsled.blue = 0;
                if(val==0) wsled.red = 100;
//Brillo luz roja
                if(val==1) wsled.green = 100;
//Brillo luz verde
                if(val==2) wsled.blue = 100;
//Brillo luz azul
                if(val++>=2) val = 0;
                for(i=0;i<8;i++)
//Emite senal de control a ocho leds
                   wswrite();
//Enciende la luz en color
            }
        }
        __delay_ms(100);
//retardo entre lecturas del pulsador
    }
}

Aquí dejo la animacion que muestra el funcionamiento del ejemplo anterior utilizando una placa de desarrollo basado en el PIC16F887.

Fig10. Funcionamiento del programa
En el siguiente video describo como realizar el control de encendido de estas luces de forma gradual utilizando el arreglo lineas de 8 leds y posterior uno circular de 24 leds, el codigo del proyecto MPLABX lo encuentras en mi reporsitorio git. <https://github.com/pablinza/piclab2>

Conclusiones y recomendaciones

Hemos visto como realizar el control de luces conformado por led inteligentes WS2812 utilizando un microcontrolador PIC16F a 20Mhz, si bien es tomar como base la rutina wswrite escrito en ensamblador para desarrollar proyectos de iluminación es importante tomar en cuenta los siguientes aspectos.

  • Al momento de enviar los datos a cada led llamando consecutivamente a la rutina wswrite se debe deshabilitar la interrupción global, puesto que una interrupción durante el proceso de envío, romperá los tiempos ajustados con cada instrucción ASM de la rutina.
  • Si bien es posible crear aplicaciones para mostrar mensajes o crear diversos efectos con imágenes para un arreglo lineal o incluso con disposicion matricial, se debe considerar las limitaciones de memoria RAM que tienen los PIC16F de rango medio, por lo que es recomendable migrar a un microcontrolador con mayores prestaciones como lo son la gama PIC18, PIC32 o dSPIC, pero necesitara realizar modificaciones en la rutina wswrite

Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.  

Atentamente, Pablo Zárate Arancibia email: pablinza@me.com / pablinzte@gmail.com, @pablinzar
Santa Cruz de la Sierra - Bolivia