LED screen driver build for hackspace.

Dependencies:   mbed

ledScreen.h

Committer:
madcowswe
Date:
2012-02-29
Revision:
1:1af5060b2a34
Parent:
0:f16a1d69a386

File content as of revision 1:1af5060b2a34:

#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:
    transformFrame crammed into the code, should be united with transformFrame2 for proper orientation
    
    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);
}