Published

Dependents:   DuckLights

Fork of TLC5940 by Spencer Davis

Files at this revision

API Documentation at this revision

Comitter:
roysandberg
Date:
Sat Jun 09 23:22:13 2018 +0000
Parent:
3:e5ed5650eb15
Commit message:
Published

Changed in this revision

FastPWM.lib Show diff for this revision Revisions of this file
TLC5940.cpp Show diff for this revision Revisions of this file
TLC5940.h Show diff for this revision Revisions of this file
TLC5940Mux.cpp Show diff for this revision Revisions of this file
TLC5955.cpp Show annotated file Show diff for this revision Revisions of this file
TLC5955.h Show annotated file Show diff for this revision Revisions of this file
diff -r e5ed5650eb15 -r ab6b451bbf40 FastPWM.lib
--- a/FastPWM.lib	Sun May 26 05:04:29 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-https://mbed.org/users/Sissors/code/FastPWM/#3094d3806cfc
diff -r e5ed5650eb15 -r ab6b451bbf40 TLC5940.cpp
--- a/TLC5940.cpp	Sun May 26 05:04:29 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-#include "TLC5940.h"
-
-TLC5940::TLC5940(PinName SCLK, PinName MOSI, PinName GSCLK, PinName BLANK, 
-                 PinName XLAT, PinName DCPRG, PinName VPRG, const int number) : number(number),
-                                                                                spi(MOSI, NC, SCLK),
-                                                                                gsclk(GSCLK),
-                                                                                blank(BLANK),
-                                                                                xlat(XLAT),
-                                                                                dcprg(DCPRG),
-                                                                                vprg(VPRG),
-                                                                                newGSData(false),
-                                                                                newDCData(false),
-                                                                                need_xlat(false)
-{   
-    // Configure SPI to 12 bits and SPI_SPEED
-    spi.format(12, 0);
-    spi.frequency(SPI_SPEED);
-    
-    // Set output pin states
-    dcprg = 0;
-    vprg = 0;
-    xlat = 0;
-    blank = 1;
-    
-    // Call the reset function every 4096 PWM outputs
-    reset_ticker.attach_us(this, &TLC5940::reset, (1000000.0/GSCLK_SPEED) * 4096.0);
-    
-    // Configure FastPWM output for GSCLK frequency at 50% duty cycle
-    gsclk.period_us(1000000.0/(GSCLK_SPEED * 1.05));
-    gsclk.write(.5);
-}
-
-void TLC5940::setNewGSData(unsigned short* data)
-{
-    gsBuffer = data;
-    
-    // Tell reset function that new GS data has been given
-    newGSData = true;
-}
-
-void TLC5940::setNewDCData(unsigned char* data)
-{
-    dcBuffer = data;
-    
-    // Tell reset function that new DC data has been given
-    newDCData = true;
-}
-
-void TLC5940::reset()
-{
-    gsclk.write(0);
-    // Turn off LEDs
-    blank = 1;
-    
-    // Virtual function that allows the next data chunk to be set after every GSCLK cycle
-    // Useful for setting the next frame when multiplexing (e.g. LED matrices)
-    setNextData();
-    
-    // Latch in data from previous cycle if needed
-    if (need_xlat)
-    {
-        // Latch
-        xlat = 1;
-        xlat = 0;
-        
-        // Don't need to latch again
-        need_xlat = false;
-    }
-    
-    // Reset the screen so that it is updating while data is being sent
-    blank = 0;
-    gsclk.write(.5);
-    
-    // Do we have new DC data to send?
-    if (newDCData)
-    {
-        // Set TLC5940 to accpet DC data
-        vprg = 1;
-        
-        // Get DC data from registers instead of EEPROM (since we are sending data to the registers now)
-        dcprg = 1;
-        
-        // Send DC data backwards - this makes the DC_buffer[0] index correspond to OUT0
-        for (int i = (16 * number) - 1; i >= 0; i--)
-        {
-            // Assemble a 12 bit packet from two 6 bit chunks
-            spi.write(((dcBuffer[i] & 0x3F) << 6) | (dcBuffer[i-1] & 0x3F));
-            i--;
-        }
-        
-        // Latch
-        xlat = 1;
-        xlat = 0;
-        
-        // No new data to send (we just sent it!)
-        newDCData = false;
-    }
-    
-    // Do we have new GS data to send?
-    if (newGSData)
-    {
-        // Set TLC5940 to accept GS data
-        vprg = 0;
-        
-        // Send GS data backwards - this makes the GS_buffer[0] index correspond to OUT0 
-        for (int i = (16 * number) - 1; i >= 0; i--)
-        {
-            // Get the lower 12 bits of the buffer and send
-            spi.write(gsBuffer[i] & 0xFFF);
-        }
-        
-        // Latch after current GS data is done being displayed
-        need_xlat = true;
-        
-        // No new data to send (we just sent it!)
-        newGSData = false;
-    }
-}
\ No newline at end of file
diff -r e5ed5650eb15 -r ab6b451bbf40 TLC5940.h
--- a/TLC5940.h	Sun May 26 05:04:29 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,242 +0,0 @@
-#ifndef TLC5940_H
-#define TLC5940_H
-
-#include "FastPWM.h"
- /*
- 
-    ASCII art cat(why not?):
-         /\_/\
-    ____/ o o \
-  /~____  =ø= /
- (______)__m_m)
- 
- */
- 
-/**
-  * SPI speed used by the mbed to communicate with the TLC5940
-  * The TLC5940 supports up to 30Mhz. This should be kept as high
-  * as possible to ensure that data has time to be sent each reset cycle.
-  */
-#define SPI_SPEED 30000000
-
-/**
-  * The rate at which the GSCLK pin is pulsed
-  * This also controls how often the reset function is called
-  * The rate at which the reset function is called can be calculated by: (1/GSCLK_SPEED) * 4096
-  * The maximum reliable rate is around ~32Mhz. I reccomend keeping this as low as possible because
-  * a higher rate will use more CPU. Also, this must be low enough to give time for sending new data
-  * before the completion of a GSCLK cycle (4096 pulses). If you are daisy chaining multiple TLC5940s,
-  * divide 32Mhz by the number of chips to get a good maximum rate.
-  */
-#define GSCLK_SPEED 2500000
-
-/**
-  *  This class controls a TLC5940 PWM driver IC.
-  *  It supports sending dot correction and grayscale data. However, it does not support error checking or writing the EEPROM.
-  *  This class uses the FastPWM library by Erik Olieman to continuously pulse the GSLCK pin without CPU intervention. After
-  *  4096 pulses, the private member funciton reset is called by the ticker. It resets the display by pulsing the BLANK pin. If new
-  *  data has been set to be sent by the functions setNewGSData or setNewDCData, it is sent here. The definition GSCLK_SPEED in TLC5940.h
-  *  controls how often this function is called. A higher GSCLK_SPEED will increase the rate at which the screen is updated but also increase
-  *  CPU time spent in that function. The default value is 1Mhz. The rate at which the reset function is called can be calculated by: 
-  *  (1/GSCLK_SPEED) * 4096.
-  *
-  *  Using the TLC5940 class to control an LED:
-  *  @code
-  *  #include "mbed.h"
-  *  #include "TLC5940.h"
-  *  
-  *  // Create the TLC5940 instance
-  *  TLC5940 tlc(p7, p5, p21, p9, p10, p11, p12, 1);
-  *  
-  *  int main()
-  *  {   
-  *      // Create a buffer to store the data to be sent
-  *      unsigned short GSData[16] = { 0x0000 };
-  *  
-  *      // Enable the first LED
-  *      GSData[0] = 0xFFF;
-  *      
-  *      // Set the new data
-  *      tlc.setNewGSData(GSData);
-  *      
-  *      while(1)
-  *      {
-  *  
-  *      }
-  *  }
-  *  @endcode
-  */
-class TLC5940
-{
-public:
-    /**
-      *  Set up the TLC5940
-      *  @param SCLK - The SCK pin of the SPI bus
-      *  @param MOSI - The MOSI pin of the SPI bus
-      *  @param GSCLK - The GSCLK pin of the TLC5940(s)
-      *  @param BLANK - The BLANK pin of the TLC5940(s)
-      *  @param XLAT - The XLAT pin of the TLC5940(s)
-      *  @param DCPRG - The DCPRG pin of the TLC5940(s)
-      *  @param VPRG - The VPRG pin of the TLC5940(s)
-      *  @param number - The number of TLC5940s (if you are daisy chaining)
-      */
-    TLC5940(PinName SCLK, PinName MOSI, PinName GSCLK, PinName BLANK,
-            PinName XLAT, PinName DCPRG, PinName VPRG, const int number = 1);
-
-    /**
-      *  Set the next chunk of grayscale data to be sent
-      *  @param data - Array of 16 bit shorts containing 16 12 bit grayscale data chunks per TLC5940
-      *  @note These must be in intervals of at least (1/GSCLK_SPEED) * 4096 to be sent
-      */
-    void setNewGSData(unsigned short* data);
-
-    /**
-      *  Set the next chunk of dot correction data to be sent
-      *  @param data - Array of 8 bit chars containing 16 6 bit dot correction data chunks per TLC5940
-      *  @note These must be in intervals of at least (1/GSCLK_SPEED) * 4096 to be sent. Also, this function is optional. If you do not
-      *  use it, then the TLC5940 will use the EEPROM, which (by default) conatins the data 0x3F.
-      */
-    void setNewDCData(unsigned char* data);
-
-protected:
-    /**
-      *  Set the next chunk of grayscale data to be sent while in the current reset cycle
-      *  @note This is useful to send the next set of data right after the first is finished being displayed.
-      *  The primary purpose for this is multiplexing, although it could be used for anything else.
-      */
-    virtual void setNextData() {}
-
-
-    // Number of TLC5940s in series
-    const int number;
-
-private:
-    // SPI port - only MOSI and SCK are used
-    SPI spi;
-
-    // PWM output using the FastPWM library by Erik Olieman
-    FastPWM gsclk;
-
-    // Digital out pins used for the TLC5940
-    DigitalOut blank;
-    DigitalOut xlat;
-    DigitalOut dcprg;
-    DigitalOut vprg;
-
-    // Call a reset function to manage sending data and GSCLK updating
-    Ticker reset_ticker;
-
-    // Has new GS/DC data been loaded?
-    volatile bool newGSData;
-    volatile bool newDCData;
-
-    // Do we need to send an XLAT pulse? (Was GS data clocked in last reset?)
-    volatile bool need_xlat;
-
-    // Buffers to store data until it is sent
-    unsigned short* gsBuffer;
-    unsigned char* dcBuffer;
-
-    // Function to reset the display and send the next chunks of data
-    void reset();
-};
-
-
-/**
-  *  This class allows a TLC5940 to be multiplexed.
-  *  It inherits the TLC5940 class and uses it to control the TLC5940 driver(s). It does not support sending dot corection data.
-  *  This class sets the new grayscale data every iteration of the GSCLK reset loop. It then updates the current row using the
-  *  user defined function SetRows. The framerate you will recieve using this function can be calculate by: 1 / (((1/GSCLK_SPEED) * 4096) * rows).
-  *  I reccomend maintaining a framerate above 30fps. However, keep in mind that as your framerate increases, so does your CPU usage.
-  *
-  *  Using the TLC5940Mux class to control an 8x8 LED matrix:
-  *  @code
-  *  #include "mbed.h"
-  *  #include "TLC5940.h"
-  *  
-  *  // Bus connecting to the rows of the LED matrix through PNP transistors
-  *  BusOut rows(p22, p23, p24, p25, p26, p27, p28, p29);
-  *  
-  *  // Function to update the rows using the BusOut class
-  *  void SetRows(int nextRow)
-  *  {
-  *      // I am using PNP transistors, so inversion is necessary
-  *      rows = ~(1 << nextRow);
-  *  }
-  *  
-  *  // Create the TLC5940Mux instance
-  *  TLC5940Mux tlc(p7, p5, p21, p9, p10, p11, p12, 1, 8, &SetRows);
-  *  
-  *  int main()
-  *  {   
-  *      tlc[0][0] = 0xFFF; // Turn on the top left LED
-  *      while(1)
-  *      {
-  *  
-  *      }
-  *  }
-  *  @endcode
-  */
-class TLC5940Mux : private TLC5940
-{
-public:
-    /**
-      *  Set up the TLC5940
-      *  @param SCLK - The SCK pin of the SPI bus
-      *  @param MOSI - The MOSI pin of the SPI bus
-      *  @param GSCLK - The GSCLK pin of the TLC5940(s)
-      *  @param BLANK - The BLANK pin of the TLC5940(s)
-      *  @param XLAT - The XLAT pin of the TLC5940(s)
-      *  @param DCPRG - The DCPRG pin of the TLC5940(s)
-      *  @param VPRG - The VPRG pin of the TLC5940(s)
-      *  @param number - The number of TLC5940s (if you are daisy chaining)
-      *  @param rows - The number of rows you are multiplexing
-      *  @param SetRows - The function pointer to your function that sets the current row. 
-      *  @note The SetRows function allows you to set exactly how you want your rows 
-      *  to be updated. The TLC5940Mux class calls this function with an argument of int that contains the number of the row to 
-      *  be turned on. If the TLC5940Mux class needs the first row to be turned on, the int will be 0.
-      */
-    TLC5940Mux(PinName SCLK, PinName MOSI, PinName GSCLK, PinName BLANK,
-               PinName XLAT, PinName DCPRG, PinName VPRG, const int number,
-               const int rows, void (*SetRows)(int));
-    
-    // Destructor used to delete memory        
-    ~TLC5940Mux();
-
-    /**
-      *  Set the contents of the buffer that contains the multiplexed data
-      *  @param data - The data to set to the buffer containing 16 12 bit grayscale data chunks per TLC5940
-      *  @returns The data provided
-      */
-    unsigned short* operator=(unsigned short* data);
-    
-    /**
-      *  Get a pointer to one of the rows of the multiplexed data
-      *  @param index - The row that you would like the contents of
-      *  @returns A pointer to the data containing the requested row containing 16 12 bit grayscale data chunks per TLC5940
-      *  @note This operator can also be used to change or get the value of an individual LED.
-      *  For example:
-      *  @code
-      *  TLC5940Mux[0][0] = 0xFFF;
-      *  @endcode
-      */
-    unsigned short* operator[](int index);
-    
-private:
-    // Virtual function overriden from TLC5940 class
-    virtual void setNextData();
-    
-    // Number of rows
-    const int rows;
-
-    // Function to set the current row
-    void (*SetRows)(int);
-
-    // The current row
-    volatile int currentIndex;
-
-    // Buffer containing data to be sent during each frame
-    unsigned short* dataBuffer;
-};
-
-#endif
\ No newline at end of file
diff -r e5ed5650eb15 -r ab6b451bbf40 TLC5940Mux.cpp
--- a/TLC5940Mux.cpp	Sun May 26 05:04:29 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-#include "mbed.h"
-#include "TLC5940.h"
-
-
-TLC5940Mux::TLC5940Mux(PinName SCLK, PinName MOSI, PinName GSCLK, PinName BLANK, 
-                       PinName XLAT, PinName DCPRG, PinName VPRG, const int number, 
-                       const int rows, void (*SetRows)(int)) : TLC5940(SCLK, MOSI, GSCLK, BLANK, XLAT, DCPRG, VPRG, number),
-                                                               rows(rows),
-                                                               SetRows(SetRows),
-                                                               currentIndex(0)
-
-{
-    // Create a data buffer to store the current LED states
-    dataBuffer = new unsigned short[rows * 16 * number];
-    
-    // Zero the buffer
-    memset(dataBuffer, 0x00, rows * 16 * number * 2);
-}
-
-TLC5940Mux::~TLC5940Mux()
-{
-    // Delete the buffer
-    delete[] dataBuffer;
-}
-
-unsigned short* TLC5940Mux::operator=(unsigned short* data)
-{
-    // Copy the memory from data to the data buffer
-    memcpy(dataBuffer, data, rows * 16 * number * 2);
-
-    return data;
-}
-
-unsigned short* TLC5940Mux::operator[](int index)
-{
-    // Return the start of the correct data chunk
-    return dataBuffer + (index * 16 * number);
-}
-
-void TLC5940Mux::setNextData()
-{
-    // Move into the dataBuffer and return a pointer corresponding to the start of the correct data block
-    setNewGSData(dataBuffer + (currentIndex * 16 * number));
-    
-    // Since the data we sent on the previous reset was just latched in,
-    // we must enable the row for the previous index so it is displayed in the right position
-    // The ternary is used in case index is zero (we can't have an index of -1)
-    SetRows((currentIndex ? (currentIndex - 1) : (rows - 1)));
-    
-    // Go to next index
-    if (currentIndex == (rows - 1))
-        currentIndex = 0;
-    else
-        currentIndex++;
-}
\ No newline at end of file
diff -r e5ed5650eb15 -r ab6b451bbf40 TLC5955.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TLC5955.cpp	Sat Jun 09 23:22:13 2018 +0000
@@ -0,0 +1,297 @@
+#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;
+    }
+}
\ No newline at end of file
diff -r e5ed5650eb15 -r ab6b451bbf40 TLC5955.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TLC5955.h	Sat Jun 09 23:22:13 2018 +0000
@@ -0,0 +1,241 @@
+#ifndef TLC5955_H
+#define TLC5955_H
+
+//#include "FastPWM.h"
+#include "mbed.h"
+
+#define SHORTS_PER_CHANNEL 3
+#define CHANNELS_PER_IC  16
+#define NUMBER_OF_ICS 3
+ 
+#define TRUE 1
+#define FALSE 0
+
+// slow this down 10x for testing
+//#define SEQUENCER_RATE 0.5f
+//#define SEQUENCER_RATE 0.025f
+#define SEQUENCER_RATE 0.030f
+
+/**
+  * SPI speed used by the mbed to communicate with the TLC5955
+  * The TLC5955 supports up to 30Mhz. This should be kept as high
+  * as possible to ensure that data has time to be sent each reset cycle.
+  */
+  // 4Mhz is fastest supported rate on nRF51
+#define SPI_SPEED 4000000
+
+void rebuildGammaTables(uint8_t amplitude);
+
+/**
+  *  This class controls a TLC5955 PWM driver IC.
+  *  It supports sending dot correction and grayscale data. However, it does not support error checking or writing the EEPROM.
+  *  After
+  *  4096 pulses, the private member funciton reset is called by the ticker. It resets the display by pulsing the BLANK pin. If new
+  *  data has been set to be sent by the functions setNewGSData or setNewDCData, it is sent here. The definition GSCLK_SPEED in TLC5955.h
+  *  controls how often this function is called. A higher GSCLK_SPEED will increase the rate at which the screen is updated but also increase
+  *  CPU time spent in that function. The default value is 1Mhz. The rate at which the reset function is called can be calculated by: 
+  *  (1/GSCLK_SPEED) * 4096.
+  */
+class TLC5955
+{
+public:
+
+    typedef enum
+    {
+        I_3_2_MA = 0,
+        I_8_0_MA = 1,
+        I_11_2_MA = 2,
+        I_15_9_MA = 3,
+        I_19_1_MA = 4,
+        I_23_9_MA = 5,
+        I_27_1_MA = 6,
+        I_31_9_MA = 7
+    } led_power_t;
+
+    /**
+      *  Set up the TLC5955
+      *  @param SCLK - The SCK pin of the SPI bus
+      *  @param MOSI - The MOSI pin of the SPI bus
+      *  @param GSCLK - The GSCLK pin of the TLC5955(s)
+      *  @param BLANK - The BLANK pin of the TLC5955(s)  <- REMOVE
+      *  @param XLAT - The XLAT pin of the TLC5955(s)
+      *  @param DCPRG - The DCPRG pin of the TLC5955(s)  <- REMOVE
+      *  @param VPRG - The VPRG pin of the TLC5955(s) <- REMOVE
+      *  @param number - The number of TLC5955s (if you are daisy chaining)
+      */
+    TLC5955(PinName SCLK, PinName MOSI, PinName GSCLK, 
+            PinName XLAT, const int number = 1);
+
+    void setChannel(int channelNum, unsigned short red, unsigned short green, unsigned short blue);
+    void latchData();
+  
+    
+    /**
+      *  Set the next chunk of grayscale data to be sent
+      *  @param data - Array of 16 bit shorts containing 16 12 bit grayscale data chunks per TLC5955
+      *  @note These must be in intervals of at least (1/GSCLK_SPEED) * 4096 to be sent
+      */
+    void setNewGSData(unsigned short* data);
+
+    /**
+      *  Set the next chunk of dot correction data to be sent
+      *  @param data - Array of 8 bit chars containing 16 6 bit dot correction data chunks per TLC5955
+      *  @note These must be in intervals of at least (1/GSCLK_SPEED) * 4096 to be sent. Also, this function is optional. If you do not
+      *  use it, then the TLC5955 will use the EEPROM, which (by default) conatins the data 0x3F.
+      */
+void 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);
+    void clockOutData();
+    void clearBit (unsigned short* value, int bitOffset); 
+    void setBit (unsigned short* value, int bitOffset); 
+    void packBit(unsigned int aBit);
+    void packByte (unsigned int aByte);
+    void packShort (unsigned int aShort);
+protected:
+    /**
+      *  Set the next chunk of grayscale data to be sent while in the current reset cycle
+      *  @note This is useful to send the next set of data right after the first is finished being displayed.
+      *  The primary purpose for this is multiplexing, although it could be used for anything else.
+      */
+    virtual void setNextData() {}
+
+
+    // Number of TLC5955s in series
+    const int number;
+
+private:
+
+    // SPI port - only MOSI and SCK are used
+    SPI spi;
+
+    // PWM output
+    DigitalOut gsclk;
+
+    // Digital out pins used for the TLC5955
+    DigitalOut xlat;
+
+    // Call a reset function to manage sending data and GSCLK updating
+    Ticker reset_ticker;
+
+    int currentBitLocation;
+    
+    // Has new GS/Control data been loaded?
+    volatile bool newGSData;
+    volatile bool newControlData;
+
+    // Do we need to send an XLAT pulse? (Was GS data clocked in last reset?)
+    volatile bool need_xlat;
+
+    // Buffer to store data until it is sent
+    unsigned short gsBuffer[(SHORTS_PER_CHANNEL * CHANNELS_PER_IC * NUMBER_OF_ICS) + 1];  // one extra word to fit the MSB control bit. Extra bits will be clocked out.
+
+    unsigned short internalData[SHORTS_PER_CHANNEL * CHANNELS_PER_IC * NUMBER_OF_ICS]; // internal data storage for use by per-channel setting function
+    unsigned short globalBrightnessRed;
+    unsigned short globalBrightnessGreen;
+    unsigned short globalBrightnessBlue;
+    unsigned short maximumCurrentRed;
+    unsigned short maximumCurrentGreen; 
+    unsigned short maximumCurrentBlue;
+    unsigned short* dotCorrect;
+    
+    // Function to reset the display and send the next chunks of data
+    void reset();
+};
+
+
+/**
+  *  This class allows a TLC5955 to be multiplexed.
+  *  It inherits the TLC5955 class and uses it to control the TLC5955 driver(s). It does not support sending dot corection data.
+  *  This class sets the new grayscale data every iteration of the GSCLK reset loop. It then updates the current row using the
+  *  user defined function SetRows. The framerate you will recieve using this function can be calculate by: 1 / (((1/GSCLK_SPEED) * 4096) * rows).
+  *  I reccomend maintaining a framerate above 30fps. However, keep in mind that as your framerate increases, so does your CPU usage.
+  *
+  *  Using the TLC5955Mux class to control an 8x8 LED matrix:
+  *  @code
+  *  #include "mbed.h"
+  *  #include "TLC5955.h"
+  *  
+  *  // Bus connecting to the rows of the LED matrix through PNP transistors
+  *  BusOut rows(p22, p23, p24, p25, p26, p27, p28, p29);
+  *  
+  *  // Function to update the rows using the BusOut class
+  *  void SetRows(int nextRow)
+  *  {
+  *      // I am using PNP transistors, so inversion is necessary
+  *      rows = ~(1 << nextRow);
+  *  }
+  *  
+  *  // Create the TLC5955Mux instance
+  *  TLC5955Mux tlc(p7, p5, p21, p9, p10, p11, p12, 1, 8, &SetRows);
+  *  
+  *  int main()
+  *  {   
+  *      tlc[0][0] = 0xFFF; // Turn on the top left LED
+  *      while(1)
+  *      {
+  *  
+  *      }
+  *  }
+  *  @endcode
+  */
+class TLC5955Mux : private TLC5955
+{
+public:
+    /**
+      *  Set up the TLC5955
+      *  @param SCLK - The SCK pin of the SPI bus
+      *  @param MOSI - The MOSI pin of the SPI bus
+      *  @param GSCLK - The GSCLK pin of the TLC5955(s)
+      *  @param BLANK - The BLANK pin of the TLC5955(s)
+      *  @param XLAT - The XLAT pin of the TLC5955(s)
+      *  @param DCPRG - The DCPRG pin of the TLC5955(s)
+      *  @param VPRG - The VPRG pin of the TLC5955(s)
+      *  @param number - The number of TLC5955s (if you are daisy chaining)
+      *  @param rows - The number of rows you are multiplexing
+      *  @param SetRows - The function pointer to your function that sets the current row. 
+      *  @note The SetRows function allows you to set exactly how you want your rows 
+      *  to be updated. The TLC5955Mux class calls this function with an argument of int that contains the number of the row to 
+      *  be turned on. If the TLC5955Mux class needs the first row to be turned on, the int will be 0.
+      */
+    TLC5955Mux(PinName SCLK, PinName MOSI, PinName GSCLK,
+               PinName XLAT, const int number,
+               const int rows, void (*SetRows)(int));
+    
+    // Destructor used to delete memory        
+    ~TLC5955Mux();
+
+    /**
+      *  Set the contents of the buffer that contains the multiplexed data
+      *  @param data - The data to set to the buffer containing 16 12 bit grayscale data chunks per TLC5955
+      *  @returns The data provided
+      */
+    unsigned short* operator=(unsigned short* data);
+    
+    /**
+      *  Get a pointer to one of the rows of the multiplexed data
+      *  @param index - The row that you would like the contents of
+      *  @returns A pointer to the data containing the requested row containing 16 12 bit grayscale data chunks per TLC5955
+      *  @note This operator can also be used to change or get the value of an individual LED.
+      *  For example:
+      *  @code
+      *  TLC5955Mux[0][0] = 0xFFF;
+      *  @endcode
+      */
+    unsigned short* operator[](int index);
+    
+private:
+    // Virtual function overriden from TLC5955 class
+    virtual void setNextData();
+    
+    // Number of rows
+    const int rows;
+
+    // Function to set the current row
+    void (*SetRows)(int);
+
+    // The current row
+    volatile int currentIndex;
+};
+
+#endif
\ No newline at end of file