"Ando" liado con unos cuantos proyectos que van a utilizar los leds digitales WS2812B, una maravilla, no los conocía hasta que leí los post de Mattyn, entonces cambie todos mis diseños que en principio utilizaban leds SMD convencionales controlados con TLC5940, así he podido reducir algunas placas de 4 capas a 2 capas, y ahorrar en componentes y en las tareas de ensamblaje de prototipos.
Lo complejo llega, para mi, con el software, eso de mezclar ensamblador con C, no me atrae en absoluto, no me gusta para nada el ensamblador, es un lenguaje endiablado, inhumano, imposible de portar, dependiente del procesador, reloj y ciclos por instrucción, la pesadilla de cualquier programador acostumbrado a trabajar con lenguajes de alto nivel.
Total, estuve googleando, buscando algo más potable, encontré algo con SPI, pero muy confuso, hasta que hoy se me ha abierto el cielo al encontrar la web de Henrik Thulin, con una rutina en C lista pasa usar en PIC32, la parte crítica para enviar los datos a los led la gestiona SPI y DMA, así que uno se puede olvidar de sufrir, y dedicarse al resto.
Por si alguien quiere trastear con los WS2812B y PIC32, en C, esta es la web, supongo que será extrapolable a otros PIC con SPI y DMA:
http://blog.gitmi.com/interfacing-ws2812b-ws2811-rgb-leds-with-a-pic32mx-250f128b-micro-controller-spi-700-khz-update-3/Pongo aquí también el código fuente, por si algún día desapareciera esa web, para que no se pierda un código tan valioso:
/*
* File: main.cpp
* Author: Henrik Thulin
*
* Created 16 may 2014, 20:26
*
* xc32-g++ optimizer should be set to 1 when using the bit-banging routine
* When using the SPI routine optimizer can be set to any value
*
*/
#pragma config POSCMOD=XT
#pragma config FSOSCEN=OFF
#pragma config FNOSC=PRIPLL
#pragma config OSCIOFNC=ON
#pragma config FPLLODIV=DIV_1
#pragma config FPLLMUL=MUL_20
#pragma config FPLLIDIV=DIV_1
#pragma config FWDTEN=OFF
#pragma config FPBDIV=DIV_1
#pragma config CP=OFF
#pragma config BWP=OFF
#pragma config PWP=OFF
#define SYS_FREQ (80000000L)
#define GetPeripheralClock() (FYC/(1 << OSCCONbits.PBDIV))
#define GetInstructionClock() (FYC)
// For 16x16 RGB leds
#define ROWS 16
#define COLS 16
// Settings used when bitbanging. B5 is here used as output pin
#define LED_PORT IOPORT_B
#define LED_PIN BIT_5
#define LED_HIGH mPORTBSetBits(LED_PIN);
#define LED_LOW mPORTBClearBits(LED_PIN);
#define SPI_HIGH 0b11100000000000000000000000000000 // This will be sent for highs
#define SPI_LOW 0b10000000000000000000000000000000 // This will be sent for lows
#define SPI_BITSPERBIT 4 // Equals the numbers of bits defined above
#define GAMMACORRECTION_ON // Enables gamma currection
#define SPIMODE_ON // Enables SPI mode. If removed, bit-banging will be used
#define SPIDMA_ON // Enables DMA mode for SPI
#include <plib.h>
#include <math.h>
#ifdef SPIMODE_ON
int spidataSize = ceil((double) (SPI_BITSPERBIT * COLS * ROWS * 24) / (double) 32); //(int)((COLS * ROWS) * (32 / SPI_BITSPERBIT));
unsigned int* spidata = (unsigned int*) malloc(spidataSize);
#endif
unsigned int bitmapBuffer[ROWS*COLS];
#ifdef GAMMACORRECTION_ON
unsigned char ledTweak[256];
void makeGamma(unsigned int& color) {
color = (ledTweak[(color >> 16) & 0xff] << 16) |
(ledTweak[(color >> 8) & 0xff] << 8) |
(ledTweak[(color) & 0xff]);
}
#endif
#ifdef SPIMODE_ON
// Initializes the SPI module. SPI here configured for port RB13. Effective bit-rate to the WS2812 will be ~500 kHz
// If you have problems try a 1 kOhm resistor in series
void InitSPI() {
OpenSPI1(SPI_MODE32_ON | SPI_SMP_ON | SPI_CKE_ON | MASTER_ENABLE_ON | SEC_PRESCAL_1_1 | PRI_PRESCAL_4_1, SPI_ENABLE);
RPB13R = 0b0011; // SET RB13 = SDO
}
#endif
#ifdef SPIDMA_ON
void InitDMA() {
DmaChnOpen(DMA_CHANNEL1, DMA_CHN_PRI3, DMA_OPEN_AUTO);
DmaChnSetEvFlags(DMA_CHANNEL1, DMA_EV_BLOCK_DONE);
DCH1ECONbits.CHSIRQ = _SPI1_TX_IRQ; // Channel Transfer Start IRQ bits
DCH1ECONbits.SIRQEN = 1; // Channel Start IRQ Enable bit, 1 =Start channel cell transfer if an interrupt matching CHSIRQ occurs
DCH1CONbits.CHAEN = 0; // Channel Automatic Enable bit, 0 = Channel is disabled on block transfer complete
DmaChnSetTxfer(DMA_CHANNEL1, (void*) &spidata[0], (void*) &SPI1BUF, spidataSize * sizeof (*spidata), 4, 1);
}
#endif
#ifdef SPIMODE_ON
// Sends bitmap buffer to the LEDS using SPI
void sendBitmapBuffer() {
char spipos = 0;
unsigned int *p, *buf;
unsigned int pixel;
buf = bitmapBuffer;
p = spidata;
*p = 0;
for (int pos = COLS * ROWS - 1; pos >= 0; pos--, buf++) {
pixel = *buf;
#ifdef GAMMACORRECTION_ON
makeGamma(pixel);
#endif
for (char bitpos = 23; bitpos >= 0; bitpos--) {
if ((pixel & (0x00000001 << bitpos)) > 0)
*p |= (SPI_HIGH >> spipos);
else
*p |= (SPI_LOW >> spipos);
spipos += SPI_BITSPERBIT;
if (spipos > 31) {
*(++p) = 0;
spipos = spipos - 32;
}
}
}
#ifdef SPIDMA_ON
DCH1CONbits.CHEN = 1;
#else
putsSPI1(spidataSize, spidata);
#endif
}
#else
// Old bitbanging routine
void bitbangBitmapPixel(unsigned int x) {
char i = 24;
do {
if ((x >> --i) & 1) {
LED_HIGH
Nop();
Nop();
Nop();
Nop();
Nop();
LED_LOW
} else {
LED_HIGH
Nop();
Nop();
LED_LOW
Nop();
Nop();
Nop();
Nop();
}
} while (i > 0);
}
void sendBitmapBuffer() {
for (int i = 0; i < COLS * ROWS; i++)
bitbangBitmapPixel(bitmapBuffer[i]);
}
#endif
// Returns bitmap buffer position for X and Y
short getPixelMappingPosition(short x, short y) {
// Use this if leds are connected left to right and downwards
return y * COLS + x;
}
// returns alpha, green, red, blue that'll work with the WS2811, but for simplicity is called ARGB
unsigned int getARGB(unsigned char alpha, unsigned char r, unsigned char g, unsigned char b) {
return (alpha << 24) | (g << 16) | (r << 8) | b;
}
// converts alpha, red, green and blue to alpha, green, red and blue
unsigned int getARGB(unsigned int i) {
return (i & 0xff000000) | ((i & 0x00ff0000) >> 8) | ((i & 0x0000ff00) << 8) | (i & 0x000000ff);
}
unsigned int* getPixel(char x, char y) {
return &bitmapBuffer[getPixelMappingPosition(x, y)];
}
// Blends the color with the bitmap buffer
unsigned int alphaBlend(char x, char y, unsigned int newColor) {
if (newColor >> 24 == 0xff) return newColor;
unsigned int* oldColor = getPixel(x, y);
unsigned int alpha = (newColor >> 24) & 0xff;
unsigned int red = (((newColor >> 8) & 0xff) * alpha / 255) + (((((*oldColor >> 8) & 0xff)) * (255 - alpha)) / 255);
unsigned int green = (((newColor >> 16) & 0xff) * alpha / 255) + (((((*oldColor >> 16) & 0xff)) * (255 - alpha)) / 255);
unsigned int blue = (((newColor) & 0xff) * alpha / 255) + (((((*oldColor) & 0xff)) * (255 - alpha)) / 255);
return (green << 16 | red << 8 | blue);
}
// Sets alpha value of color
void setAlpha(unsigned char alpha, unsigned int * color) {
*color = (*color & 0x00ffffff) | (alpha << 24);
}
// Returns alpha value of color
unsigned int getAlpha(unsigned char *alpha, unsigned int color) {
return (color & 0x00ffffff) | (*alpha << 24);
}
// Sends pixel to the bitmap buffer
void setPixel(short x, short y, unsigned int color) {
if (x < 0 || y < 0 || x >= COLS || y >= ROWS) return; // pixels outside the boundaries are ignored
bitmapBuffer[getPixelMappingPosition(x, y)] = alphaBlend(x, y, color);
}
// Fills the buffer with specified color
void setBackground(unsigned int color) {
for (unsigned int i = 0; i < COLS * ROWS; i++)
bitmapBuffer[i] = color;
}
// Creates a solid rectangle
void fillRectangle(char x1, char y1, char x2, char y2, unsigned int color) {
for (int x = x1; x <= x2; x++)
for (int y = y1; y <= y2; y++)
setPixel(x, y, color);
}
// Draws a line
void drawLine(char x1, char y1, char x2, char y2, unsigned int color) {
short xSteps = x2 > x1 ? (x2 - x1) : (x1 - x2);
short ySteps = y2 > y1 ? (y2 - y1) : (y1 - y2);
float xPosInc = (float) (x2 - x1) / (xSteps > ySteps ? xSteps : ySteps);
float yPosInc = (float) (y2 - y1) / (xSteps > ySteps ? xSteps : ySteps);
float xpos = x1;
float ypos = y1;
for (int i = 0; i < (xSteps > ySteps ? xSteps : ySteps); i++) {
setPixel(round(xpos), round(ypos), color);
xpos += xPosInc;
ypos += yPosInc;
}
}
// Draws the outlines of a rectangle
void drawRectangle(char x1, char y1, char x2, char y2, unsigned int color) {
drawLine(x1, y1, x2, y1, color); // top
drawLine(x1, y2, x2, y2, color); // bottom
drawLine(x1, y1, x1, y2, color); // left
drawLine(x2, y1, x2, y2, color); // right
}
void drawSquareRotate(float centerX, float centerY, float size, float rotate, unsigned int color) {
float rot = -0.7855 + (((float) rotate / 360) * 6.284);
char x1 = round(centerX + size * cos(rot));
char y1 = round(centerY + size * sin(rot));
char x2 = round(centerX + size * cos(rot + (float) 1.571));
char y2 = round(centerY + size * sin(rot + (float) 1.571));
char x3 = round(centerX + size * cos(rot + (float) 3.142));
char y3 = round(centerY + size * sin(rot + (float) 3.142));
char x4 = round(centerX + size * cos(rot + (float) 4.713));
char y4 = round(centerY + size * sin(rot + (float) 4.713));
drawLine(x1, y1, x2, y2, color);
drawLine(x2, y2, x3, y3, color);
drawLine(x3, y3, x4, y4, color);
drawLine(x4, y4, x1, y1, color);
}
// Not optimized but does the job
void drawCircle(char x, char y, char radius, unsigned int color) {
for (float angle = 0; angle < 3.142 * 2; angle += 0.1)
setPixel(x + round(radius * cos(angle)), y + round(radius * sin(angle)), color);
}
int main() {
SYSTEMConfigPerformance(80000000L);
SYSTEMConfig(SYS_FREQ, SYS_CFG_WAIT_STATES | SYS_CFG_PCACHE);
mJTAGPortEnable(DEBUG_JTAGPORT_OFF);
#ifdef SPIMODE_ON
InitSPI();
#else
PORTSetPinsDigitalOut(LED_PORT, LED_PIN);
#endif
#ifdef SPIDMA_ON
InitDMA();
#endif
OpenTimer1(T1_ON | T1_PS_1_256, 0xffffffff);
#ifdef GAMMACORRECTION_ON
// Calculates gamma values
for (float f = 0; f < 256; f++) {
ledTweak[(unsigned char) f] = (unsigned char) round(
(f * (f / 256) + 1)
);
}
ledTweak[0] = 0;
#endif
setBackground(0xff000000);
while (1) {
for (short i = 0; i < 90; i += 5) {
drawSquareRotate(7.5, 7.5, 8, i, getARGB(100, 0, 0, 255));
sendBitmapBuffer();
fillRectangle(0, 0, 15, 15, getARGB(0xff, 0, 0, 0));
while (ReadTimer1() < 0xf) Nop();
WriteTimer1(0);
}
}
return 0;
}