Hola!
Os presento al WS2812, un LED RGB con driver integrado. Y al WS2811 un driver para LEDs RGB.
Datasheet:
http://www.mikrocontroller.net/attachment/180459/WS2812B_preliminary.pdfHe tenido el placer de probar una tira de LEDs y jugar un poco usando Arduino. Realmente sencillo, pero no puedo evitar quererlo tambien para PICs, por lo que basandome en una libreria de Arduino desarrollada por Adafruit he decidido portarla a CCS.
Libreria de Adafruit:
https://learn.adafruit.com/adafruit-neopixel-uberguideMe referire siempre como "LED" que es lo que utilizare para pruebas, pero en realidad son lo mismo, ya que el WS2812 es un encapsulado de LED 5050 con el chip WS2811 integrado dentro del propio LED. Y el WS2811 es solo el IC, pero sin el LED.
Los LEDs se controlan con una señal de pulsos y no necesita señal de reloj.
Cada LED tiene un pin de entrada de datos, y otro de salida de datos. Los leds se conectan en cascada, es decir, la salida de uno con la entrada del siguiente. El primer LED utiliza el primer dato de la trama de datos, lo quita de la trama y reenvia los restantes, el siguiente led hace lo mismo, pilla el primer dato, lo quita y reenvia el resto. Asi no hace falta "direccionar" cada led, ya que es el propio led el que toma el dato que le corresponde.
Un unico pin del PIC puede controlar unos 1000 LEDs a 30Hz.
La forma de enviar los datos es la siguiente:
-Cada LED utiliza 3 bytes, cada byte nos dara 255 niveles de brillo para cada color, que combinados nos ofrecen colores de 24bits, es decir casi 17 millones de colores diferentes.
-Cada byte tiene 8 bits, por supuesto.
-Cada bit se envia como una combinacion de nivel alto + nivel bajo del pin que en total dura 1.25uS:
* CERO = 0.4uS High + 0.85uS Low
* UNO = 0.8uS High + 0.45uS Low
-El tiempo para enviar cada byte es de (1.25uS *

10uS
-El tiempo para enviar el valor RGB completo por cada led es (10uS * 3) 30uS
-Entre bit y bit, o entre byte y byte NO HAY DEMORA. Luego de enviar el valor del primero se envia inmediatamente el siguiente.
-Cuando se ha enviado los datos de todos los LEDs hay que hacer una pausa de al menos 50uS para volver a enviar.
Cada LED es capaz de mantener el color que se le asigne, por lo que no hace falta estar constantemente enviando datos.
Dado los tiempos tan apretados la velocidad minima para controlar estos leds es un PIC a 32Mhz e instrucciones optimizadas y medidas en assembler. Gracias a la ayuda de muchos compañeros en el foro de assembler he podido terminar una libreria basica para controlar estos leds.
la libreria es esta: (
ANTIGUO, descargar codigo para ultima version)
// ---- COMPROBACIONES DE COMPATIBILIDAD ----
#IF getenv("VERSION")<5.019
//test realizado con compilador CCS v5.019
//para versiones anteriores comprobar que el codigo
//generado se adecua a los tiempos requeridos
#ERROR Compilador antiguo
#ENDIF
#IF getenv("CLOCK") != 32000000
//necesitamos que el PIC corra a 32MHZ al menos
#ERROR Velocidad del PIC muy baja
#ENDIF
#IFNDEF NUM_LEDS
#ERROR "NUM_LEDS" no definido
#ELSE
#IF NUM_LEDS > 85
//solo podemos usar un maximo de 85 leds.
//Cada led usa 3 bytes, y nuestro contador de envio es un int
#ERROR "NUM_LEDS" tiene que ser menor a 85
#ELIF NUM_LEDS*3 > getenv("RAM")
#ERROR Tu PIC no tiene suficiente RAM para tantos LEDs
#ENDIF
#ENDIF
// ------------------------------------------
#include <stdlibm.h>
#byte INDF0 = getenv("SFR:INDF0")
#byte FSR0L = getenv("SFR:FSR0L")
#byte FSR0H = getenv("SFR:FSR0H")
#bit GIE = getenv("BIT:GIE")
int *pixels; //puntero al la primera direccion donde se guardan los valores
int brightness = 0; //permite ajustar el brillo sin modificar el color
int numLEDs = 0; //cantidad de leds en la tira
int numBytes = 0; //numero de bytes usados
void ConfigPixels(int n){
//n = numero de leds en la tira
numLeds = n; //establece la cantidad de leds a usar
numBytes = n * 3; //cantidad de bytes necesarios para manejar a todos los leds
pixels
= malloc(numBytes
); //crea espacio en memoria para los leds que vamos a usar
if(pixels){
}
}
int NumPixels(void){
return numLeds;
}
void SetPixelColor(int n, int r, int g, int b){
//Set pixel color from separate R,G,B components:
if(n < numLEDs) {
if(brightness) { // See notes in setBrightness()
r = (r * brightness) >> 8;
g = (g * brightness) >> 8;
b = (b * brightness) >> 8;
}
int * p = &pixels[n * 3];
*p++ = r;
*p++ = g;
*p = b;
}
}
void SetPixelColor(int n, int32 c){
//Set pixel color from 'packed' 32-bit RGB color:
if(n < numLEDs) {
int r = (int)(c >> 16);
int g = (int)(c >> 8);
int b = (int)c;
if(brightness) { // See notes in setBrightness()
r = (r * brightness) >> 8;
g = (g * brightness) >> 8;
b = (b * brightness) >> 8;
}
int * p = &pixels[n * 3];
*p++ = r;
*p++ = g;
*p = b;
}
}
int32 Color32(int r, int g, int b){
//Convierte los colores separados R,G,B en un paquete de 32 bits con el color
return ((int32)r << 16) | ((int32)g << 8) | b;
}
int32 GetPixelColor(int n){
//devuelve el color de un pixel que hayamos "coloreado" antes
//en formato de 32bits y en order RGB
if(n < numLEDs) {
long ofs = n * 3;
return (int32)((pixels[ofs] << 16) | (pixels[ofs + 1] << 8) | pixels[ofs + 2]);
}
return 0; // Pixel # is out of bounds
}
void SetBrightness(int b) {
// Adjust output brightness; 0=darkest (off), 255=brightest. This does
// NOT immediately affect what's currently displayed on the LEDs. The
// next call to show() will refresh the LEDs at this level. However,
// this process is potentially "lossy," especially when increasing
// brightness. The tight timing in the WS2811/WS2812 code means there
// aren't enough free cycles to perform this scaling on the fly as data
// is issued. So we make a pass through the existing color data in RAM
// and scale it (subsequent graphics commands also work at this
// brightness level). If there's a significant step up in brightness,
// the limited number of steps (quantization) in the old data will be
// quite visible in the re-scaled version. For a non-destructive
// change, you'll need to re-render the full strip data. C'est la vie.
// Stored brightness value is different than what's passed.
// This simplifies the actual scaling math later, allowing a fast
// 8x8-bit multiply and taking the MSB. 'brightness' is a uint8_t,
// adding 1 here may (intentionally) roll over...so 0 = max brightness
// (color values are interpreted literally; no scaling), 1 = min
// brightness (off), 255 = just below max brightness.
int newBrightness = b + 1;
if(newBrightness != brightness) { // Compare against prior value
// Brightness has changed -- re-scale existing data in RAM
int c;
int *ptr = pixels;
int oldBrightness = brightness - 1; // De-wrap old brightness value
long scale;
if(oldBrightness == 0)
scale = 0; // Avoid /0
else if
(b == 255) scale = 65535 / oldBrightness;
else
scale = (((long)newBrightness << 8) - 1) / oldBrightness;
for(int i=0; i<numBytes; i++) {
c = *ptr;
*ptr++ = (c * scale) >> 8;
}
brightness = newBrightness;
}
}
void ShowPixels(void){
short GIEval;
int i; //loop
GIEVal = GIE; //guardo valor de global interrupt enable
GIE = 0; //deshabilito interrupciones
i = numBytes; //numero de bytes a enviar
//apunto FSR0 al inicio de mis bytes
FSR0L = pixels;
FSR0H = pixels >> 8;
//10 instrucciones por cada bit: HHxxxxxLLL
//OUT instructions: ^ ^ ^ (T=0,2,7)
SendByte: //Clk Instr
//bit7 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 7)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit6 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 6)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit5 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 5)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit4 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 4)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit3 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 3)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit2 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 2)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit1 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 1)) //1 1
DAT = FALSE; //2 1
delay_cycles(4); //3-6 4
DAT = FALSE; //7 1
delay_cycles(2); //8-9 2
//bit0 ---
DAT = TRUE; //0 1
if(!bit_test(INDF0, 0)) //1 1
DAT = FALSE; //2 1
#asm
DECFSZ i, F //3 1 decrementar contador de bytes enviados, si es cero salta 1 -> listo.
GOTO Salto //4 1 salta 1 instruccion (no me deja usar GOTO $+2 ni BRA 2 ¿?)
GOTO Listo //5 1 todo enviado. Salir
#endasm
Salto:
FSR0L++; //6 1 incremento puntero
DAT = FALSE; //7 1 PIN = LOW
goto SendByte; //8 2 vuelve al principio
Listo:
DAT = FALSE;
delay_cycles(2);
// fin de transmision
delay_us(50); //espero 50uS para volver a enviar
GIE = GIEval; //restauro valor de GIE
}
Y un codigo de ejemplo es este (Ejemplo.c):
//------------------------------------------------------------------------------
// TITULO PROYECTO
//
// Descripcion:
//
// PIC: 12F1822
//
//------------------------------------------------------------------------------
#include <12F1822.h>
#device adc=8
#FUSES NOFCMEN //NO fail safe clock monitor
#FUSES NOIESO //NO internal external switchover
#FUSES NOBROWNOUT //NO brownout reset
#FUSES NOCPD //NO eeprom protegido de lecturas
#FUSES NOPROTECT //NO codigo protegido de lecturas
#FUSES NOMCLR //NO mclr
#FUSES PUT //power up timer
#FUSES NOWDT //NO watch dog timer
#FUSES INTRC_IO //oscilador interno, pines CLK como I/O
#FUSES CLKOUT
#use delay(clock=32000000)
#use fast_io(a) //se accede al puerto a como memoria
//--- Constantes ---
#byte PORTA = getenv("SFR:PORTA")
#define TRIS_A 0b00111011 //define cuales son entradas y cuales salidas
#bit DAT = PORTA.2
#define NUM_LEDS 10
//------------------
//--- Variables ---
//-----------------
//#use rs232(baud=9600, xmit=TX,rcv=RX)
#include "LED_Pixels.c"
void Rainbow(int wait);
int32 Wheel(int WheelPos);
// ---------------
void main(void) {
setup_wdt(WDT_OFF); //configuracion wdt
setup_timer_0(T0_INTERNAL|RTCC_DIV_1); //configuracion timer0
setup_timer_1(T1_INTERNAL|T1_DIV_BY_1); //configuracion timer1
setup_timer_2(T2_DISABLED,255,1); //configuracion timer2
setup_dac(DAC_OFF); //configura DAC
setup_adc(ADC_OFF); //configura ADC
setup_ccp1(CCP_OFF); //configura CCP
setup_spi(SPI_DISABLED);
//setup_uart(FALSE); //configura UART
setup_comparator(NC_NC); //comparador apagado
setup_vref(VREF_OFF); //no se usa voltaje de referencia
setup_oscillator(OSC_8MHZ|OSC_PLL_ON); //configura oscilador interno
set_tris_a(TRIS_A); //
ConfigPixels(NUM_LEDS);
int i = 0;
int32 ColorTest = 0;
do{
//Rainbow(50);
for(i = 0; i<NUM_LEDS; i++){
ColorTest = Wheel(i * (255 / NUM_LEDS));
SetPixelColor(i, ColorTest);
}
ShowPixels();
delay_ms(50);
}while(true);
}
void Rainbow(int wait){
long i, j;
for(j=0; j<256; j++){
for(i=0; i<numPixels(); i++){
SetPixelColor(i, Wheel((i+j) & 255));
}
ShowPixels();
delay_ms(wait);
}
}
int32 Wheel(int WheelPos) {
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
if(WheelPos < 85){
return Color32(WheelPos * 3, 255 - WheelPos * 3, 0);
}else if(WheelPos < 170){
WheelPos -= 85;
return Color32(255 - WheelPos * 3, 0, WheelPos * 3);
}else{
WheelPos -= 170;
return Color32(0, WheelPos * 3, 255 - WheelPos * 3);
}
}
Basicamente, lo que hay que hacer es cargar cada led con el color que queramos con la funcion SetPixelColor(NumLed, ColorFormato32bits).
Tambien podemos usar la funcion SetPixelColor(NumLed, Rojo, Verde, Azul).
Podemos cargar los colores con un bucle como se hace en el ejemplo.
Una vez cargados los colores hay que usar la funcion ShowPixels() para que se envien los datos. Mientras no se use esta funcion, solo estamos manipulando valores de memoria, pero no enviamos nada.
La libreria tiene mas funciones que copie de la libreria de Adafruit, pero que aun no he probado como SetBrightness(int Valor) que permite ajustar el brillo de todos nuestros leds, sin modificar el color.
El resultado es este:

Aun quedan bastantes cosas por mejorar:
-Si bien parece funcionar correctamente, el mayor problema es la cantidad de LEDs que podemos manejar. Necesitamos 3 bytes de ram por cada led, y el total de bytes necesarios para todos los leds deber estar alojados consecutivamente en nuestra ram para poder acceder a ellos.
Para ello usamos la funcion malloc() que busca un bloque consecutivo de memoria ram del tamaño que necesitamos. Pero la memoria debe estar muy fragmentada, porque no consigo hacer funcionar mas de 10 leds. Eso quiere decir que no hay mas de 30 bytes libres y consecutivos en mi ram.
-En caso de que nuestro PIC tenga suficiente memoria ram, no podremos manejar mas de 85 leds (255 bytes de ram) porque nuestro contador de envio de bytes es de tipo INT y solo podra enviar hasta 256 antes de desbordarse.
-Solo funciona con un oscilador de 32Mhz. Las instrucciones estan contadas solo para este velocidad.
Las mejoras que vaya consiguiendo las ire publicando. Si alguien quiere ayudar a completar la libreria se agradece.
Reitero mi agradecimiento a los amigos del subforo de assembler, que sin ellos no habria podido enviar los datos en los tiempos requeridos.
EDITO:
Actualizacion de libreria a v0.2: Mejor manejo de la memoria, posibilidad de enviar datos a 800 o 400Khz, comprobada y bien comentada.