Hardware SPI and TLC5940?

Has anyone successfully controlled the TLC5940 LED driver with the Arduino's hardware SPI?

I adapted the code given by psmackey in this post to use port manipulation for all of the digitalWrites, partially for speed but mostly as an exercise for myself. It works great (and I can post it if anyone is interested).

However, when I try switching from a manual SPI implementation to the ATMEGA's hardware SPI, I get nothing. Keeping the GSCLK signal on pin 8 I got nothing at all, and then figuring that, since the hardware SPI is on PORTB it might be interfering, I moved it to pin 7; I then got all of the LEDs turning on, but no control over them. It seems the SPI isn't properly sending data, because I can't see the on-board LED on pin 13 flickering as it does in other hardware SPI situations.

I've tried all sorts of test cases and switched up all sorts of settings (I've tried all four clock modes, for example) all to no avail. I really wanted to figure this out on my own, but I'm at my wit's end. Frankly, I wouldn't be upset if there's some reason it can't work, so long as I knew what that reason was. Anybody have any ideas?

Here is some very simple spi_transfer test code, which doesn't work. It should just turn all the LEDs on full and leave them there, but I get all blanks. The equivalent code without hardware SPI works fine.

/*Ti 5940 16-port LED driver 
 * Peter Mackey  June 2007  Pratt Digital Arts  pmackey@pratt.edu 
 * smooth flickerless fading (thanks to Amp on the Arduino forum) 
 * additional logic from David Cuartielles's & Marcus Hannerstig's LEDdriver demo 
 * Modified to utilize hardware SPI by Jonathan Guberman, January 2008
 * Hardware SPI adapted from Heather Dewey-Hagborg's SPIDigitalPot tutorial, http://www.arduino.cc/en/Tutorial/SPIDigitalPot
 */
//using the pin codes on the TLC5940NT to name the Arduino ports  
#define VPRG 2 //"chip pin 27 to Arduino pin 2" 
#define SIN 11 // MOSI - Hardware SPI
#define SCLK 13 // SCK - Hardware SPI  
#define XLAT 4 
#define BLANK 5 
#define DCPRG 6 
#define GSCLK 8 //note: but using PORTB method 

#define FADEMIN 0 //lowest fade level LEDs will reach (min would be 0, max 4095) 
#define FADEINCR 16   //determines how many steps it takes to run the desired range (lower=longer) 

char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

void setup() { 
  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 
  
  // SPCR = 01010000
  //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
  //sample on leading edge of clk,system clock/4 (fastest)
  byte clr;
  SPCR = (1<<SPE)|(1<<MSTR);
  clr=SPSR;
  clr=SPDR; 
  delay(10);
  
  preset();    //input “Dot Correction” data 
  //Serial.begin(19200);
} 

void loop () { 
  feedPorts();     
} 

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 
  PORTB &= B11110111; // Serial in low
  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 < 12; i++) { // 96 bits of data = 12 * 8 bits per byte
      spi_transfer(B11111111);      //for now, 111111 for everybody 
  } 

  //FIRST GREYSCALE SETTING 

  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 < 24; i++) { //16 ports 
    spi_transfer(B11111111);
  }   
  digitalWrite(XLAT, HIGH);  //latch the greyscale data  
  digitalWrite(XLAT, LOW); 
  PORTB |= B00100000;               //193rd clock pulse only need to do the FIRST time after dot correx 
  PORTB &= B11011111;
  digitalWrite(BLANK, LOW);  //=all outputs ON, start PWM cycle... moved here 
} 

void pulseGSCLK() { 
  //ultra fast pulse trick, using digitalWrite caused flickering 
  PORTD |= 0x80 ; // bring pin 8 high, but don't touch any of the other pins in PORTB
  //16nanosecs is the min pulse width for the 5940, but no pause seems needed here 
  PORTD &= 0x7F;  // bring pin 8 low without touching the other pins in PORTB
}

As is so often the case, the problem was silly and obvious in retrospect. I wasn't defining the Arduino's slave select pin as an output, and it wasn't connected to anything, so it was getting randomly pulled low, switching the hardware SPI into slave mode and effectively turning everything off. Oops!

I will post corrected example code once I get the chance, just in case anyone else searches for how to do this.

Did you ever get this working with SPI?

I have tried every piece of code I could find for the Ti5940 and nothing seems to work.

Did you have luck with this code:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1173831471/12#12

I connected as the comments instructed, but all that happens is the 5940 gets hot....
Are you using a resistor between GND and Iref?

