SPI With TLC5947 & Arduino Nano

Hi all!

I am currently troubleshooting how to program the TLC5947 24-channel 12-bit PWM LED driver using SPI protocol on a Nano. I am a noob to SPI, so it very well could be something that I’m not understanding.

The chip and breakout board I’m using are here: Adafruit 24-Channel 12-bit PWM LED Driver - SPI Interface [TLC5947] : ID 1429 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits

First- I don’t understand how tailor my LED brightness data to the SPI’s 8 or 16-bit word length, since each LED channel is 12-bit. Is there a way to set the length of SPI words to 12 bits at a time? The only other option I see would be to do bit math to break up the data such that each 12-bit channel has 8 of the bits stored in one word and then the other 4 in another, which would be shared in part with another 12-bit channel.

I want to be able to control brightness data on each of the 24 LEDs individually using Max MSP through USB. I’ve been able to do this with some amount of success using the tlc5947 library, but when I’ve written each individual LED to what should be its’ maximum value (4095), it looks dim. In fact, it seems like 255 is actually it’s brightest value, but I don’t understand why that would be since it isn’t 8-bit PWM.

I will post my code shortly. Any suggestions or input is much appreciated! :slight_smile:

Thanks,
Amina

Hi,
Welcome to the forum.

Please read the first post in any forum entitled how to use this forum.
http://forum.arduino.cc/index.php/topic,148850.0.html .
Then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Should have the information you need.

Tom... :slight_smile:

Below is the code I have thus far. Please note that because I’m only using one TLC5947 and am not receiving any data from it, there is no slave select or MISO line.

#include <SPI.h>

int LEDs[] = {4095, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4095};
SPISettings A(15000000, MSBFIRST, SPI_MODE0); //TLC5947 can accept 30MHz if individual device or 15MHz if in series with other devices.
#define SCLK 13
#define MOSI 11
int latch = 8;

void setup() {
Serial.begin(115200);
Serial.println("SPI TEST");

pinMode(latch, OUTPUT);

SPI.begin();
SPI.beginTransaction(A);

for(int i = 0; i < 24; i ++){             //For each LED channel
SPI.transfer(LEDs[i]);                    //Transfer first 8 bits of 12-bit data
SPI.transfer(LEDs[i] >> 8);};             //Transfer last 4 bits of 12-bit data
digitalWrite(latch, HIGH);
digitalWrite(latch, LOW);
};

void loop() {  
}

Also, just to be sure I have it right- because I’ve included the SPI library, do I not have to set the pin mode on the MOSI and SCLK lines as outputs?

Also Tom- the link you provided above appears to be broken for me. Can you repost it? :slight_smile:

aminafoxdye:
Also Tom- the link you provided above appears to be broken for me. Can you repost it? :slight_smile:

Fixed.... :slight_smile:

According to the TLC5947 datasheet it has a single 288-bit grayscale register used to control the brightness (and a shadow register, that is actually used for the brightness control).

Now for the 24 channels, each 12 bits you get 288 bits in total.
The spi can only transfer 8 bits, so divide the 288 by 8 and you get 36 bytes.

That being said, create an array of 36 bytes and put your brightness data into that. Following the first 12 bits, you directly append the next 12 bits and so on (not aligned to a byte boundary).
Then you can send the 36 byte array over to the TLC and pull the latch line to latch the new values in.

EDIT:
I don’t own a TLC5947, so I couldn’t test it with real hardware. I changed the latch pin to pin 10, because that is the slave select pin which has to be an output anyways (not sure, if the spi library does this automatically).

TLC5947 tlc(15000000, 10);

Create a new instance of the tlc device with a speed of 15MHz and the latch pin on pin 10.

tlc.begin();

Initialize the tlc device.

tlc.selectBrightness(23, 4095);

Set the brightness of channel 23 (channel numbers are from 0-23) to 4095 (maximum value). The allowed value range for the brightness is 0-4095.

tlc.updateBrightness();

Send the brightness values to the TLC5947 via spi.

Full working example:

#include <SPI.h>

