Trouble with hardware SPI and the TLC5940

Hello,

I am trying to send data to the TLC5940 using SPI on a MEGA. I found an example that works, but the GSCLK pin is pulse in the main loop. I’m trying to use a CTC hardware toggle to pulse the pin and then load the data in between clock cycles. I have this method working without SPI, but whn I add the SPI it stops uploading the data.

Here is the broken code

#include <stdint.h>
// Definition of interrupt names
#include <avr/interrupt.h>
// ISR interrupt service routine
#include <avr/io.h>

// TLC5940 pin definitions
// PWM PIN 11
#define GSCLK_DDR DDRB
#define GSCLK_PORT PORTB
#define GSCLK_PIN PB5
// MOSI PIN 51
#define SIN_DDR DDRB
#define SIN_PORT PORTB
#define SIN_PIN PB2
// SCK PIN 52
#define SCLK_DDR DDRB
#define SCLK_PORT PORTB
#define SCLK_PIN PB1
// SS PIN 53
#define BLANK_DDR DDRL
#define BLANK_PORT PORTL
#define BLANK_PIN PL0

#define DCPRG_DDR DDRC
#define DCPRG_PORT PORTC
#define DCPRG_PIN PC4

#define VPRG_DDR DDRC
#define VPRG_PORT PORTC
#define VPRG_PIN PC7

#define XLAT_DDR DDRC
#define XLAT_PORT PORTC
#define XLAT_PIN PC1

#define DATAIN 50 //MISO - not used, but part of builtin SPI
#define SLAVESELECT 53//ss

#define TLC5940_N 1

// Macros
#define setOutput(ddr, pin) ((ddr) |= (1 << (pin)))
#define setLow(port, pin) ((port) &= ~(1 << (pin)))
#define setHigh(port, pin) ((port) |= (1 << (pin)))
                        
#define outputState(port, pin) ((port) & (1 << (pin)))

#if (12 * TLC5940_N > 255)
#define dcData_t uint16_t
#else
#define dcData_t uint8_t
#endif

#if (24 * TLC5940_N > 255)
#define gsData_t uint16_t
#else
#define gsData_t uint8_t
#endif

#define dcDataSize ((dcData_t)12 * TLC5940_N)
#define gsDataSize ((gsData_t)24 * TLC5940_N)

// Array for Dot Correction Data
uint8_t dcData[12 * TLC5940_N] = {
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
};

// Array for GreyScale Data
uint8_t gsData[24 * TLC5940_N] = {
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b11111111,
  0b00000000,
  0b10000000,
  0b00010000,
  0b00000010,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00001111,
  0b11111111,
};

inline void pulse(uint8_t port, uint8_t pin)
{
    setHigh(port, pin);
    setLow(port, pin);
}

