LED screen driver build for hackspace.

Dependencies:   mbed

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);
+}