Published

Dependents:   DuckLights

Fork of TLC5940 by Spencer Davis

TLC5955.cpp

Committer:
roysandberg
Date:
2018-06-09
Revision:
4:ab6b451bbf40

File content as of revision 4:ab6b451bbf40:

#include "TLC5955.h"
#include "math.h"

extern void init_clock(PinName clkPin);

DigitalOut led2(LED2);

// gamma correction array compensates for non-linearities
// https://learn.adafruit.com/led-tricks-gamma-correction/the-longer-fix
uint16_t redGamma[256];
uint16_t greenGamma[256];
uint16_t blueGamma[256];

#define GAMMA_FACTOR 2.8
#define GAMMA_MAX_IN 255
#define GAMMA_RED_MAX_OUT 0xFFFF
#define GAMMA_BLUE_MAX_OUT 0x4000
#define GAMMA_GREEN_MAX_OUT 0x8000

void buildGammaTable(float gammaFactor, uint16_t maxInput, uint16_t maxOutput, uint16_t* gamma) {
    for(int i=0; i<=maxInput; i++) {
        gamma[i] = (uint16_t) (pow((float)i / (float)maxInput, gammaFactor) * maxOutput + 0.5);
        //printf("Max=%x, i=%d, val=%x\n\r", maxOutput, i, gamma[i]);
    }
}
  
void rebuildGammaTables(uint8_t amplitude) {
    buildGammaTable (GAMMA_FACTOR, GAMMA_MAX_IN, (uint16_t)((((long) GAMMA_RED_MAX_OUT)*amplitude)/0xFFl), redGamma);
    buildGammaTable (GAMMA_FACTOR, GAMMA_MAX_IN, (uint16_t)((((long) GAMMA_GREEN_MAX_OUT)*amplitude)/0xFFl), greenGamma);
    buildGammaTable (GAMMA_FACTOR, GAMMA_MAX_IN, (uint16_t)((((long) GAMMA_BLUE_MAX_OUT)*amplitude)/0xFFl), blueGamma);
}  

TLC5955::TLC5955(PinName SCLK, PinName MOSI, PinName GSCLK,
                 PinName XLAT, const int number) : number(number),
                                                                                spi(MOSI, NC, SCLK),
                                                                                gsclk(GSCLK),                                                                            
                                                                                xlat(XLAT),                                                                                
                                                                                newGSData(false),
                                                                                newControlData(false),
                                                                                need_xlat(false)
{   
    
    rebuildGammaTables(0xFF); // oxFF is full amplitude
    
    for (int i=0; i<(SHORTS_PER_CHANNEL * CHANNELS_PER_IC * NUMBER_OF_ICS); i++) {
        internalData[i] = 0xFFFF; // invert pwm outputs because transistor will get pulled low when on
    }
    for (int i=0; i<(SHORTS_PER_CHANNEL * CHANNELS_PER_IC * NUMBER_OF_ICS)+1; i++) {
        gsBuffer[i] = 0xFFFF;
    }
    
    
    // Configure SPI to 16 bits and SPI_SPEED
    spi.format(8, 0);
    spi.frequency(SPI_SPEED);
    
    
    
    
    // Set lat pin state
    xlat = 0;
    
    // 62.5Hz should be fast enough
    reset_ticker.attach_us(this, &TLC5955::reset, 16000);

    // Outputs 8Mhz on pin 
    init_clock(GSCLK);    
}
// https://github.com/FastLED/FastLED/wiki/FastLED-Color-Correction
// Red LEDs are the dimmest, so no scaling
// Blue LEDs are a bit brighter, so scale down by 0.9 (0xE6)
// Green LED are by far the brightest, so scale down by 0.6 (0x99)
void TLC5955::setChannel(int channelNum, unsigned short red, unsigned short green, unsigned short blue) {
    if (red > 0xFF) red = 0xFF;
    if (green > 0xFF) green = 0xFF;
    if (blue > 0xFF) blue = 0xFF;
    
    if (channelNum < CHANNELS_PER_IC * NUMBER_OF_ICS) {        
        // chip color values leave in b, r, g order, but that should map to r, g, b order

        if (channelNum > 31) { 
            channelNum -= 32;
        } else if (channelNum < 16) {
            channelNum += 32;        
        }
        
        // mappings are slightly different for first eight and last eight of each board (red and green get inverted)
        if (channelNum % 16 < 8) {
            internalData[channelNum*SHORTS_PER_CHANNEL + 1] = (0xFFFF - GAMMA_BLUE_MAX_OUT) + (GAMMA_BLUE_MAX_OUT - blueGamma[blue]);  
            internalData[channelNum*SHORTS_PER_CHANNEL + 0] = (0xFFFF - GAMMA_RED_MAX_OUT) + (GAMMA_RED_MAX_OUT - redGamma[red]); 
            internalData[channelNum*SHORTS_PER_CHANNEL + 2] = (0xFFFF-GAMMA_GREEN_MAX_OUT) + (GAMMA_GREEN_MAX_OUT - greenGamma[green]); 
        } else {
            internalData[channelNum*SHORTS_PER_CHANNEL + 2] = (0xFFFF - GAMMA_BLUE_MAX_OUT) + (GAMMA_BLUE_MAX_OUT - blueGamma[blue]);  
            internalData[channelNum*SHORTS_PER_CHANNEL + 1] = (0xFFFF - GAMMA_GREEN_MAX_OUT) +(GAMMA_GREEN_MAX_OUT - greenGamma[green]); 
            internalData[channelNum*SHORTS_PER_CHANNEL + 0] = (0xFFFF - GAMMA_RED_MAX_OUT) + (GAMMA_RED_MAX_OUT - redGamma[red]); 
        }
    }
}