void TLC5940_Init(void) { 
  
  setOutput(GSCLK_DDR, GSCLK_PIN);
  setOutput(SCLK_DDR, SCLK_PIN);
  setOutput(DCPRG_DDR, DCPRG_PIN);
  setOutput(VPRG_DDR, VPRG_PIN);
  setOutput(XLAT_DDR, XLAT_PIN);
  setOutput(BLANK_DDR, BLANK_PIN);
  setOutput(SIN_DDR, SIN_PIN);
  
  pinMode(DATAIN, INPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH); //disable device

  setLow(GSCLK_PORT, GSCLK_PIN);
  setLow(SCLK_PORT, SCLK_PIN);
  setLow(DCPRG_PORT, DCPRG_PIN);
  setHigh(VPRG_PORT, VPRG_PIN);
  setLow(XLAT_PORT, XLAT_PIN);
  setHigh(BLANK_PORT, BLANK_PIN);
  
  // Enable SPI, Master, set clock rate fck/2
  SPCR = (1 << SPE) | (1 << MSTR);
  SPSR = (1 << SPI2X);
  
   byte clr;
  clr=SPSR;
  clr=SPDR;
  delay(10);

  
  // Dont need to call sei(); because Arduino already does this
  
  // SET TIMERS
  // Clear TIMER1 Reg back to default
  TCCR1A = B00000000;
  TCCR1B = B00000000;
  // Enable timer 1 Compare Output channel A in toggle mode
  TCCR1A |= (1 << COM1A0);
  // Configure timer 1 for CTC mode
  TCCR1B |= (1 << WGM12);
  // Set up timer to fCPU (no Prescale) = 16Mhz/1 = 16Mhz
  TCCR1B |= (1 << CS10);
  // Set CTC compare value to pulse PIN at 8Mhz
  // (1 / Target Frequency) / (1 / Timer Clock Frequency) - 1
  // (1/16000000)/(1/16000000)-1 = 0 = 16Mhz
  // Full period of PIN 11 pulse requires 2 clock ticks (HIGH, LOW) 
  // So = f/2 = 16Mhz/2 = 8Mhz
  OCR1A = 0;
  
  // Clear TIMER3 Reg back to default  
  TCCR3A = B00000000;
  TCCR3B = B00000000;
  // Configure timer 3 for CTC mode
  TCCR3B |= (1 << WGM32);
  // Set up timer to fCPU (no prescale) = 16Mhz/1 = 16Mhz 
  TCCR3B |= (1 << CS30);
  // Set CTC compare value to 4096 full clock periods so 4096 @ 8Mhz
  // Hence (GSCLK value @ 16Mhz * 2 to bring it down to 8Mhz) -1
  OCR3A = (4096*2) - 1;
  // Enable Timer/Counter3 Compare Match A interrupt
  TIMSK3 |= (1 << OCIE3A);
}

void TLC5940_ClockInDC(void) {
  setHigh(DCPRG_PORT, DCPRG_PIN);
  setHigh(VPRG_PORT, VPRG_PIN);
  
  for (dcData_t i = 0; i < dcDataSize; i++) {
    // Start transmission
    SPDR = dcData[i];
    // Wait for transmission complete
    while (!(SPSR & (1 << SPIF))){};
  }
  pulse(XLAT_PORT, XLAT_PIN);
}

ISR(TIMER3_COMPA_vect) {
  static uint8_t xlatNeedsPulse = 0;
  
  setHigh(BLANK_PORT, BLANK_PIN);
  if (outputState(VPRG_PORT, VPRG_PIN)) {
    setLow(VPRG_PORT, VPRG_PIN);
    if (xlatNeedsPulse) {
      pulse(XLAT_PORT, XLAT_PIN);
      xlatNeedsPulse = 0;
    }
    pulse(SCLK_PORT, SCLK_PIN);
  } 
  else if (xlatNeedsPulse) {
    pulse(XLAT_PORT, XLAT_PIN);
    xlatNeedsPulse = 0;
  }
  setLow(BLANK_PORT, BLANK_PIN);
  // Below this we have 4096 cycles to shift in the data for the next cycle
  for (gsData_t i = 0; i < gsDataSize; i++) {
    SPDR = gsData[i];
    while (!(SPSR & (1 << SPIF))){};
  }
  xlatNeedsPulse = 1;
}

void setup(){
  Serial.begin(9600);
  TLC5940_Init();
  //*** for now keep this out and just hold DCPROG LOW using the default EEPROM value
  TLC5940_ClockInDC();

}

void loop() {
  
}

and here is the post I found with working SPI but no hardware CTC pin toggle

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1200704490

Thanks for any thoughts, and please feel free to point out anything I could clean up in my code :slight_smile: always trying to learn to code little better.

Have you seen the flow diagram of the feeding of the TLC5940, it might give you some pointers:-
http://focus.ti.com.cn/cn/lit/sw/slvc106/slvc106.pdf

Thanks for the link. Actually, the code I was working on is based off this tutorial.