class TLC5947
{
  private:
    uint8_t tlcGrayscaleData[36];
    SPISettings spiSettings;
    uint8_t latchPin;
  public:
    // Constructor. Needed for initializing the SPISettings instance
    TLC5947(uint32_t speed, uint8_t latchPin) : spiSettings(speed, MSBFIRST, SPI_MODE0), latchPin(latchPin) {
      
    }

    // Initializes the tlc
    void begin()
    {
      // Initialize the brightness to zero
      for(uint8_t i = 0; i < 36; i++) {
        tlcGrayscaleData[i] = 0;
      }

      pinMode(latchPin, OUTPUT);
      digitalWrite(latchPin, LOW);
      SPI.begin();
    }

    // Selects a brightness for the provided channel (0-23).
    // Brightness values can range from 0-4095 (12 bit)
    void selectBrightness(uint8_t channel, uint16_t brightness)
    {
      // Get the bit position of the channel
      uint16_t bitPosition = channel * 12;
      // Get the index of the channel in the tlc grayscale array
      uint8_t tlcGSArrayIndex = bitPosition / 8;

      // put the brightness in the array. 
      // For this, check if the channel is an even or uneven number
      if((channel & 0x1) == 0) {
        tlcGrayscaleData[tlcGSArrayIndex] = brightness & 0xFF;
        uint8_t temp = tlcGrayscaleData[tlcGSArrayIndex+1];
        temp &= 0xF0;
        temp |= (brightness >> 8) & 0x0F;
        tlcGrayscaleData[tlcGSArrayIndex+1] = temp;
      } else {
        uint8_t temp = tlcGrayscaleData[tlcGSArrayIndex];
        temp &= 0x0F;
        temp |= (brightness & 0x0F) << 4;
        tlcGrayscaleData[tlcGSArrayIndex] = temp;
        tlcGrayscaleData[tlcGSArrayIndex+1] = (brightness >> 4) & 0xFF;
      }
    }

    void updateBrightness()
    {
      SPI.beginTransaction(spiSettings);
      
      for(int8_t i = 35; i >= 0; i--){
        SPI.transfer(tlcGrayscaleData[i]);
      } 
      digitalWrite(latchPin, HIGH);
      digitalWrite(latchPin, LOW);
    }  
};

TLC5947 tlc(15000000, 10);

void setup() {
  // put your setup code here, to run once:
  tlc.begin();
  tlc.selectBrightness(23, 4095);
  tlc.selectBrightness(15, 2047);
  tlc.updateBrightness();
}

void loop() {
  // put your main code here, to run repeatedly:

}

aminafoxdye:
[...] but when I’ve written each individual LED to what should be its’ maximum value (4095), it looks dim. In fact, it seems like 255 is actually it’s brightest value, but I don’t understand why that would be since it isn’t 8-bit PWM.

A quick note on that: The brightness of an led does not change linearly with the pwm signal. So a 50% pwm signal does not correspond to 50% brightness. Read this article to achieve a linear relationship between the pwm-signal and the led's brightness.

Try the following sketch only for OUT0 channel. The brightness of 3-series LED should change by 10% at 1-sec interval.

//connect 3 LEDs in series with OUT0-pin biased by 5V
//intensity should chnage by 10% of Ch-0 at 1-sec interval
//connect SS/-pin of UNO with BLANK pin of TLC
//Connect DPin-13 (SCK) of UNO with SCLK pin of TLC
//connect DPin-11 (MOSI) of UNO with SIN pin of TLC
//connect DPin-8 of UNO with XTAL pin of TLC
//connevt GND pin of UNO with GND pin of TLC
//connect exteranl RIREF resistor between IREF-pin and GND of TLC; 
//check data sheet for value of RIREF

#include <SPI.h>
#define XTAL 8    //connect DPin-8 with XTAL pi of TLC
int x = 0x019A;   //GS data for 10% intensity
void setup()
{
  Serial.begin(115200);
  SPI.begin();   //default SCK (13) = 125 kHz; SS/-pin  (10) = HIGH; MOSI(11)
  Serial.println("SPI/TLC TEST");
  pinMode(XTAL, OUTPUT);
  digitalWrite(XTAL, LOW);
}