void TLC5955::latchData() {
    newGSData = true;
}

void TLC5955::setNewControlData(unsigned short _globalBrightnessRed, unsigned short _globalBrightnessGreen, unsigned short _globalBrightnessBlue,
                                led_power_t _maximumCurrentRed, led_power_t _maximumCurrentGreen, led_power_t _maximumCurrentBlue, 
                                unsigned short* _dotCorrect) {
    globalBrightnessRed = _globalBrightnessRed;
    globalBrightnessGreen = _globalBrightnessGreen;
    globalBrightnessBlue = _globalBrightnessBlue;
    maximumCurrentRed = _maximumCurrentRed;
    maximumCurrentGreen = _maximumCurrentGreen; 
    maximumCurrentBlue = _maximumCurrentBlue;
    dotCorrect = _dotCorrect;

    // Tell reset function that new DC data has been given
    newControlData = true;
}

// clock out the data over spi such that the MSB is the control bit (so 769 bits total)
void TLC5955::clockOutData() {
    // clock out buffer, where the first word contains 
    // unused leading bits that will get clocked past the registers. 
    
    for (int i=0; i< (SHORTS_PER_CHANNEL * CHANNELS_PER_IC * NUMBER_OF_ICS) + 1; i++) {
        spi.write(gsBuffer[i]>>8);
        spi.write(gsBuffer[i]&0xFF);
        //printf ("%d:%x:%x\n\r",i, gsBuffer[i]>>8, gsBuffer[i]&0xFF);
    }
}

// Need a way to assemble bits in random offsets, since the 769 bit sequence doesn't divide evenly. 
// So depending on how many TLC5955s are daisy chained together, there are an arbitrary number of extra bits.
// Function that tracks next available bit location, and packs everything in is needed.


inline void TLC5955::clearBit (unsigned short* value, int bitOffset) {
    *value = *value & ~(1<<bitOffset);
}

inline void TLC5955::setBit (unsigned short* value, int bitOffset) {
    *value = *value | (1<<bitOffset);
}


inline void TLC5955::packBit(unsigned int aBit) {
    // current bit location divided by 16 to get the short
    int shortOffset = currentBitLocation / 16;
    
    // current bit location mod 8 to get the bit to mask
    int bitOffset = currentBitLocation % 16;
    
    if (aBit == 0) {
        // clear
        clearBit (&(gsBuffer[shortOffset]), 15 - bitOffset);
    } else {
        // set
        setBit (&(gsBuffer[shortOffset]), 15 - bitOffset);
    }
    currentBitLocation++;    
} 

void TLC5955::packByte (unsigned int aByte) {
    // call packBit for 8 bits, starting at the current bit location
    for (int i = 7; i >= 0;i--) {  // MSB gets packed first
        packBit( aByte & (1<<i));
    }
        
}

void TLC5955::packShort (unsigned int aShort) {
    packByte( aShort >> 8); // MSB first
    packByte( aShort & 0xFF);
}

void TLC5955::reset()
{   
    // Do we have new control data to send?
    if (newControlData)
    {
       // printf ("Control:\n\r");

        currentBitLocation = 0;
        for (int i=0; i < 16 - NUMBER_OF_ICS;i++) {
            packBit(0); // stuff leading byte
        }
        for (int icNum=0; icNum < NUMBER_OF_ICS; icNum++) {
            // TODO: For daisy-chaining, this will need to consider the number of boards when calculating how many clocked out bits to offset
            //packShort(1); // set LSB to one, will clock into LSB spot when spi serializes it to trigger control data load when latched
            packBit(1); // set LSB to one
            packByte(0x96); // 8 bit command decoder must be set to 0x96
            
            // 390 bits skipped, so could be anything
            for (int i=0;i<389;i++) {
                packBit(0);
            }
            
            // FC 5 bits
            packBit (0); // LED short circuit detection voltage - don't care for this application
            packBit (0); // Use conventional PWM for this application; it's compatible with daisy chaining
            packBit (0); // auto data refresh disabled
            packBit (0); // display timing reset disabled
            packBit (1); // auto display repeat mode ENABLED
            
            // BC, 21 bits
            packBit (globalBrightnessRed & (1<<6));
            packBit (globalBrightnessRed & (1<<5));
            packBit (globalBrightnessRed & (1<<4));
            packBit (globalBrightnessRed & (1<<3));
            packBit (globalBrightnessRed & (1<<2));
            packBit (globalBrightnessRed & (1<<1));
            packBit (globalBrightnessRed & (1<<0));
    
            packBit (globalBrightnessGreen & (1<<6));
            packBit (globalBrightnessGreen & (1<<5));
            packBit (globalBrightnessGreen & (1<<4));
            packBit (globalBrightnessGreen & (1<<3));
            packBit (globalBrightnessGreen & (1<<2));
            packBit (globalBrightnessGreen & (1<<1));
            packBit (globalBrightnessGreen & (1<<0));
    
            packBit (globalBrightnessBlue & (1<<6));
            packBit (globalBrightnessBlue & (1<<5));
            packBit (globalBrightnessBlue & (1<<4));
            packBit (globalBrightnessBlue & (1<<3));
            packBit (globalBrightnessBlue & (1<<2));
            packBit (globalBrightnessBlue & (1<<1));
            packBit (globalBrightnessBlue & (1<<0));
            
            // MC, 9 bits
            packBit (maximumCurrentBlue & (1<<2));
            packBit (maximumCurrentBlue & (1<<1));
            packBit (maximumCurrentBlue & (1<<0));
    
            packBit (maximumCurrentGreen & (1<<2));
            packBit (maximumCurrentGreen & (1<<1));
            packBit (maximumCurrentGreen & (1<<0));
    
            packBit (maximumCurrentRed & (1<<2));
            packBit (maximumCurrentRed & (1<<1));
            packBit (maximumCurrentRed & (1<<0));
    
            // DC 336 bits
            // dot correct for each channel, starting with channel 48
            for (int i=0; i<48; i++) {
                packBit (dotCorrect[i] & (1<<6));
                packBit (dotCorrect[i] & (1<<5));
                packBit (dotCorrect[i] & (1<<4));
                packBit (dotCorrect[i] & (1<<3));
                packBit (dotCorrect[i] & (1<<2));
                packBit (dotCorrect[i] & (1<<1));
                packBit (dotCorrect[i] & (1<<0));        
            }
        }
        clockOutData();
        
        // Latch
        xlat = 0;
        xlat = 1;
        wait_us(10);
        xlat = 0;
        
        
        // No new data to send (we just sent it!)
        newControlData = false;
        
    } else if (newGSData) {  // Do we have new GS data to send?
        //printf ("Data:\n\r");
 
        led2 = !led2;
        
        currentBitLocation = 0;
        for (int i=0; i < 16 - NUMBER_OF_ICS;i++) {
            packBit(1); // stuff leading byte
        }
        for (int icNum=0; icNum < NUMBER_OF_ICS; icNum++) {
            // TODO: For daisy-chaining, this will need to consider the number of boards when calculating how many clocked out bits to offset
            //packShort(0); // set LSB to zero, will clock into LSB spot when spi serializes it to trigger control data load when latched
            packBit(0); // set LSB to zero
            
            // Send GS data backwards - this makes the GS_buffer[0] index correspond to OUT0 
            for (int i = (SHORTS_PER_CHANNEL * CHANNELS_PER_IC) - 1; i >= 0; i--)
            {
                packShort(internalData[i + (icNum*SHORTS_PER_CHANNEL * CHANNELS_PER_IC)]);
            }
        }        
        clockOutData();
        
        // Latch
        xlat = 0;
        xlat = 1;
        wait_us(10);
        xlat = 0;
        
        // No new data to send (we just sent it!)
        newGSData = false;
    }
}