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
}