Is there anything that I could be missing? I connect to pins to the Arduino as the code instructs, and the Vcc and GND go to a 5V DC supply, separate from the Arduino since I want to use more current than the Arduino can ouput.

supply the TLC5940 chip with 3.3v from the decimilia

this will set the maximum current on each pin to something like 60 mA and the chip wont overheat

Did you remember to connect the ground from the external powersupply to the Arduino ground ?

Did you ever get this working with SPI?

I never had any problem getting the "Expanding PWM" code to work, and I also managed to get it working with SPI. Here's the working SPI code, as I mentioned above. (I haven't cleaned this code up to make it easier for others to read, so I apologize for any incorrect/missing comments.)

//Fades the LEDs in sequence

//using the pin codes on the TLC5940NT to name the Arduino ports  
#define VPRG 2 //"chip pin 27 to Arduino pin 2" 
#define SIN 11 // MOSI - Hardware SPI
#define SCLK 13 // SCK - Hardware SPI  
#define XLAT 4 
#define BLANK 5 
#define DCPRG 6 
#define GSCLK 7
#define DATAIN 12 //MISO - not used, but part of builtin SPI
#define SLAVESELECT 10//ss
// uncomment the followsing line to enter debug mode
// #define DEBUG
#define FADEINCR 8 //controls the speed of fading: higher is faster, 0 won't do anything at all, 1 is the fastest possible rate
#define FADEMIN 0

int level[16] = {0};
int fadeState[16] = {1, 0};

unsigned long time;

char spi_transfer(volatile byte data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  {
  };
#ifdef DEBUG
  Serial.println(data, BIN);
#endif
  return SPDR;                    // return the received byte
}

void setup() { 
  pinMode(VPRG, OUTPUT);     
  pinMode(SIN, OUTPUT);     
  pinMode(SCLK, OUTPUT);  
  pinMode(XLAT, OUTPUT);     
  pinMode(BLANK, OUTPUT);     
  pinMode(DCPRG, OUTPUT);     
  pinMode(GSCLK, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH); //disable device
  digitalWrite(SIN, LOW);
  digitalWrite(SCLK, LOW);
  digitalWrite(XLAT, LOW);
  digitalWrite(VPRG, LOW);
  digitalWrite(BLANK, HIGH);
  digitalWrite(GSCLK, HIGH);
  digitalWrite(DCPRG, LOW); //Use EEPROM DC register if LOW
  // SPCR = 01010000
  //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
  //sample on leading edge of clk,system clock/4 (fastest)
  byte clr;
  SPCR = (1<<SPE)|(1<<MSTR);
  clr=SPSR;
  clr=SPDR; 
  delay(10);

#ifdef DEBUG
  Serial.begin(19200);
#endif

  setGreys();
  time = millis();
} 

void loop () {
  incrementFades();
  setGreys();
  feedPorts();     
} 

void setGreys() {
  digitalWrite(BLANK, HIGH);
  digitalWrite(XLAT,LOW);
  for(int i = 7; i>=0; i--){ //192 bits shift
    spi_transfer( (level[2*i+1] & 0x0FF0) >> 4 );
    spi_transfer( ((level[2*i+1] & 0xF) << 4) | ((level[2*i] & 0x0F00) >> 8) );
    spi_transfer( level[2*i] & 0xFF);
#ifdef DEBUG
    Serial.print("High: ");
    Serial.println((level[2*i+1] & 0x0FF0) >> 4, BIN);
    Serial.print("Mid: ");
    Serial.println( ((level[2*i+1] & 0xF) << 4) | ((level[2*i] & 0x0F00) >> 8), BIN);
    Serial.print("Low: ");
    Serial.println( level[2*i] & 0xFF, BIN );
    Serial.println();
#endif
  }
  digitalWrite(XLAT,HIGH);
  digitalWrite(XLAT,LOW);
  digitalWrite(BLANK, LOW); 
}

void incrementFades() { 
  //uses two 16 item arrays  
  //fadeState stores -1,0,or1 to specify direction of fade 
  //level stores current value of fade 
  //too long to post, sorry...
  for(int i = 0; i<16;++i){
    level[i] += fadeState[i]*FADEINCR;
    if(level[i] > 4095){
      level[i] = 4095;
      fadeState[i] = -1;
      fadeState[(i+1)%16] = 1;
    } 
    else if(level[i] <= FADEMIN) {
      level[i] = FADEMIN;
      fadeState[i] = 0;
    }
  }
} 

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