void loop()
{
  digitalWrite(SS, LOW);   //BLANK pin of TLC is LOW ' control is o PWM timing
  //-----increase intensity by 10% at every 1 sec----------
  SPI.transfer(highByte(x));  //data goes to GS register
  delayMicroseconds(80);      //8+ SCK delay
  SPI.transfer(lowByte(x));
  delayMicroseconds(80);      //data transfer delay = 8+ SCK period
  digitalWrite(XTAL, HIGH);   //data goes to GS latch and driver
  digitalWrite(XTAL, LOW);    
  //---------------------------------------
  x = x + 0x019A;   //brightness increases by 10%
  delay(1000);      //test interval
  digitalWrite(SS, HIGH);   //BLANK pin of TLC is HIGH to zero counter
}

Hi all,

Thanks for the suggestions thus far. I still haven’t written to the TLC5947 with SPI successfully, but for the time being I at least got it to work with shiftOut(), which I’ve had more experience with. I think now the next step would just be adapting it to SPI… The working code using shiftOut() is below. I started putting in SPI parameters but have slashed them out for the time being.

//#include <SPI.h>
//#define SCLK 5
//#define MOSI 12
//#define LATCH 10
//SPISettings A(15000000, MSBFIRST, SPI_MODE0);

int CLK = 8;
int DATA = 7;
int LATCH = 6;
int CAPTUREDATA;
int LED_VAL[24] = {4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0, 4095, 0};
int SPI_BYTEMSB[12];
int SPI_BYTEMID[12];
int SPI_BYTELSB[12];
byte SPI_WRITE[36];
int i;

//int RTC_init(){
//  SPI.begin(); 
//  SPI.setBitOrder(MSBFIRST);
//  SPI.setDataMode(SPI_MODE0);
//  SPI.transfer(0);
//}

void DATAPARSE(int *CAPTUREDATA){
  Serial.println("SPI DATA");

  for(int x = 0; x < 23; x += 2){
    SPI_BYTELSB[(x - 2) / 2] = ((LED_VAL[x + 1] & 4080) >> 4);                             //Takes 12-bit LED PWM values and parses them into 3 bytes.
    SPI_BYTEMID[(x - 2) / 2] = ((LED_VAL[x] >> 8) + ((LED_VAL[x + 1] & 15) << 4));         //One LED_VAL's 8 most significant bits are parsed into 1 byte.
    SPI_BYTEMSB[(x - 2) / 2] = LED_VAL[x] & 255;                                           //the 4 least significant bits are added to the prior LED_VAL's 4 most significant bits.
  };                                                                                       //Lastly, the prior LED_VAL's 8 least significant bits are parsed into their own byte.
  for(int h = 0; h < 36; h += 3){
    SPI_WRITE[h] = SPI_BYTEMSB[(h / 3) - 1];                                               //Adds the 3 bytes shared among each 2 LED_VALs to an array to be shifted out.
    SPI_WRITE[h + 1] = SPI_BYTEMID[(h / 3) - 1];
    SPI_WRITE[h + 2] = SPI_BYTELSB[(h / 3) - 1];
  };

for(int s = 0; s < 4; s ++){                                                               //Initializes the TLC5947- first four clock pulses are ignored.
  digitalWrite(CLK, HIGH);
  digitalWrite(CLK, LOW);};

for(int g = 0; g < 36; g += 1){
  shiftOut(DATA, CLK, LSBFIRST, SPI_WRITE[g]);};     
  digitalWrite(LATCH, HIGH);
  digitalWrite(LATCH, LOW);
};

void setup(){
Serial.begin(115200);
//SPI.begin();
pinMode(CLK, OUTPUT);
pinMode(DATA, OUTPUT);
pinMode(LATCH, OUTPUT);
digitalWrite(LATCH, LOW);

DATAPARSE(&CAPTUREDATA);
};

void loop(){}

I’m not sure if bit math was the best way to convert the 12-bit bytes of LED_VAL into 8-bit bytes, but it seems to have worked. Each 12-bit byte has 8 of it’s bits stored in one byte, and then 4 of each is stored into another byte- so each LED essentially occupies 1.5 bytes of data going into the TLC5947.

To be continued once I switch out the shiftOut() for the SPI library…