Autor Tema: LEds WS2812B con PIC32 en C  (Leído 3097 veces)

0 Usuarios y 1 Visitante están viendo este tema.

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 3520
    • Pinballsp
LEds WS2812B con PIC32 en C
« en: 06 de Agosto de 2014, 23:01:29 »


"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:

Código: [Seleccionar]
/*
 * 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;
}







Desconectado Nocturno

  • Administrador
  • DsPIC33
  • *******
  • Mensajes: 18269
    • MicroPIC
Re: LEds WS2812B con PIC32 en C
« Respuesta #1 en: 07 de Agosto de 2014, 01:10:30 »
Qué chulada de rutina. Me alegro que la hayas encontrado y posteado por aquí.

Estoy loco por meterle mano a esos leds.

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 3520
    • Pinballsp
Re: LEds WS2812B con PIC32 en C
« Respuesta #2 en: 07 de Agosto de 2014, 09:39:01 »



Si, la rutina es fantástica, a mi me va a ahorrar un montón de faena y de dolores de cabeza, porque el ensamblador no lo uso desde el año 2000,  y creo que fueron 4 tonterías con el 16F84.

Lo único que me ha matado, es este comentario que hace Thulin:
"For this to work properly I had to add a 1k Ohm resistor between the WS2812 and the PIC output pin".

La madre del cordero, ahora resulta que hay que poner una resistencia de 1K entre el puerto del PIC y la entrada al primer WS2812B, ya tengo hechas algunas placas con este led, y el puerto me va directo del PIC al led, sin nada de por medio. Como sea estrictamente necesario, a tirar las placas y a hacerlas de nuevo, además de los stencil, vamos un pastizal.

En cuanto al LED WS2812B, lo más barato que tengo localizado es este chino en Aliexpress, un carrete de 1000 unidades, cada led sale a 0.12 dólares, 120 dólares el carrete, más 35 dólares de gastos de envío por DHL, me voy a pedir uno:  
http://www.aliexpress.com/item/New-Section-2014-Wholesle-1K-1000-PCS-WS2812B-LED-Chip-Dream-Color-Large-Stock-For-Strip/1752138444.html
« Última modificación: 07 de Agosto de 2014, 09:47:49 por planeta9999 »

Desconectado AKENAFAB

  • Colaborador
  • DsPIC30
  • *****
  • Mensajes: 3227
Re: LEds WS2812B con PIC32 en C
« Respuesta #3 en: 22 de Noviembre de 2014, 01:21:51 »
Hola!

También lo mas barato que hemos encontrado el LED es a 0.12 usd, esto directo con world semi.
Supongo serán los mismos vendiendo ahí.
saludos!

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re: LEds WS2812B con PIC32 en C
« Respuesta #4 en: 22 de Noviembre de 2014, 01:42:18 »
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.

Ami me encanta el ASM :) lo que si hay que entender completamente el micro sino no sirve para nada. Al menos entre los PIC32 va a servir si mantienen su nucleo MIPS32 portarlo de ahi naaa xD, pero igual ya es complejo aprender a programar en ASM en un dsPIC33f ( no por las instrucciones de mas ) por el pensamiento que tenes que tener sobre la segmentacion (pipeline ) saltos, demas verduras, a eso le agregas un cache y mas cosas en el PIC32 xD. Y C con ASM no sirve, estuve leyendo por que queria usar para la estructura y luego meterle ASM nomas dentro, pero tiene muchas limitantes por que el compilador usa los registros a su antojo y luego vos con el ASM se lo arruinas y puede que ande mal todo, al menos por lo que lei no lo aconsejan para nada desde microchip

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.

Cuando lei eso se me vino a la cabeza:



Por el codigo, muy bueno, hasta con DMA, una joya de codigo para el baul :3
« Última modificación: 22 de Noviembre de 2014, 01:44:55 por KILLERJC »

Desconectado planeta9999

  • Moderadores
  • DsPIC30
  • *****
  • Mensajes: 3520
    • Pinballsp
Re: LEds WS2812B con PIC32 en C
« Respuesta #5 en: 22 de Noviembre de 2014, 08:39:07 »
Ami me encanta el ASM :) lo que si hay que entender completamente el micro sino no sirve para nada. Al menos entre los PIC32 va a servir si mantienen su nucleo MIPS32 portarlo de ahi naaa xD, pero igual ya es complejo aprender a programar en ASM en un dsPIC33f ( no por las instrucciones de mas ) por el pensamiento que tenes que tener sobre la segmentacion (pipeline ) saltos, demas verduras, a eso le agregas un cache y mas cosas en el PIC32 xD. Y C con ASM no sirve, estuve leyendo por que queria usar para la estructura y luego meterle ASM nomas dentro, pero tiene muchas limitantes por que el compilador usa los registros a su antojo y luego vos con el ASM se lo arruinas y puede que ande mal todo, al menos por lo que lei no lo aconsejan para nada desde microchip


Solo utilicé el ensamblador cuando no había más remedio, con los primeros micros que disponían de 256 bytes de programa (los ST6 que publicitó durante mucho tiempo la revista Nueva Electrónica) o como mucho 1K con los PIC16F84, allá por la prehistoria en el año 2000-2001.

Hace mucho tiempo que solo uso PIC32, con 512K de flash, y ahora con los MZ, ya tenemos 1 y 2Mb de flash, de sobra para trabajar agusto, en C por supuesto. TAmbién contemplo usar los ARM STM32 con 1 y 2Mb de flash, programados en C con Gcc y Eclipse.

Lo olvidaba, también usé el ensamblador para programar mi primer ordenador personal, un Sinclair ZX Spectrum, me diseñé una caja de ritmos con un AY-3-8910 y le hice un programita en ensamblador, pero programado a lo bruto, ensamblador escrito en un papel y traducido a binario a mano con la tabla que había en un libro que te relacionaba el código assembler con su equivalente en hexadecimal.
¡¡¡ Viva el Zx Spectrum ¡¡¡¡, aún lo conservo, aunque hace años le saqué el 7805 para un apaño que necesitaba.  :-/ :-/ :-/ :-/







« Última modificación: 22 de Noviembre de 2014, 08:42:37 por planeta9999 »

Desconectado KILLERJC

  • Colaborador
  • DsPIC33
  • *****
  • Mensajes: 8242
Re: LEds WS2812B con PIC32 en C
« Respuesta #6 en: 22 de Noviembre de 2014, 15:55:25 »
Sos un genio, como dije ASM me encanta, el mayor obstaculo a no meterte con ASM es generalmente la perdida de tiempo que tenes xD. Como vos decis hay flash de sobra, y hasta tal ves (lo mas seguro) sea mas barato comprar un micro mas potente y mas flash que dedicar horas y horas de desarrollo en una produccion