void pulseGSCLK() { 
  //ultra fast pulse trick, using digitalWrite caused flickering 
  PORTD |= 0x80 ; // bring pin 7 high, but don't touch any of the other pins in PORTB
  //16nanosecs is the min pulse width for the 5940, but no pause seems needed here 
  PORTD &= 0x7F;  // bring pin 7 low without touching the other pins in PORTB
}

go to this site:
https://whatever.metalab.at/user/wizard23/tlc5940/arduino/TLC5940/

and download and install the library for the TLC5940

it was written by someone at MIT.... and it works with the diecimila

and download and install the library for the TLC5940

I tried that, no luck

I also looked at this with no luck

A._Square: Thanks very much for the code. Unfortunately, I was not able to get it to work, so I must be connecting it incorrectly, or I have 4 bad chips :(. My first guess is that I made some connection mistake, but it is possible that I have bad chips (or burnt them out due to my connection mistake). I know my Arduino and the SPI is working find because I have interfaced with a SPI-controlled latching multiplexer and an EEPROM.

I took out the extra power source for now, to try and debug the circuit. So, now I am pulling 5V from the Arduino (via USB).

Would anyone mind taking a look at my breadboard?

I know that the LEDs are wired correctly, because if I jump +5V to the any resistor, then the LED lights up.

2 odd things that I have noticed:
If I disconnect the GND from the Arduino, then the green LED (which I use to see if I am getting power) dims but does not go out. It will only go out if I disconnect the GND from the 5940.

If I replace the GND wire to the 5940 with a resistor, then when I hold in the Arduino's reset button, all of the connected LED's will light dimly, and go out when I release the button. AT least this indicates that there are no breadboard-connection issues.

Any help would be greatly appreciated.

do u have a resistor from ground to IREF?

this is necessary

I tried it both with and without a 470ohm resistor.

I want an output current on each pin of 20mA. According to my understanding of the data sheet I really need a 2.2kOhm resistor between GND and Iref, but 470 is the largest I have around before 10kOhm. I figure 470ohm will limit the current to around 80mA, and that is why I then have a resistor for each led.

(perhaps trying without the resistor burnt the chip?)

i dont think not having the IREF resistor will burn the chip. In my experience, the chip didnt respond without a resistor. My TLC5940 overheated (one burnt/gone) because I fed it 5v to the VCC

The TLC5940 can take a Vcc of up to 5.5V, and I've run plenty for long periods of time with 5V on Vcc with no problems.

I've had one chip burn out on me, and that was the only chip I soldered onto a board, so I assume the problem was that I overheated it when soldering. I replaced it with a socket, and haven't had any problems since.

Finally, I think I figured it out.

I assumed that each Out(n) went to GND, I.E. that current flowed from the Vcc pin through to each Out(n), and then through the LED and to GND. But, when looking at other TI docs, I see that the current really flows from each Vcc through the LED, into Out(n) and then out of the GND pin.

Swapping it around fixed it, and now it all works :slight_smile:

Since I am new to this EE stuff, how should I have known the proper polarity from the data-sheet. I looked at it again and don't see anything that would indicate that current flows from each Out pin to GND.

Well, for starters, the first line of the description is "The TLC5940 is a 16-channel, constant-current sink LED driver" (emphasis mine). A current sink means that the current flows into the out pins; the alternative would be a current source.

Another place is Figure 22, the Application Example, on page 22 of the datasheet. It shows a schematic of the TLC5940 all wired up (two wired in a chain, in fact), which has the LED grounds connected to the Out(n) pins.

Don't feel too bad about misreading the datasheet - that was probably the single mosti intimidating thing for me to get around, too. It'll come together quickly, though. Glad it's working!

i made that same mistake too until i looked over the hook-up diagram and discovered it was active-low

Ok so ive the the brillidea led painter. spent ages making the rgb leds with leads and the connectors no if olny i knew how to code :frowning:

tried the metlabs codes none work i get errors
e.g [u]https://whatever.metalab.at/projects/Arduino/libraries/TLC5940/TLC5940.cpp[/u] I get "undefined reference to `setup' "

or if i try

[u]https://whatever.metalab.at/projects/Arduino/libraries/TLC5940/examples/Simple/[/u] I get "o: In function __static_initialization_and_destruction_0': undefined reference to TLC5940::TLC5940(unsigned char, unsigned char)'o: In function loop': o: In function setup'"

Please help :frowning:

which version of arduino IDE are you using?

you need decimilia with at least IDE 10 i think

im using arduino decimila wghat do you mena by IDE?

0011 Alpha if thats what you mean

ok, go to the folder where the tlc5940.o file is located, then delete it, and restart arduino

it should recompile the file and hopefully it will work

nope still doesnt work :frowning: