Oskar Weigl
/
LED_for_hackspace
LED screen driver build for hackspace.
Diff: ledScreen.h
- Revision:
- 0:f16a1d69a386
- Child:
- 1:1af5060b2a34
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ledScreen.h Wed Feb 29 17:01:43 2012 +0000 @@ -0,0 +1,260 @@ +#include "mbed.h" + +/* + ****************************************************** + *Hacked together by Imperial College Robotics Society* + ****************************************************** + + Usage: + Example code supplied (main.cpp). Format of data input: 8bit RGB channels per pixel sequential in one framebuffer. + + Note: + This version uses inverted outputs (to bring voltage from 3.3 to 5v and drive more current) for data, clock and address lines. + + Quirks of current platform: + address lines are put through inverters, code still thinks it's addressing with logic 1's, + whereas in reality MA0 is inverted and put into the ribbon cable line of MA1 and vice-versa + +*/ + +extern "C" void frameout(unsigned char dsVal[], unsigned char transformedSource[]); + +class ledScreen { +public: + ledScreen(); + ~ledScreen() {} + + void transformFrame(unsigned char* imageSource); + void transformFrame2(unsigned char* imageSource); + void outputFrame(); + void start(); // start outputting frames on an interrupt + +private: + + int MAX_PULSE_WIDTH; // constant: max enable pulse duration + int pulseLength; // length of current pulse (used in delta-sigma pwm) + int OP_TIME; + + static const int NUM_PANELS = 3; // number of panels horizontally + + int running; + int subFrameCtr; + + Timeout nextFrameTimer; // timeout routine + + // Buffers to hold the RGB data after rearranging to match the LED shifting pattern + unsigned char transformedSource[256*3*NUM_PANELS]; + + // Error values for all 256 brightness levels + unsigned int dsErr[256]; + unsigned int ssdsErr[256]; + + // On/off state per sub-frame for all 256 brightness levels + unsigned char dsVal[256]; + + // Precomputed gamma for all 256 brightness levels + unsigned short gamma[256]; + + + DigitalOut flatch; // data latch (for all connected panels in parallel) + DigitalOut MA0; // module address 0 + DigitalOut MA1; + DigitalOut NREN; // active low enable for red channel (low -> LED on). Note: need to have enable high when latching data + DigitalOut Rdat; // red data + DigitalOut Gdat; // green data + DigitalOut Bdat; // blue data + DigitalOut sclk; // clock + +}; + +ledScreen::ledScreen() : + flatch(p11), // data latch (for all connected panels in parallel) + MA0(p18), // module address 0 + MA1(p19), + NREN(p12), // active low enable for red channel (low -> LED on). Note: need to have enable high when latching data + Rdat(p15), // red data + Gdat(p16), // green data + Bdat(p17), // blue data + sclk(p14) { // clock + + // precompute gamma for every possible RGB intensity value (0-255). + // Gamma correction with gamma = 3, downshifting by 8 to bring the range of values back to 0-65535 + for (int i=0; i<256; i++) { + gamma[i] = pow(i, 2.2) * 0.33;//(i*i*i)>>8; + } + + // initialising lines + flatch = 0; + NREN = 0; + sclk = 0; + + // initialising values + MAX_PULSE_WIDTH = 512; //must currently be a power of 2, and when changing this, you must change the ssdsErr crossover masking + pulseLength = MAX_PULSE_WIDTH; + OP_TIME = 345; //Determined by scoping. Change this every time you change num screens + //NUM_PANELS = 3 + + running=0; + subFrameCtr=0; + + // initialising errors for delta-sigma + for (int j=0; j<256; j++) { + dsErr[j] = 0; + ssdsErr[j] = 0; + } + +} + +void ledScreen::start() { + running=1; + outputFrame(); +} + + +void ledScreen::transformFrame2(unsigned char* imageSource) { + + int psp = 0; // panel space pointer + int tpsp = 0; // target psp + int isp = 8 * 3; // imag espace pointer + // preprocessing the image data to match shifting pattern + for (int panel = 0; panel < NUM_PANELS; panel++) { + // int base = panel * 0x20; //i - image space*/ + + for (int drows = 0; drows < 8; drows++) { + //for (int m = 0; m < 0x1F; m++) { //m - quad block in panel space + + for (tpsp = psp + (8*3); psp < tpsp;) { //0th quad + isp -= 3; + transformedSource[psp++] = imageSource[isp]; + transformedSource[psp++] = imageSource[isp+1]; + transformedSource[psp++] = imageSource[isp+2]; + } + + //next row + isp += 0x10 * NUM_PANELS * 3; + for (tpsp = psp + (8*3); psp < tpsp; isp+=3) { //2nd quad + transformedSource[psp++] = imageSource[isp]; + transformedSource[psp++] = imageSource[isp+1]; + transformedSource[psp++] = imageSource[isp+2]; + } + + //previous row + isp -= (0x10 * NUM_PANELS - 8) * 3; + for (tpsp = psp + (8*3); psp < tpsp;) { //1st quad + isp-=3; + transformedSource[psp++] = imageSource[isp]; + transformedSource[psp++] = imageSource[isp+1]; + transformedSource[psp++] = imageSource[isp+2]; + } + isp += 0x10 * NUM_PANELS * 3; + for (tpsp = psp + (8*3); psp < tpsp; isp+=3) { //3rd quad + transformedSource[psp++] = imageSource[isp]; + transformedSource[psp++] = imageSource[isp+1]; + transformedSource[psp++] = imageSource[isp+2]; + + } + isp += (0x10 * NUM_PANELS - 8) * 3; + } + + isp += (0x10 - 0x20 * NUM_PANELS * 8) * 3; + } +} + +void ledScreen::transformFrame(unsigned char* imageSource) { + unsigned char rotatedSource[256*3*NUM_PANELS]; + + for (int panels = 0; panels < 3; panels++){ + for (int x = 0; x<16; x++){ + for (int y = 0; y<16; y++){ + for (int c = 0; c < 3; c++){ + rotatedSource[((15-x) + panels*16 + 48*y)*3 + c] = imageSource[(y + panels*16 + 48*x)*3 + c]; + } + } + } + } + + transformFrame2(rotatedSource); +} + +// Output one frame and call itself after a period of time if running is set to true +void ledScreen::outputFrame() { + + if (pulseLength != MAX_PULSE_WIDTH) + NREN = 1; // turn off + + if (subFrameCtr<=0) subFrameCtr=36; + subFrameCtr--; + + if (subFrameCtr == 0) { // Every cycle of delta sigma we take a snapshot of the error that needs to be corrected by the short pulses. + for (int i = 0; i < 256; i++) { // This is required to eliminate visible flicker due to beat frequencies otherwise created. + dsErr[i] += ssdsErr[i] & 0xFE000000; + ssdsErr[i] %= 0x10000; + ssdsErr[i] += dsErr[i] % (512 * 0x10000); + dsErr[i] &= 0xFE000000; + } + + // Doing delta sigma for the snapshot + for (int i = 0; i <= 9; i++) { + int lpl = 1<<i; + + if (ssdsErr[i]/0x10000 & lpl) + ssdsErr[i]-=(0x10000-gamma[i])*lpl; + else + ssdsErr[i]+=gamma[i]*lpl; + } + + } + + // produce pulse lengths of 1, 2, 4, ... 256, spread throughout all subframes (only one in four are not MAX_PULSE_WIDTH long) + pulseLength = ((subFrameCtr%4)?MAX_PULSE_WIDTH:(1<<(subFrameCtr>>2))); + + for (int i = 0; i < 256; i++) { + if (pulseLength == MAX_PULSE_WIDTH) { + // Delta-Sigma modulation with variable pulse length weighting + // Based on energy dimensions (time * amplitude) + if (dsErr[i] > (0x10000-gamma[i])*pulseLength) { + dsVal[i] = 0;//-1; Invert as we are using inverting buffers + dsErr[i]-=(0x10000-gamma[i])*pulseLength; + } else { + dsVal[i] = (unsigned char)-1; + dsErr[i]+=gamma[i]*pulseLength; + } + } else { // if short pulse + if (ssdsErr[i]/0x10000 & pulseLength) { + //Doing proper least significant delta sigma live still causes flicker (but only for dim pixels) + //ssdsErr[i]-=(0x10000-gamma[i])*pulseLength; + dsVal[i] = 0; + } else { + dsVal[i] = (unsigned char)-1; + //ssdsErr[i]+=gamma[i]*pulseLength; + } + + } + } + + // output data + for (int i = 0; i < NUM_PANELS; i++) { // NUM_PANELS + MA0 = i&1; + MA1 = i&2; + + frameout(dsVal, &transformedSource[i*256*3]); + } + + NREN = 1; // need to have enables high before every latch, (in case we are on a long pulse) + flatch = 1; // latching all data to LEDs + flatch = 0; + NREN = 0; // turn on LEDs + + if (pulseLength < 4) { // short pulses done through wait + wait_us(pulseLength); + NREN = 1; //Turn off LEDs + + bool wasrunning = running; + running = false; + outputFrame(); //this will recurse only once due to the distrubution of pulses. pulseLength of the next instance will be attached. + running = wasrunning; + } + // long waits done through attaching an interrupt that will turn off the LEDs at the start of next function call. + // Meanwhile, the main code can run between the interrupts. + if (running) nextFrameTimer.attach_us(this, &ledScreen::outputFrame, (pulseLength == MAX_PULSE_WIDTH) ? pulseLength - OP_TIME : pulseLength); +}