Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

This is an old example. It is recommended that you use the TLC5940 library instead.

The Ugly Part

I intend to make a nice library for this, but I have to handle some other code first, so I thought I'd just show you my test program. You can go from there.

The test program lets you set the DC and Greyscale registers from the serial input of your Arduino. You can send down the serial line:

  • A hexadecimal number... e.g. 1f
  • A 'D' to store the preceding number into each of the DC registers.
  • A 'G' to store the preceding number into each of the grayscale register.

For example: 3fDfffG will turn all the outputs on full current, full time (almost).

The Rough Edge

I am not getting the data out correctly. Rather, I can, but I have to deviate from my understanding of the datasheet to do so. You will see some oddness about the 'needPulse' variable related to that. In practice there isn't much useful in there. If you were making a stadium sized LED television you'd want the open circuit detectors. The thermal overload is the only other interesting bit.

Where to go from here

Notice the way the update cycles work... they shift in either a 6 or 12 bit value for each of the 16 channels. Shift in your own data values instead of the dummy ones. If you want to daisychain multiple TLC5940s then shift 16 times for each chip in the chain before strobing in the data.

And here is the great mound of ugly code

 //
 // If you are using Arduino-0007 or before you will need to...
 // #define clockCyclesPerMicrosecond() (16)
 //
 enum pins { xerr=2, sout, dcprg, xlat, sclk, sin, vprg, blank=9, gsclk=11 };
 uint8_t needPulse = 0;

uint8_t shift6(uint8_t v)
{
  uint8_t r=0;

  for (uint8_t m = 0x20; m; m >>= 1) {
    digitalWrite(sin, ((v&m) ? 1 : 0));
    if ( digitalRead(sout)) r |= m;
    digitalWrite(sclk, 1);
    digitalWrite(sclk, 0);

  }
  return r;
}

uint16_t shift12(uint16_t v)
{
  uint16_t r=0;

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

void setDC(uint8_t v)
{
  digitalWrite(vprg,1);
  for ( uint8_t i = 0; i < 16; i++) shift6(v);
  digitalWrite(xlat,1);
  digitalWrite(xlat,0);
  digitalWrite(vprg,0);

  needPulse = 1;
}

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

    Serial.print( (int) ((r&0xf00)>>8), HEX);
    Serial.print( (int) ((r&0xf0)>>4), HEX);
    Serial.print( (int) (r&0x0f), HEX);
    Serial.print(" ");
  }

  Serial.println();
  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;

  Serial.begin(9600);

  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(sin,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(TOIE2A);
  TIMSK2 &= ~_BV(OCIE2A);
 #else
  TIMSK &= ~_BV(TOIE2);
  TIMSK &= ~_BV(OCIE2);
 #endif
  clocksPerGSCLK = 624U*clockCyclesPerMicrosecond()/256U; // 624/256 is about 2.44 microseconds
  OCR2 = 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 + 10U;  
      // 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;
  default:
    v = 0;
    break;
  }
}