This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TLC59711 flashing when using Arduino's hardware-enabled SPI

Other Parts Discussed in Thread: TLC59711

I am using the TLC59711 LED driver to drive four RGD LEDs with an Arduino Uno, and I am experiencing LEDs flashing off momentarily, and somewhat randomly, while receiving new grayscale levels over the built-in SPI protocol of Arduino (2 MHz).  I purchased this kit from Adafruit.com, used their library for the TLC597111, and followed the tutorial found here: http://learn.adafruit.com/tlc5947-tlc59711-pwm-led-driver-breakout/overview

Alternatively, if I manually bit-bang the SPI channels (much slower at 70 kHz), there is no flickering observed. Running the unmodified example works fine, but if I change the Adafruit_TLC59711 constructor to use the built-in SPI function instead of bit-banging, the sketch still runs, but the LEDs will flash off periodically (for periods on the order of 1-10 ms).

//This constructor bit-bangs the SPI commands
//Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711, clock, data);

//This constructor uses the hardware SPI protocol, and causes LEDs to flicker.
Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711);

Can someone help me understand why using the built-in SPI causes flickering? I'd like to use the built-in SPI because it's much faster (2 MHz vs. 70 kHZ).

Thanks for any advice you can offer.

Setup:
- I have 4 RGB LEDs connected to the board.
- Current limit resistor is 1.98k, which is a 25mA limit.
- Connections below (followed Adafruit tutorial http://learn.adafruit.com/tlc5947-tlc59711-pwm-led-driver-breakout
- SDTI -> Arduino Digital 11 (MOSI)
- SCKI -> Arduino Digital 13 (SCK)
- VCC -> Arduino 3.3v
- GND -> Arduino GND
- VLED -> Arduino VIN (=4.1V)
- No connections on Arduino pins 10 and 12 (SS and MISO).
- SPI is communicating at 2 MHz (16 MHz clock divided by 8 )
- SPI mode 0 (default in SPI.cpp)

I scoped the SPI lines during a write and verified that the timing seems correct:

Arduino - Adafruit example - write and config byte1 (built-in SPI).png
SPI transaction - Write and config bits (Built-in SPI)



This is what it looks like with the stock example method, which is essentially bit-banging the SDTI and SCKI pins manually:

Arduino - Adafruit example - write and config byte1 (bit-bang) annotated.png
SPI transaction - Write and config bits (Bit-banged SPI)


Note that the configuration register is defined as follows:
OUT_TMG = 1, EXT_GCK = 0, TMG_RST = 1, DSP_RPT = 1, BLANK = 0 --> 10110b
(see page 21 of the datasheet for more info about these register bits.)

As far as I can tell, whether the TLC59711's output is working properly without flickering, or the output is occasionally going low for one cycle, the hardware SPI transaction looks identical.  That is to say, I verified that the grayscale data sent was correct, and the output from the TLC59711 was still 0.  So I'm stumped. 

Has anyone seen this before?

Here is a link to the datasheet http://www.ti.com/lit/ds/symlink/tlc59711.pdf.
The important data concerning communication protocol is on page 12 (SDTI and SCKI signals).

 

Code snippets:


TLC59711test.ino (Adafruit example sketch )


/*************************************************** 
  This is an example for our Adafruit 12-channel PWM/LED driver

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/products/

  These drivers uses SPI to communicate, 2 pins are required to  
  interface: Data and Clock. The boards are chainable

  Adafruit invests time and resources providing this open source code, 
  please support Adafruit and open-source hardware by purchasing 
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.  
  BSD license, all text above must be included in any redistribution
 ****************************************************/

#include "Adafruit_TLC59711.h"
#include <SPI.h>

// How many boards do you have chained?
#define NUM_TLC59711 1

#define data   11
#define clock  13

//Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711, clock, data);
Adafruit_TLC59711 tlc = Adafruit_TLC59711(NUM_TLC59711);

void setup() {
  Serial.begin(9600);
  
  Serial.println("TLC59711 test");
  pinMode(10, OUTPUT);
  tlc.begin();
  tlc.write();
}

void loop() {
 
  rainbowCycle(5);
}


// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
  uint32_t i, j;

  for(j=0; j<65535; j+=10) { // 1 cycle of all colors on wheel
    for(i=0; i < 4*NUM_TLC59711; i++) {
      Wheel(i, ((i * 65535 / (4*NUM_TLC59711)) + j) & 65535);
    }
    tlc.write();
    delay(wait);
  }
}

// Input a value 0 to 4095 to get a color value.
// The colours are a transition r - g - b - back to r.
void Wheel(uint8_t ledn, uint16_t WheelPos) {
  if(WheelPos < 21845) {
    tlc.setLED(ledn, 3*WheelPos, 65535 - 3*WheelPos, 0);
  } else if(WheelPos < 43690) {
    WheelPos -= 21845;
    tlc.setLED(ledn, 65535 - 3*WheelPos, 0, 3*WheelPos);
  } else {
    WheelPos -= 43690;
    tlc.setLED(ledn, 0, 3*WheelPos, 65535 - 3*WheelPos);
  }
}

