Reduce flicker of TLC5947?

I created a simple fade program to fade in and out RGB leds using a tlc5947. I notice quite a bit of flickering, even with 512 levels of greyscale.

probably in your code, mind posting it (don't forget to use the # icon in the forum post editor)

I created a LEDDriver class. I have some #ifdefs in the code for testing. When I compile using XCode I define TESTING. This enables code that outputs data to a file for debugging on the PC before uploading to the arduino. Befor eI upload to the arduino I undefine TESTING so that code won't execute.

I based the code off the tlc5947's datasheet and the octobright code. It contains two arrays: temp_data[NUM_PINS] and led_data[NUM_PINS]. led_data gets pushed out to the tlc. temp_data gets pushed to led_data if it is different than led_data. This way I am not pushing led_data to the tlc if I don't need to.

LEDDriver.h

#ifndef LEDDriver_H
#define LEDDriver_H

#include <stdint.h>
#include "led_config.h"

#ifdef TESTING
#include <iostream>  //remove for AVR
#include <fstream>
using namespace std;
#endif

class LEDDriver 
{
  public:
    LEDDriver();
    ~LEDDriver();
      
    void SetPins(uint8_t pClock_pin, uint8_t pData_pin, uint8_t pBlank_pin, uint8_t pLatch_pin);
            
    void Update();  //this should set pLED_data to tempLedData, then compare with led_data to see if need to update.
                //if need to update then update led_data and SetLatch().  This will minimize flickering.  Only latch if needed.
      
    void SetClock();
    void SetLatch();
            
    void BlankToggle();
    void ClearLEDS();
            
    void SetLED(uint16_t led, uint16_t red, uint16_t green, uint16_t blue);
            
  private:
#ifdef TESTING
    ofstream fp_out;
#endif

    uint16_t temp_data[NUM_PINS];
    uint16_t led_data[NUM_PINS];
            
    uint8_t blank_status;
            
    uint8_t clock_pin;
    uint8_t data_pin;
    uint8_t blank_pin;
    uint8_t latch_pin;
      
    void _WriteChannel(uint16_t value);
    void _UpdateLEDS();
};
#endif

LEDDriver.cpp

#include "led_config.h"
#include "LEDDriver.h"

#include "wiring.h"
#include "HardwareSerial.h"

#ifdef TESTING
#include <iostream>  //remove for AVR
#include <fstream>

using namespace std;
#endif

LEDDriver::LEDDriver()
{
  blank_status = LOW;
#ifdef TESTING
  fp_out.open("myfile.txt");
#endif
}

LEDDriver::~LEDDriver()
{
#ifdef TESTING
  fp_out.close();
#endif
}

void LEDDriver::SetPins(uint8_t pClock_pin, uint8_t pData_pin, uint8_t pBlank_pin, uint8_t pLatch_pin)
{
  clock_pin = pClock_pin;
  data_pin = pData_pin;
  blank_pin = pBlank_pin;
  latch_pin = pLatch_pin;
}

void LEDDriver::Update()
{
  //compare with led_data to see if need to update.
  //if need to update then update led_data and SetLatch().  This will minimize flickering.  Only latch if needed.
  uint8_t updateleds = 0;
  int i = 0;
      
  //Check if we need updating first TODO
  //The function shoudl transfer tempData if needed
      
  for(i = (NUM_PINS - 1); i >= 0; i--)
  {
    if(led_data[i] != temp_data[i])
    {
      updateleds++;
      led_data[i] = temp_data[i];
    }
  }
      
  //Check if leds need to be updates then push the update
  if(updateleds > 0)
  {
#ifdef TESTING
    fp_out << "  Update " << endl;
#endif
#ifdef SERIAL_DEBUG
    Serial.println(" Update ");
#endif
    _UpdateLEDS();
    SetLatch();
  }
#ifdef TESTING
  else
  {
    fp_out << "  No update " << endl;
  }
      
  for(i = (NUM_PINS - 1); i >= 0; i--)
  {
    fp_out << "l " << i << ": " << led_data[i] << endl;
  }
  fp_out << flush;
#endif
}

void LEDDriver::SetClock()
{
  digitalWrite(clock_pin, HIGH);
  digitalWrite(clock_pin, LOW);
}

void LEDDriver::SetLatch()
{
  digitalWrite(latch_pin, HIGH);
  delay(1);
  digitalWrite(latch_pin, LOW);
}

void LEDDriver::ClearLEDS()
{
  for(int i = (NUM_PINS - 1); i >= 0; i--)
  {
    temp_data[i] = 0;
  }
}


void LEDDriver::BlankToggle()
{
  if(blank_status == LOW)
  {
    blank_status = HIGH;
   }
  else
  {
    blank_status = LOW;
  }
}

void LEDDriver::SetLED(uint16_t led, uint16_t red, uint16_t green, uint16_t blue)
{
  temp_data[(led*PINS_PER_LED)+RED] = red;
  temp_data[(led*PINS_PER_LED)+GREEN] = green;
  temp_data[(led*PINS_PER_LED)+BLUE] = blue;
}

void LEDDriver::_WriteChannel(uint16_t value)
{
  int bit;
      
  // Write value, MSB first
  for (bit=11; bit>=0; --bit)
  {
    if (value & (1<<bit))
    {
      digitalWrite(data_pin, HIGH);
    }
    else
    {
      digitalWrite(data_pin, LOW);
    }
            
    // We need to wait 30ns after writing data before clocking it in.
    // Fortunately, our AVR is slow enough that we don't need to
    // do an explicit wait here
    digitalWrite(clock_pin, HIGH);
    digitalWrite(clock_pin, LOW);
    }
}


void LEDDriver::_UpdateLEDS()
{
  //Check if we need updating first TODO
  //The function should transfer tempData if needed
  for(int i = (NUM_PINS - 1); i >= 0; i--)
  {
    _WriteChannel(led_data[i]);

#ifdef SERIAL_DEBUG
     Serial.print("write led pin ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(led_data[i]);
#endif
            
#ifdef TESTING
    fp_out << "write led pin " << i << ": " << led_data[i] << endl;
    fp_out << flush;
#endif
  }
  SetClock();
}

The TLC5947 has its own PWM oscillator, so you can have things happening at different times during a PWM cycle. Just like the ShiftBrite, I find that pulsing the BLANK signal along with the LATCH signal restarts the PWM clock and reduces flickering.

You might also double check the code that decides how data gets shifted out to the chain, for a while there I had some code that had the bytes messed up so it was no better than 4 bit resolution and looked sort of fine until I did some more critical testing.

So this? I'm not a hardware guy, I'm a programmer. Most of what you said didn't make sense.

void LEDDriver::SetLatch()
{
digitalWrite(blank_pin, HIGH);
digitalWrite(latch_pin, HIGH);
delay(1);
digitalWrite(blank_pin, LOW);
digitalWrite(latch_pin, LOW);
}

I gave that modification to the code a shot. Wow, that worked perfectly. Well, there's a slight flicker but I only see it in my side vision.

You could probably eliminate that faint flicker by changing the delay(1) statement to delayMicroseconds(10) or so. It'll have the same effect but turn off the LEDs for a much shorter time.

I had just ordered some samples of this chip to try it, since it looked like with an internal oscilator it could fix the difficulties with the TLC5940 to get the timing correct and update the values.

However, reading the datasheet, it looks like there's still a problem loading new values to it, since it turns off the outputs at the moment you finished updating the values.
Does anyone know why they did it this way instead of doing the latch at the moment the timer resets to 0?

To get the timing close to perfect from what I understand you'd still have to have a timer running on your Arduino, which could run at 1kHz, and do the latch/blank thing there whenever you updated the values.
Blanking every time would probably lose you some max brightness (since it stays off for at least 5 cycles after a blank).
On the other hand, when you don't do a lot of updates for a while, the clock on the Arduino might drift from the TLC's oscilator.