Its actually an attempt to create a C library from that exact Flow diagram. I've managed to get the pins translated to the MEGA, and clock the TLC5940 off a Hardware CTC pin instead of the CLKO pin used in the tutorial. It all works up until I added the SPI :(. I'm probably missing something simple as I'm pretty new to working with AVRs at this level.

bump? Sorry to bump this so quick, but I'm heading towards a deadline for school and I'm pretty stuck on this one.

I got the problem figured out :slight_smile: Looks like the tutorial defined a BYTE using “0b”, when Arduino wants it to be defined as “B”. Also, looks like the inline function wasn’t pulsing the GSCLK PIN. I replaced it with the setHigh and setLow macros and alls good :slight_smile:

Here is the code. SPI @ 8MHZ with the 4096 GSCLK ticks from a hardware CTC.

#include <stdint.h>
// Definition of interrupt names
#include <avr/interrupt.h>
// ISR interrupt service routine
#include <avr/io.h>

// TLC5940 pin definitions
// MEGA PWM PIN 11
#define GSCLK 11
#define GSCLK_DDR DDRB
#define GSCLK_PORT PORTB
#define GSCLK_PIN PB5
// MEGA MOSI PIN 51
#define SIN 51
#define SIN_DDR DDRB
#define SIN_PORT PORTB
#define SIN_PIN PB2
// MEGA SCK PIN 52
#define SCLK 52
#define SCLK_DDR DDRB
#define SCLK_PORT PORTB
#define SCLK_PIN PB1
// MEGA PIN 49 
#define BLANK 49
#define BLANK_DDR DDRL
#define BLANK_PORT PORTL
#define BLANK_PIN PL0
// MEGA PIN 33
#define DCPRG 33
#define DCPRG_DDR DDRC
#define DCPRG_PORT PORTC
#define DCPRG_PIN PC4
// MEGA PIN 30
#define VPRG 30
#define VPRG_DDR DDRC
#define VPRG_PORT PORTC
#define VPRG_PIN PC7
// MEGA PIN 36
#define XLAT 36
#define XLAT_DDR DDRC
#define XLAT_PORT PORTC
#define XLAT_PIN PC1

// Additional SPI PIN defs (Not used but set)
// MEGA MISO PIN 50
#define DATAIN 50 
// MEGA SS PIN 53
#define SLAVESELECT 53  

#define TLC5940_N 1

// Macros
#define setLow(port, pin) ((port) &= ~(1 << (pin)))
#define setHigh(port, pin) ((port) |= (1 << (pin)))

#define outputState(port, pin) ((port) & (1 << (pin)))

#if (12 * TLC5940_N > 255)
#define dcData_t uint16_t
#else
#define dcData_t uint8_t
#endif

#if (24 * TLC5940_N > 255)
#define gsData_t uint16_t
#else
#define gsData_t uint8_t
#endif

#define dcDataSize ((dcData_t)12 * TLC5940_N)
#define gsDataSize ((gsData_t)24 * TLC5940_N)

// Array for Dot Correction Data
byte dcData[12 * TLC5940_N] = {
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
  B11111111,
};

// Array for GreyScale Data
byte gsData[24 * TLC5940_N] = {
  B11111111,
  B11110000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B10000000,
  B00010000,
  B00000010,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00000000,
  B00001111,
  B11111111,
};

void setup(){
  //************** SET PINS **************
  pinMode(GSCLK, OUTPUT);
  pinMode(SCLK, OUTPUT);
  pinMode(DCPRG, OUTPUT);
  pinMode(VPRG, OUTPUT);
  pinMode(XLAT, OUTPUT);
  pinMode(BLANK, OUTPUT);
  pinMode(SIN, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SLAVESELECT,OUTPUT);
  digitalWrite(SLAVESELECT,HIGH); //disable device

  setLow(GSCLK_PORT, GSCLK_PIN);
  setLow(SCLK_PORT, SCLK_PIN);
  setHigh(DCPRG_PORT, DCPRG_PIN);
  setHigh(VPRG_PORT, VPRG_PIN);
  setLow(XLAT_PORT, XLAT_PIN);
  setHigh(BLANK_PORT, BLANK_PIN);

  //************** SET SPI **************
  // Enable SPI, Master, set clock rate fck/2
  SPCR = (1 << SPE) | (1 << MSTR);
  SPSR = (1 << SPI2X);

  //  Clear SPI data Registers
  byte clr;
  clr=SPSR;
  clr=SPDR;
  delay(10);

  //************** SET TIMERS **************
  // Dont need to call sei(); because Arduino already does this
  // Clear TIMER1 Reg back to default
  TCCR1A = B00000000;
  TCCR1B = B00000000;
  // Enable timer 1 Compare Output channel A in toggle mode
  TCCR1A |= (1 << COM1A0);
  // Configure timer 1 for CTC mode
  TCCR1B |= (1 << WGM12);
  // Set up timer to fCPU (no Prescale) = 16Mhz/1 = 16Mhz
  TCCR1B |= (1 << CS10);
  // Set CTC compare value to pulse PIN at 8Mhz
  // (1 / Target Frequency) / (1 / Timer Clock Frequency) - 1
  // (1/16000000)/(1/16000000)-1 = 0 = 16Mhz
  // Full period of PIN 11 pulse requires 2 clock ticks (HIGH, LOW) 
  // So = f/2 = 16Mhz/2 = 8Mhz
  OCR1A = 0;

  // Clear TIMER3 Reg back to default  
  TCCR3A = B00000000;
  TCCR3B = B00000000;
  // Configure timer 3 for CTC mode
  TCCR3B |= (1 << WGM32);
  // Set up timer to fCPU (no prescale) = 16Mhz/1 = 16Mhz 
  TCCR3B |= (1 << CS30);
  // Set CTC compare value to 4096 full clock periods so 4096 @ 8Mhz
  // Hence (GSCLK value @ 16Mhz * 2 to bring it down to 8Mhz) -1
  OCR3A = (4096*2) - 1;
  // Enable Timer/Counter3 Compare Match A interrupt
  TIMSK3 |= (1 << OCIE3A);
 
  // initalize DC_Registers
  TLC5940_ClockInDC();
}

void TLC5940_ClockInDC(void) {
  setHigh(DCPRG_PORT, DCPRG_PIN);
  setHigh(VPRG_PORT, VPRG_PIN);

  for (dcData_t i = 0; i < dcDataSize; i++) {
    // Start transmission
    SPDR = dcData[i];
    // Wait for transmission complete
    while (!(SPSR & (1 << SPIF))){};
  }
  
  setHigh(XLAT_PORT, XLAT_PIN);
  setLow(XLAT_PORT, XLAT_PIN);
}

ISR(TIMER3_COMPA_vect) {
  static uint8_t xlatNeedsPulse = 0;

  setHigh(BLANK_PORT, BLANK_PIN);
  if (outputState(VPRG_PORT, VPRG_PIN)) {
    setLow(VPRG_PORT, VPRG_PIN);
    if (xlatNeedsPulse) {
      setHigh(XLAT_PORT, XLAT_PIN);
      setLow(XLAT_PORT, XLAT_PIN);
      xlatNeedsPulse = 0;
    }
    setHigh(SCLK_PORT, SCLK_PIN);
    setLow(SCLK_PORT, SCLK_PIN);
  } 
  else if (xlatNeedsPulse) {
    setHigh(XLAT_PORT, XLAT_PIN);
    setLow(XLAT_PORT, XLAT_PIN);
    xlatNeedsPulse = 0;
  }
  setLow(BLANK_PORT, BLANK_PIN);
  // Below this we have 4096 cycles to shift in the data for the next cycle
  for (gsData_t i = 0; i < gsDataSize; i++) {
    SPDR = gsData[i];
    while (!(SPSR & (1 << SPIF))){};
  }
  xlatNeedsPulse = 1;
}

void loop() {}