Adafruit_TLC58711.cpp library

/*************************************************** 
  This is a library for our Adafruit 24-channel PWM/LED driver

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/products/1455

  These drivers uses SPI to communicate, 3 pins are required to  
  interface: Data, Clock and Latch. The boards are chainable

  Adafruit invests time and resources providing this open source code, 
  please support Adafruit and open-source hardware by purchasing 
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.  
  BSD license, all text above must be included in any redistribution
 ****************************************************/


#include <Adafruit_TLC59711.h>
#include <SPI.h>

Adafruit_TLC59711::Adafruit_TLC59711(uint8_t n, uint8_t c, uint8_t d) {
  numdrivers = n;
  _clk = c;
  _dat = d;

  BCr = BCg = BCb = 0x7F;

  pwmbuffer = (uint16_t *)calloc(2, 12*n);
}

Adafruit_TLC59711::Adafruit_TLC59711(uint8_t n) {
  numdrivers = n;
  _clk = -1;
  _dat = -1;

  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  SPI.setDataMode(SPI_MODE0);
  BCr = BCg = BCb = 0x7F;

  pwmbuffer = (uint16_t *)calloc(2, 12*n);
}

void  Adafruit_TLC59711::spiwriteMSB(uint32_t d) {

// Bit-Banging method if (_clk >= 0) { uint32_t b = 0x80; // b <<= (bits-1); for (; b!=0; b>>=1) { digitalWrite(_clk, LOW); if (d & b) digitalWrite(_dat, HIGH); else digitalWrite(_dat, LOW); digitalWrite(_clk, HIGH); } } else {
// Arduino's hardware enabled SPI: SPI.transfer(d); } } void Adafruit_TLC59711::write(void) { uint32_t command; // Magic word for write command = 0x25; command <<= 5; //OUT_TMG = 1, EXT_GCK = 0, TMG_RST = 1, DSP_RPT = 1, BLANK = 0 -> 0x16 //OUT_TMG = 1, EXT_GCK = 0, TMG_RST = 0, DSP_RPT = 1, BLANK = 0 -> 0x12 //OUT_TMG = 0, EXT_GCK = 0, TMG_RST = 0, DSP_RPT = 1, BLANK = 0 -> 0x02 command |= 0x16; command <<= 7; command |= BCb; command <<= 7; command |= BCg; command <<= 7; command |= BCb; cli(); for (uint8_t n=0; n<numdrivers; n++) { spiwriteMSB(command >> 24); spiwriteMSB(command >> 16); spiwriteMSB(command >> 8); spiwriteMSB(command); // 12 channels per TLC59711 for (int8_t c=11; c >= 0 ; c--) { // 16 bits per channel, send MSB first spiwriteMSB(pwmbuffer[n*12+c]>>8); spiwriteMSB(pwmbuffer[n*12+c]); } } if (_clk >= 0) { //digitalWrite(_dat, LOW); delayMicroseconds(200); } else delayMicroseconds(2); sei(); } void Adafruit_TLC59711::setPWM(uint8_t chan, uint16_t pwm) { if (chan > 12*numdrivers) return; pwmbuffer[chan] = pwm; } void Adafruit_TLC59711::setLED(uint8_t lednum, uint16_t r, uint16_t g, uint16_t b) { setPWM(lednum*3, r); setPWM(lednum*3+1, g); setPWM(lednum*3+2, b); } void Adafruit_TLC59711::setLED(uint8_t lednum, uint16_t rgb[3]) { setPWM(lednum*3, rgb[0]); setPWM(lednum*3+1, rgb[1]); setPWM(lednum*3+2, rgb[2]); } boolean Adafruit_TLC59711::begin() { if (!pwmbuffer) return false; if (_clk >= 0) { pinMode(_clk, OUTPUT); pinMode(_dat, OUTPUT); } else { SPI.begin(); } return true; }


Arduino's SPI.cpp

/*
 * Copyright (c) 2010 by Cristian Maglie <c.maglie@bug.st>
 * SPI Master library for arduino.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#include "pins_arduino.h"
#include "SPI.h"

SPIClass SPI;

void SPIClass::begin() {

  // Set SS to high so a connected chip will be "deselected" by default
  digitalWrite(SS, HIGH);

  // When the SS pin is set as OUTPUT, it can be used as
  // a general purpose output port (it doesn't influence
  // SPI operations).
  pinMode(SS, OUTPUT);

  // Warning: if the SS pin ever becomes a LOW INPUT then SPI
  // automatically switches to Slave, so the data direction of
  // the SS pin MUST be kept as OUTPUT.
  SPCR |= _BV(MSTR);
  SPCR |= _BV(SPE);

  // Set direction register for SCK and MOSI pin.
  // MISO pin automatically overrides to INPUT.
  // By doing this AFTER enabling SPI, we avoid accidentally
  // clocking in a single bit since the lines go directly
  // from "input" to SPI control.  
  // http://code.google.com/p/arduino/issues/detail?id=888
  pinMode(SCK, OUTPUT);
  pinMode(MOSI, OUTPUT);
}


void SPIClass::end() {
  SPCR &= ~_BV(SPE);
}

void SPIClass::setBitOrder(uint8_t bitOrder)
{
  if(bitOrder == LSBFIRST) {
    SPCR |= _BV(DORD);
  } else {
    SPCR &= ~(_BV(DORD));
  }
}

void SPIClass::setDataMode(uint8_t mode)
{
  SPCR = (SPCR & ~SPI_MODE_MASK) | mode;
}

void SPIClass::setClockDivider(uint8_t rate)
{
  SPCR = (SPCR & ~SPI_CLOCK_MASK) | (rate & SPI_CLOCK_MASK);
  SPSR = (SPSR & ~SPI_2XCLOCK_MASK) | ((rate >> 2) & SPI_2XCLOCK_MASK);
}


Arduino's SPI.h

/*
 * Copyright (c) 2010 by Cristian Maglie <c.maglie@bug.st>
 * SPI Master library for arduino.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of either the GNU General Public License version 2
 * or the GNU Lesser General Public License version 2.1, both as
 * published by the Free Software Foundation.
 */

#ifndef _SPI_H_INCLUDED
#define _SPI_H_INCLUDED

#include <stdio.h>
#include <Arduino.h>
#include <avr/pgmspace.h>

#define SPI_CLOCK_DIV4 0x00
#define SPI_CLOCK_DIV16 0x01
#define SPI_CLOCK_DIV64 0x02
#define SPI_CLOCK_DIV128 0x03
#define SPI_CLOCK_DIV2 0x04
#define SPI_CLOCK_DIV8 0x05
#define SPI_CLOCK_DIV32 0x06
//#define SPI_CLOCK_DIV64 0x07

#define SPI_MODE0 0x00
#define SPI_MODE1 0x04
#define SPI_MODE2 0x08
#define SPI_MODE3 0x0C

#define SPI_MODE_MASK 0x0C  // CPOL = bit 3, CPHA = bit 2 on SPCR
#define SPI_CLOCK_MASK 0x03  // SPR1 = bit 1, SPR0 = bit 0 on SPCR
#define SPI_2XCLOCK_MASK 0x01  // SPI2X = bit 0 on SPSR

class SPIClass {
public:
  inline static byte transfer(byte _data);

  // SPI Configuration methods

  inline static void attachInterrupt();
  inline static void detachInterrupt(); // Default

  static void begin(); // Default
  static void end();

  static void setBitOrder(uint8_t);
  static void setDataMode(uint8_t);
  static void setClockDivider(uint8_t);
};

extern SPIClass SPI;

byte SPIClass::transfer(byte _data) {
  SPDR = _data;
  while (!(SPSR & _BV(SPIF)))
    ;
  return SPDR;
}

void SPIClass::attachInterrupt() {
  SPCR |= _BV(SPIE);
}

void SPIClass::detachInterrupt() {
  SPCR &= ~_BV(SPIE);
}

#endif

 

 

 

  • Hello Dan,

    What is most likely your issue that if you are using random pieces of wire to connect the Arduino to the TLC597111 and trying to run a 2MHz SPI that you are seeing a high error rate in your data transfer rate which might be causing your flickering. This could be why you do not see it in your 70kHz bit banging solution.

    I would recommend going into you SPI.h file and reducing the clock frequency. I would comment out of the clock divider values except the 128 divider and then see if it works. Then if you want to have it communicate faster I would then recommend trying progressively smaller dividers until you start to see the flicker again and then scale increase the divider by one step.

  • Ryan,

    Thanks for the reply.  I tried the SPI_CLOCK_DIV128 and experienced a similar issue.  In fact, this time the TLC59711 actually froze up and stopped responding to incoming SPI commands.  Resetting the arduino didn't help; I had to power cycle the TLC59711 in order to get it to start working again.

    I also scoped the signal at the LED driver side and there doesn't appear to be any noise.  Edges look crisp.

    Does it matter that the clock is high when idle?

  • Hello Dan,

    Looking at the first code snippet I see the #defines for the data & clock. I assume that this defines the pins, right?

    It looks like the Clock divider is defined in two places. There is the compiler define in the header header file but the actual declaration of the divider occurs in Adafruit_TLC58711.cpp on line 38. The previous comment that I made before was incorrect. Please reverse the commenting that I recommended in the previous post and change the code in this place. This is actually where the SPI data rate is defined.

    Let me know if this makes a difference. Otherwise I will have to look at things a little bit differently.

  • I understood you correctly the first time.  I changed line 38 of Adafruit_TLC59711.cpp to SPI.setClockDivider(SPI_CLOCK_DIV128) and still experienced the problem.

    I just changed the SPI mode to mode 3 and seem to be getting better results (no flickering or freezing), which puzzles me because I was sure that I have seen issues with this method too.  I only had a chance to test quickly though.  I'll do more thorough testing on Monday and post with the results.

    Thanks for the help.

  • Did this ever get resolved? I'm having the same problem and it's driving me crazy. The whole reason I'm using a 16-bit PWM driver is so that I can have smooth fades. The flickering pretty much makes that pointless.

    Thanks,

    Andrew Hughes

  • Hi Andrew,

    It's been a while since I worked on this project, but I think it was resolved simply by switching the SPI mode from 0 to 3.  The four modes are for specifying clock polarity (idle high or idle low), and clock phase (whether data is read on the rising or falling edge of the clock).   According to the datasheet for the TLC59711, it appears that mode 0 is correct (idle low, rising edge), but I had success by using mode 3 (idle high, rising edge).  The datasheet doesn't say the mode number outright, but you can look at the timing diagrams on page 11 (first two lines) to see what it's looking for.

    My advice would be to experiment with all the different SPI modes (http://www.arduino.cc/en/Reference/SPISetDataMode) and try different speeds.

    By the way, I also posted about this topic in the adafruit forum (http://forums.adafruit.com/viewtopic.php?f=47&t=48075

    -Dan

  • I think I've finally fixed this for me. It boiled down to a clock problem. Teensy was sending serial data too fast, and the Adafruit library has the lines below which are supposed to set the clock speed and other serial options but weren't actually doing anything.

    SPI.setBitOrder(MSBFIRST);
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    SPI.setDataMode(SPI_MODE0);

    The library for the TLC59711 references old SPI code. I think the clock settings, the MSB settings, and the phase settings were basically being ignored. I updated my TLC library to take into account the new transactional SPI code as show on this page: [url]www.pjrc.com/.../td_libs_SPI.html[/url].

    And set my settings to: SPISettings SPI_SETTINGS(1000000, MSBFIRST, SPI_MODE0);

    I also had to put "SPI.beginTransaction(SPI_SETTINGS)" at the beginning of the Adafruit_TLC59711::write() method, and put a matching "SPI.endTransaction()" at the end of the method. See below.

    No more flickering or glitchy fades.

    Best -- Andrew

    SPISettings SPI_SETTINGS(1000000, MSBFIRST, SPI_MODE0);

    void Adafruit_TLC59711::write(void) {

    SPI.beginTransaction(SPI_SETTINGS);

    uint32_t command;

    // Magic word for write
    command = 0x25;
    command <<= 5;

    //OUTTMG = 1, EXTGCK = 1, TMGRST = 1, DSPRPT = 0, BLANK = 0 -> 0x16
    command |= 0x16;

    command <<= 7;
    command |= BCr;

    command <<= 7;
    command |= BCg;

    command <<= 7;
    command |= BCb;

    cli();
    for (uint8_t n=0; n<numdrivers; n++) {
    spiwriteMSB(command >> 24);
    spiwriteMSB(command >> 16);
    spiwriteMSB(command >> 8);
    spiwriteMSB(command);

    // 12 channels per TLC59711
    for (int8_t c=11; c >= 0 ; c--) {
    // 16 bits per channel, send MSB first
    spiwriteMSB(pwmbuffer[n*12+c]>>8);
    spiwriteMSB(pwmbuffer[n*12+c]);
    }
    }

    if (_clk >= 0)
    delayMicroseconds(200);
    else
    delayMicroseconds(2);

    SPI.endTransaction();

    sei();

    }

  • Hi Dan,

    FYI, even though you seem to no longer work on this...

    For my setup (Arduino Uno, Adafruit boards), SPI_MODE0 worked even at 8MHz but I used different wiring from you since I noticed wiring bugs in Adafruit's "learn" article, see my post to their forum (forums.adafruit.com/viewtopic.php In a nutshell, with your wiring, the TLC59711 was operating at 3.3V, but was getting 5V signals, which according to the datasheet is "stress beyond the absolute maximum ratings."  It is possible this was the root cause for your problem.

    In addition, Adafruit's library has a bug, see my second post to their forum (forums.adafruit.com/viewtopic.php My hunch is this bug was not what caused your problem.  But I referred to your scope screenshot above explaining the bug (see github.com/.../wiki, so I thought you may be interested.

    Cheers,

    Uli