Arduino & TLC5940 Issue -- Fixed

Hi all,

I'm hoping someone can help to shed some light on an issue I am having. This is my first time using an arduino, and generally in interfacing with hardware as a whole, but I've done my best to try to debug my application and determine the root of the problem.

My code is based off of the code provided in this article:
http://www.arduino.cc/playground/Learning/TLC5940

With some adjustments due to it being somewhat dated. You can download the documentation for the TLC5940 IC here: http://www.ti.com/lit/gpn/tlc5940

First, what I am attempting to do: I will be sending data via host computer every 20ms, with new brightness information for the LEDs. With a required 192 bits to set all 16 channels individually, plus a control byte, this is well within the means of the serial communication. I initially thought my problem might've been with latency or loss of data here, but have moved all the code to the arduino to ensure it's not due to the serial communication.

Given the code linked above, two internal timers are used to drive the TLC5940. The first one pulses pin 11 every 2.44uS, in order to manage roughly 4096 cycles in 10ms -- this is the PWM clock that actually drives the LEDs. At the end of every 4096 cycles, the IC requires a pulse on its BLANK input, which resets its internal timer and begins another iteration at the given clock speed. Instead of trying to keep a counter for the first timer (and then pulse BLANK when counter == 4095), the author of the above code opted instead to drive a second timer that pulses in roughly the same amount of time as it takes the first timer to reach 4096 cycles (2.44uS * 4096, so it tries to do so every 9994 microseconds). Hopefully I've both interpreted the code clearly, and done the same for the explanation.

As far as all of that goes -- on the surface, it works great. There's no flickering of the LEDs once I've set their brightness, and the blank signal pulses in time, preventing the output from stopping. However, I doubt the code was intended for 50 updates/second, as I'm looking to do.

My problem is thus:
While sending data to adjust the brightness of the LEDs every 20ms, I will occasionally get the LEDs flickering at a higher brightness than any of the values I'm setting them to. To clarify, I'll use the actual code I'm using to reproduce this:

    for (uint16_t i = 0; i < 5000; i++) {
      if (i%2) {
        setGS(1);
      }
      else {
        setGS(16);
      }
      delay(20);
    }

setGS is the function used to actually write the value to the TLC5940's registers (I'll include the full test script at the bottom). I'm having the LEDs alternate between values of 1 and 16 every 20ms.. that is 1/4096th and 1/256th of the LEDs' actual potential brightnesses. Quite dim.

However, while watching this script run, for a fraction of a second the LEDs will occasionally light up what appears to be 100% brightness. There seems to be no reason nor rhyme to the rate at which this occurs. One run of the script can have it happen three times in 3 seconds, then another time 10 seconds later, whereas another run can have it not occur at all. I believe, though cannot be certain, that there are also occasional flickers at 0% brightness. Due to the nature of them, though, they are much harder for me to spot. Actually, I don't know why.. all I need to do is set both values to be up high, and see if it flickers low. Will test that now. .

.. okay. Aside from my eyes now seeing spots, I could discern no flicker at the lower end -- they only flicker up to 100%. Running them between 50/52% brightness, I could still see them flicker brighter. Running them at 95/97% or so brightness, I couldn't discern the flickering.

