For my work on the responsive lighting system, I’ve chained three TLC 5940s together and modified some code that I found from peterM from Pratt.
I’m specifically working with RGB LEDs, so the code and the circuit were made to make that easier. I haven’t found any sources online or else where for making multiple RGB LED control easy and simple. The blinkM from sprakfun.com is ridiculously expensive, uses i^2c protocol, and in the end only controls a single freakin’ light. If this has enraged you as well, I hope my code and circuit pictures help you. If this doesn’t make sense to you, feel free to email me: cea276@nyu.edu
Alright, here’s some pictures to start.
This is just one TLC 5940 hooked up to an Arduino microcontroller. The code is the same as found on Peter’s page. He also has a good diagram for how to set it up. Although, on the right side of my picture you can see how to easily break out RGB LEDs on a separate breadboard.

Alright, now we take a big jump to daisy chaining 3 TLD 5940s. If you can do 3, you can do 40. Doing 2 in a row really isn’t that helpful if you can’t scale up, but you can always scale back from 3.
The essential idea is that the SPI lines (white wires) need to be drawn up to each TLC 5940 so that they’re running in parallel. Also, the SOUT of the first chip needs to go to the SIN of the second, then the SOUT of the second goes to the SIN of the third….and so on. In this picture I have the Arduino going into the bottom TLC 5940 and then having the subsequent chips stacking above. However, because of my code I should have done the opposite and had the arduino at the top of the stack. That way the LEDs would turn on sequence from top to bottom.
Ok here’s the picture:

Now, a nice big code drop, take a deep breath, it’s a bit heavy. I’ll clean it up and make it user-friendly real soon, I promise.
//using the pin codes on the TLC5940NT to name the Arduino ports
#define VPRG 2 //”chip pin 27 to Arduino pin 2″
#define SIN 3
#define SCLK 7
#define XLAT 4
#define BLANK 5
#define DCPRG 6
#define GSCLK 8 //note: but using PORTB method
// rates
#define fadeupRate .02 //determines how many steps it takes to run the desired range (higher=faster)
#define fadedownRate .007
#define rMax 4095 //brightness maximums
#define gMax 4095
#define bMax 4095
#define NumberOfLights 15
int totalLeads = (NumberOfLights*3)+(NumberOfLights/5);
int Max = 0;
int lightCounter = 0;
int chipNumber = 0;
int fadeLevel[(NumberOfLights*3)+(NumberOfLights/5)];
int leadNumber = 0; //counter used in this fading sequence
int fadeState[(NumberOfLights*3)+(NumberOfLights/5)]; //stores the direction of fading for each port 1,0,-1
//start with first port
int next; //used for limit checking in fading function
int word[] = {
0,0,0,0,0,0,0,0,0,0,0,0}; //temp storage for reversing bits in a word (for greyscale setting)
void setup() {
beginSerial(9600);
Serial.println(”Let’s do this…”);
pinMode(VPRG, OUTPUT);
pinMode(SIN, OUTPUT);
pinMode(SCLK, OUTPUT);
pinMode(XLAT, OUTPUT);
pinMode(BLANK, OUTPUT);
pinMode(DCPRG, OUTPUT);
pinMode(GSCLK, OUTPUT); //could also set DDRB directly
fadeState[0] = 1;
fadeState[1] = 1;
fadeState[2] = 1;
preset(); //input ‘Dot Correction’ data
}
void loop () {
setGreys();
feedPorts();
incrementFades();
}
void incrementFades() {
//a very particular sequence: fade up from LED 0 to 15 then fade down in same direction
//overlaps incoming&outgoing adjacent ports’ fade level
for (leadNumber=0; leadNumber<(totalLeads-1); leadNumber++) {
if ((leadNumber+1)%16 == 0){
leadNumber++;
}
lightCounter = leadNumber;
if ((lightCounter)%16 == 0){
chipNumber = lightCounter/16;
}
lightCounter-=chipNumber;
//Figure out the color
if (lightCounter%3 == 0){
Max = rMax;
}else if (lightCounter%3 == 1){
Max = gMax;
}else if (lightCounter%3 == 2){
Max = bMax;
}
if (fadeState[leadNumber]!=0) { //if the state for this LED is not 0…
if (fadeState[leadNumber]==1) { //then fade up…
next = fadeLevel[leadNumber]+(Max*fadeupRate);
if (next<Max) {
fadeLevel[leadNumber] = next; //…by incrementing the value in the fadeLevel array
}
else { //keep this one bright for sec then set state for this LED to fade down
fadeLevel[leadNumber] = Max; //be sure is at “max level”
//if this LED is pretty bright, make next neighbor begin a fade up
if (leadNumber== 14 + (16*chipNumber)){
fadeState[leadNumber+2] = 1;
fadeState[leadNumber+3] = 1;
fadeState[leadNumber+4] = 1;
} else if (leadNumber < (totalLeads -1)){
fadeState[leadNumber+1] = 1;
fadeState[leadNumber+2] = 1;
fadeState[leadNumber+3] = 1;
}
if (leadNumber > (totalLeads - 3)){
fadeState[0] = 1;
fadeState[1] = 1;
fadeState[2] = 1;
}
fadeState[leadNumber] = -1; //NOW flip my sign
}//————————————————
}
else {
next = fadeLevel[leadNumber]-(Max*fadedownRate);
if (next>0) { //—————–
fadeLevel[leadNumber] = next;
}
else {
//set me to fade down
fadeLevel[leadNumber] = 0; //be sure at minimum level
fadeState[leadNumber] = 0; //stop fading me until neighbor sets me to fade up
}
}//————————————————
}// end check for state not 0
}// end of cycle thru each port
}// end increment fades
//=======5940 control======================================
void setGreys() {
//data for each port (12 bit word * 16 ports =192 bits in this loop)…
//read the fadeLevel array
for (int i=(totalLeads-1); i>=0; i–) { // ports, count DOWN
int datb = fadeLevel[i];
//load fade level bits into the temp array BACKWARDS
for (int j=11; j>=0; j–) {
word[j]=(datb & 1); //& bitwise AND
datb >>= 1; //shift right and assign
// (maybe there’s a slicker way to do this!? but this works…)
}
//send the data to the 5940
for (int j=0; j<12; j++) {
digitalWrite(SIN,word[j]);
pulseSCLK();
}
}
digitalWrite(XLAT, HIGH);
digitalWrite(XLAT, LOW);
}
void feedPorts() {
//The actual sequencing of the PWM data into the LEDs, must do constantly…
digitalWrite(BLANK, HIGH);
digitalWrite(BLANK, LOW); //=all outputs ON, start PWM cycle
for (int i=0; i<4096; i++) {
pulseGSCLK();
}
}
//DOT CORRECTION…do once
void preset() {
//Input ‘DotCorrex’ Data
//16 outputs, 64 posssible levels of adjustment, 6 bits/chan = 96 bits total
//[use if any LEDs in array are physically too bright]
digitalWrite(DCPRG, HIGH); //leaving it H is my arbitrary choice (=”write to register not EEPROM”)
digitalWrite(VPRG, HIGH); //=inputting data into dot correx register
digitalWrite(BLANK, HIGH); //=all outputs off, when this goes high it resets the greyscale counter
digitalWrite(SIN, LOW); //to start dot correction
digitalWrite(XLAT, LOW);
//begin loading in the dot correx data, most significant bit first…
//but here we are not correcting anything, so LSB is going first!
for (int i=0; i<totalLeads; i++) { //16 ports
for (int j=0; j<6; j++) { //6 bits of data for each port
digitalWrite(SIN, HIGH); //for now, 111111 for everybody
pulseSCLK();
digitalWrite(SIN, LOW);
}
}
//—-doing the FIRST GREYSCALE SETTING here becuz of the unique 193rd clock pulse
digitalWrite(XLAT, HIGH); //latch the dot data into the dot correx register
digitalWrite(XLAT, LOW);
digitalWrite(VPRG, LOW); //entering greyscale mode
for (int i=0; i<totalLeads; i++) { //16 ports
int datb = 4095; //using same fade level for all ports this first time
for (int j=0; j<12; j++) { //data for each port, all the same value to start
digitalWrite(SIN, datb & 01);
pulseSCLK();
datb>>=1;
}
}
digitalWrite(XLAT, HIGH); //latch the greyscale data
digitalWrite(XLAT, LOW);
pulseSCLK(); //193rd clock pulse only need to do the FIRST time after dot correx
digitalWrite(BLANK, LOW); //=all outputs ON, start PWM cycle… moved here
}
//SCLK used in dot correx and greyscale setting
void pulseSCLK() {
digitalWrite(SCLK, HIGH);
digitalWrite(SCLK, LOW);
}
void pulseGSCLK() {
//ultra fast pulse trick, using digitalWrite caused flickering
PORTB=0×01; //bring PORTB0 high (pin 8), other ports go low [0×01 does only pin 8, 0×21 also lifts pin 13]
//16nanosecs is the min pulse width for the 5940, but no pause seems needed here
PORTB=0×20; //keep pin13 high [0×00 would be all low]
}
Enjoy.