TODOPIC
Microcontroladores PIC => Lenguaje C para microcontroladores PIC => Mensaje iniciado por: planeta9999 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:
/*
* 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;
}
-
Qué chulada de rutina. Me alegro que la hayas encontrado y posteado por aquí.
Estoy loco por meterle mano a esos leds.
-
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
-
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!
-
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:
(http://3.bp.blogspot.com/-59pmD0YU3i8/UNOiC9cZavI/AAAAAAAAP-k/bj-1PJ0vQJY/s1600/miembros-iglesia-para-de-sufrir.jpg)
Por el codigo, muy bueno, hasta con DMA, una joya de codigo para el baul :3
-
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. :-/ :-/ :-/ :-/
(http://www.chip.pl/blobimgs/2012/05/full/7e23ea2f362ec0efc29b94429f17f25b.jpeg)
-
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