Some observations while trying to determine the cause:

  • It is not directly related to the fast speed. If I run it every 50ms, I still get flickering, but not as often -- at least, not as often in real time. Which is to be expected, however, as it's now running over twice as slow. 100ms, I can still get it to flicker. If I tried doing so at 1 second, I think it's possible that I could get the flickering to occur, but would take me some time to find out..

  • There's no software way for me to detect the flicker. I've tracked all of the variables right up until they're clocked into the IC, and they check out all the way through.

  • It only occurs when I alternate between different values. If I call the function to set the brightness to the same value every 20ms, even if the value is really low, I get zero flickering (and note that there's no code check to prevent an update to the same value). So, making it set the brightness to 1/4096 every 20ms, no flicker. Getting it to alternate between 1/4096 and 2/4096 every 20ms, flickering.

Reading over the documentation, I found this:
Grayscale data and dot correction data can be entered during a grayscale cycle. Although new grayscale data can be clocked in during a grayscale cycle, the XLAT signal should only latch the grayscale data at the end of the grayscale cycle. Latching in new grayscale data immediately overwrites the existing grayscale data.

I'm curious if this could be the cause. I say "I'm curious", because I can't sort out how to test this. The ideal solution would be to drop the timer for BLANK, and run a counter instead (or some sort of interrupt based on the first timer reaching a given value) -- but isn't keeping a counter too slow for the needed 2.44uS?

Whoops. Hit the character limit. Perhaps I'm being overly verbose, but I thought I'd try to be as accurate as possible in my initial post as I could manage.

So, I'm ultimately looking for both opinions on whether the XLAT issue could be the problem, and, if so, how I could re-implement the timer where I have control over what happens at the 4096 mark (pulse blank, check to see if GS data was set and needs to be latched in, if so, latch it, then continue).

But does it make sense for this to be the issue? If I am overwriting the brightness values when it's in the middle of a cycle, it -is- possible for me to technically change a value's brightness. How does that explain the 100% brightness when switching between 000000000001 and 000000000010 though? Even if I latched the new data in just after the cycle processed the '1' value from before, that'd give me a total brightness of 3/4096, nowhere near the intensity of the flash I see.

Here's the code I am using, the only real changes from the playground code being pin assignment, serial speed, fixing some timer register names, and the 'L' loop program at the bottom, which is what I was testing with.

Thanks for taking the time.

Andy

(Removed two non-related functions to help make it fit.)

 enum pins { xerr=2, sout=7, dcprg=2, xlat=3, sclk=4, dsin=5, vprg=6, blank=9, gsclk=11 };
 uint8_t needPulse = 0;
 int statusPin = 13;


uint16_t shift12(uint16_t v)
{
  uint16_t r=0;
  
  for (uint16_t m = 0x0800; m; m >>= 1) {
    digitalWrite(dsin, ((v&m) ? 1 : 0));
    //if ( digitalRead(sout)) r |= m;
    digitalWrite(sclk, 1);
    digitalWrite(sclk, 0);
  }

  return r;
}


void setGS(uint16_t v)
{
  
  digitalWrite(vprg,0);
  for ( uint8_t i = 0; i < 16; i++) {
    uint16_t r = shift12( v);
  }

  digitalWrite(xlat,1);
  digitalWrite(xlat,0);
  if ( needPulse) {
    digitalWrite(sclk,1);
    digitalWrite(sclk,0);
    // For some reason, it doesn't work right if I clear needPulse, my reading
    // of the datasheet says I should clear it, but then the bits get off.
    //needPulse = 0;
  }
}

void setup()
{
  uint16_t clocksPerGSCLK, clocksPerBLANK;
  pinMode(statusPin, OUTPUT);
  Serial.begin(38400);

  pinMode( xerr, INPUT);
  digitalWrite(xerr,1);    // pull up
  pinMode(sout, INPUT);
  pinMode(dcprg, OUTPUT);
  digitalWrite(dcprg,1);   // use DC register, EEPROM takes 22v to program, I ignore it
  pinMode(blank,OUTPUT);
  digitalWrite(blank,0);   // blank everything until ready
  pinMode(xlat,OUTPUT);
  digitalWrite(xlat,0);
  pinMode(sclk,OUTPUT);
  digitalWrite(sclk,0);
  pinMode(dsin,OUTPUT);
  pinMode(gsclk,OUTPUT);
  pinMode(vprg,OUTPUT);


  setDC(3);

   // Time for some math...
   // We will shoot for a 100Hz refresh rate on the PWM outputs, that is, one PWM cycle will
   // be 10 milliseconds long. This rate is chosen because it is fast enough not to flicker
   // to human eyes and also can be used as the refresh time of servos so the same code can be used
   // to position servos.
   // In order to have 4096 clocks in 10 milliseconds, each gsclk must be 2.44 microseconds.
   // The BLANK signal must be pulsed for each PWM refresh. The outputs will stop if blank
   // is not pulsed. Rather than try to precisely align the GSCLK and BLANK timers we will
   // just provide a little slop at the end of the cycle. BLANK will need to pulse, then remain off
   // for 4096 GSCLKs... 9995 microseconds. This will overflow the 16 bit timer at 16MHz, so we
   // will program the timer with a div8 prescaler.

  cli();
 #if defined(__AVR_ATmega168__)
  TCCR2A = 0;
  TCCR2B = 0;
 #else
  TCCR2 = 0;
 #endif
  // We turn Timer2 into a free running 400kHz clock for gsclk
  //Timer2, no interrupts 
 #if defined(__AVR_ATmega168__)
   TIMSK2 &= ~_BV(TOIE2);
  TIMSK2 &= ~_BV(OCIE2A);
 #else
  TIMSK &= ~_BV(TOIE2);
  TIMSK &= ~_BV(OCIE2);
 #endif
  clocksPerGSCLK = 624U*clockCyclesPerMicrosecond()/256U; // 624/256 is about 2.44 microseconds
  OCR2A = clocksPerGSCLK/2U-1U;  
      //the "-1" is because the count includes the top value, the /2 is for half cycles
  // Use internal clock - external clock not used in Arduino  
  ASSR &= ~(1<<AS2);  
 #if defined(__AVR_ATmega168__)
  TCCR2A = (1<<WGM21 /* CTC mode */ | 1<<COM2A0 /* toggle on match */);
  TCCR2B =  1<<CS20 /* no prescaler */;
 #else
  TCCR2 = (1<<WGM21 /* CTC mode */ | 1<<COM20 /* toggle on match,  */ | 1<<CS20 /* no prescaler */);
 #endif  

  TCCR1B = 0;  /* stop timer, and mess up a bunch of stuff we fix later */
  TCCR1A = _BV(COM1A1) | _BV(COM1A0) /* oc1a sets on match, clears at bottom */
           /* oc1b is a normal pin */
           | _BV(WGM11) /* WGM:1110, Fast PWM, top=ICR1 */
           ;  TCNT1 = 0;
  TCCR1B = _BV(WGM12) | _BV(WGM13); /* WGM:1110, Fast PWM, top=ICR1 */
  clocksPerBLANK = clocksPerGSCLK*(4096U/8U) + 2U;  
      // the 4096 is number of steps,  the /8 is the prescaler, +2 is fudge
  OCR1A = clocksPerBLANK; // Must set WGM first or the high bits are cleared. Bastards.
  ICR1 = clocksPerBLANK+clocksPerGSCLK/8 + 1U;  
      // make sure we span a GSCLK, that 10U is because 1U made things erratic, should have been legal though
 #if defined(__AVR_ATmega168__)
  TIMSK1 &= ~( _BV(ICIE1) | _BV(OCIE1A) | _BV(OCIE1B));
  TIFR1 &= ~ _BV(ICF1);
 #else
  TIMSK &= ~( _BV(TICIE1) | _BV(OCIE1A) | _BV(OCIE1B) | _BV(ICF1));
 #endif

  // this next line starts the timer again
  TCCR1B |= _BV(CS11) /* div8 scaled clock */;
  sei();

  //Serial.print((int)clocksPerGSCLK);
  //Serial.print(",");
  //Serial.print((int)clocksPerBLANK);
  //Serial.println();
}

 //
 // The test loop. Take commands from the serial port.
 // Type a string of digits to set the number.
 // 'D' puts the number into all of the DC registers.
 // 'G' puts the number into all of the grayscale registers.
 // The number is reset to 0 after a 'D' or 'G'.
 //
void loop()
{
  int ch;
  static uint16_t v = 0;

  switch( ch = Serial.read()) {
  case -1:
    return;
  case '0'...'9':
    v = v*16+ch-'0';
    break;
  case 'a'...'f':
    v = v*16+10+ch-'a';
    break;
  case 'D':
    //Serial.print("SetDC:");
    //Serial.print(v);
    //Serial.println();    
    setDC(v);
    v = 0;
    break;
  case 'G':
    Serial.print("SetGS:");
    Serial.print(v);
    Serial.println();
    setGS(v);
    v = 0;
    break;
  case 'N':
    needPulse = 1;
    break;
  case 'L':
    // Loop test to try to reproduce flickering brightness.
    for (uint16_t i = 0; i < 5000; i++) {
      if (i%2) {
        setGS(2000);
      }
      else{
        setGS(2050);
      }
      delay(20);
    }
  default:
    v = 0;
    break;
  }
}

I found this:
http://www.arduino.cc/playground/Code/Timer1

Fantastic library, that allows me to easily set a callback function for Timer1. Timer1 is what the original code used to pulse BLANK, so all I did was strip out that code, include TimerOne, and do:

  Timer1.initialize(9994);
  Timer1.attachInterrupt(toggleBlank);

Then, the toggleBlank function looks like so:

void toggleBlank() {
  if (needLatch) {
    digitalWrite(xlat, HIGH);
    digitalWrite(xlat, LOW);
    needLatch = 0;
  }
  digitalWrite(blank, HIGH);
  digitalWrite(blank, LOW);
}

And "needLatch" is set to true every time data is finished being shifted into the GS registers. I'll be rewriting the toggleBlank function to do direct port manipulation, as it's being run every 10ms and might at current be somewhat off, but as as a whole, this works flawlessly -- no more flickering. Fantastic.

Edit: Had some trouble with setting the brightness values -- the reason ended up being a good one. The "needPulse"-related code in the original (which the author admitted wasn't right) can now all also be dropped.