Skip to content
Ulrich Stern edited this page May 5, 2022 · 36 revisions

Tlc59711

An Arduino library for controlling TI's TLC59711 (or TLC5971).

Why?

There already is Adafruit's library, which I used initially. After working with and improving Adafruit's code for some time, little was left of the original code, and I decided to do a rewrite from scratch. The new library offers these advantages:

  • improved SPI reliability
    • on the Arduino Uno, the old library can cause incorrect values to be latched (used) by the TLC59711 even at 2 MHz SPI clock, while the new library works reliably at 8 MHz
  • significantly improved SPI performance
    • on the Arduino Uno, the speedup between the fastest data transfer modes of old (Adafruit) and new library is about 4.8 times (3.5 times for releases before v1.0.2)
  • test code with good code coverage and focus on reliability
  • avoids deprecated SPI library calls (making it, e.g., Teensy compatible)
  • wider API
    • data transfer modes have wider range and can be changed online
    • access to the TLC59711's global brightness control and TMGRST bit
    • convenience functions that set values for all chips and LEDs
  • fixed some minor issues

Our application

We use the TLC59711 and TLC5971 for high-throughput behavior-dependent optogenetic stimulation of Drosophila in my wife's neurobio lab, see the SkinnerTrax project on GitHub. Movie 3 on the lab web page shows SkinnerTrax in action, controlling a board with a Teensy 3.2, 8 TLC59711s, and 80 LEDs. (There are two cameras per board; the movie shows SkinnerTrax using only one of the two cameras. SkinnerTrax can handle multiple boards on a "regular" machine.)

API documentation

The fastest intro is the library's HelloWorld.ino example sketch. For more details, see the comments in Tlc59711.h and the library's test code (Tlc59711Test.ino) that uses most of the library.

Flickering and reliability

Flickering seems a common problem with PWM LED drivers, and three of the tests in Tlc59711Test.ino aim at making possible flickering and reliability issues visible. They were inspired by the discussion on this post and this thread on PJRC's Forum. For my initial setup (Arduino Uno, Adafruit boards), none of the data transfer modes used during the tests caused flickering. But the "SPI, 10 MHz, 16 bits at a time" transfer mode failed the "latch pulse" test (i.e., incorrect values can be latched), see the How does the TLC59711 know when a transfer is done? section below for why this can happen. I left the transfer mode in the code since, at some point, it was the fastest transfer mode for the Teensy 3.2 (and passed the "latch pulse" test).

Running the tests

See Running Tlc59711Test.

Data transfer speed

Data is transferred to the TLC59711 using the library's write() function either

  • with hardware support (see beginFast()) or
  • without (see beginSlow()).

The "with hardware support" option internally uses either the AVR's SPI port directly (since v1.0.2) or the SPI library and is referred to as "SPI" below. At 10 MHz SPI clock (the TLC59711's maximum), a bit should take as little as 0.1 μs. For the Arduino Uno, the hardware support can operate at an SPI clock of 8 MHz but not 10 MHz (the SPI library picks 8 MHz when 10 MHz are requested), making the theoretical minimum 0.125 μs per bit.

On the Arduino Uno, the fastest the library can transfer is 0.19 μs per bit (see below), so it is about a factor 1.5 off from the theoretical minimum. Adafruit's library needs about 0.93 μs per bit in its fastest mode (SPI with SPI_CLOCK_DIV2); the slower speed is due to its calling the SPI library once for every 8 bits and a compiler optimization issue. Also note that the difference between the fastest SPI option and the "no hardware support" option is 2 orders of magnitude. (The "no hardware support" option, which currently uses shiftOut() internally, could likely be sped up by an order of magnitude by directly accessing the AVR's I/O registers.)

testing Tlc59711 library
number chips: 2

test: timing 100 write() calls
xfer: SPI, 10 MHz, buffer...
  per write(): 86.7 us, per bit: 0.19 us
xfer: SPI, 10 MHz, 16 bits at a time...
  per write(): 148.0 us, per bit: 0.33 us
xfer: SPI, 2 MHz, buffer...
  per write(): 259.0 us, per bit: 0.58 us
xfer: no hardware support, interrupts...
  per write(): 8909.7 us, per bit: 19.89 us

On the Teensy 3.2, using transfer16() ("16 bits at a time" below) used to be faster than a buffer transfer (60.2 μs vs. 78.4 μs per write()), but when I reran Tlc59711Test using Teensyduino 1.47, both transfer modes got close to the theoretical minimum of 0.125 μs per bit: (The Teensy 3.2 may actually use an SPI clock closer to 10 MHz; I took a quick look at the SPI code, but the look was not long enough to be certain which code actually runs on the Teensy.)

test: timing 100 write() calls
xfer: SPI, 10 MHz, buffer...
  per write(): 56.8 us, per bit: 0.13 us
xfer: SPI, 10 MHz, 16 bits at a time...
  per write(): 59.2 us, per bit: 0.13 us
xfer: SPI, 2 MHz, buffer...
  per write(): 234.7 us, per bit: 0.52 us
xfer: no hardware support, interrupts...
  per write(): 626.5 us, per bit: 1.40 us

Here the Teensy 3.2 numbers for 20 MHz transfer rate, which the TLC5971 but not the TLC59711 supports, with postXferDelayMicros 1 (see below): (I switched to the TLC5971 for new designs for the faster transfer rate.)

test: timing 100 write() calls
xfer: SPI, 20 MHz, buffer...
  per write(): 35.7 us, per bit: 0.080 us
xfer: SPI, 20 MHz, 16 bits at a time...
  per write(): 39.7 us, per bit: 0.089 us

Finally, here Teensy LC numbers, given the Teensy 3.2 is currently (May 2022) hard to get and the Teensy 4.0 uses more power, for 20 MHz transfer rate with postXferDelayMicros 2:

test: timing 100 write() calls
xfer: SPI, 20 MHz, buffer...
  per write(): 64.6 us, per bit: 0.144 us
xfer: SPI, 20 MHz, 16 bits at a time...
  per write(): 80.3 us, per bit: 0.179 us

How does the TLC59711 know when a transfer is done?

The TLC59711 keeps track of the duration between the last two SCKI rising edges (time per bit). If it does not see a SCKI rising edge for 8 times this duration (and the MSB-side of its shift register has the command code 25h), a latch pulse is generated (see datasheet pg. 22). So it is important to

  • transfer the data for all cascaded TLC59711 chips with not too much variation in time per bit.
    • this is the reason the library disables interrupts via cli() during write() (see Tlc59711.cpp). Without hardware support, however, the Arduino Uno needs about 20 μs per bit (see above), which makes disabling interrupts less important. Moreover, 20 μs per bit is about 4.5 ms per chip, and disabling interrupts for more than 1 ms causes, e.g., millis() and micros() to be incorrect, see this Stack Exchange answer. This is why whether to allow interrupts is a parameter for beginSlow().
    • if there is a transfer delay when the command code (25h) happens to be in the six most significant bits of the shift register but the shift register does not yet have the correct values otherwise, a premature latch pulse results and the chip operates incorrectly.
      • for cascaded TLC chips, the data has a command code for each chip, and when transferring data to n chips with delays between chips, chip 1 (the one directly connected to the Arduino), for example, will first incorrectly latch chip n's data, then chip n-1's, ..., until it finally latches its own data. Chip 1 will not use incorrect data for too long with a fast transfer, but the incorrect values can still be visible; assume, say, that chip 1's LEDs are supposed to be off and all other chips are at 100%.
      • but even for a single TLC chip, premature latch pulses can occur if, say, the PWM values happen to include command codes.
        • this problem is demonstrated for Adafruit's library in LatchPulseTest.ino, which, for my setup, caused the problems described in the comment at the top of the file at an SPI clock of 2 MHz (Adafruit's default). I was initially surprised that one could get long enough delays to cause problems with Adafruit's library at a not-too-fast 2 MHz, but the first scope screenshot of this TI forum post shows that the delays between bytes are greater than 8 times the time per bit. (The delays between bytes would, of course, also cause the problems described in the previous bullet point for cascaded TLC chips.) I posted the issue on Adafruit's forum.
        • for the new library and my setup, of the transfer modes in Tlc59711Test.ino, only "SPI, 10 MHz, 16 bits at a time" did not pass the "latch pulse" test.
    • note that it is good to test your particular chips; the TLC59711's internal oscillator, which is used to measure the time per bit, can range from 7 MHz to 12 MHz, which can cause some variation in how soon a possible latch pulse is generated. (The shortest wait should occur if the internal oscillator is just slightly slower than the SPI clock.)
  • wait after the transfer if one wants to guarantee that the new values got latched (became active) by the time write() returns.
    • in the library, delayMicroseconds(postXferDelayMicros_) at the end of write() serves this purpose.
    • the default values for postXferDelayMicros in beginFast() and beginSlow() depend on the time per bit and possibly need (if the time per bit goes up) or should be adjusted. E.g., beginSlow()'s 200 μs is appropriate for the Arduino Uno (and, at about 10 times the time per bit, offers a good margin of safety), but 12 μs would be sufficient for the Teensy 3.2.

Electronics notes

Before designing my own boards and switching to the Teensy 3.2, I used Adafruit's TLC59711 boards and the Arduino Uno. While I like Adafruit's boards, their pin naming is "not optimal":

  • Adafruit's VCC is connected to the TLC's VREG (not the TLC's VCC)
  • Adafruit's V+ is connected to the TLC's VCC

In the following, I use the TLC's naming.

For my setup, I used the lower Application Circuit Example on page 2 of the datasheet, powering the TLCs directly from the Arduino Uno's 5V and using a VLED of 9V (with four 2.1V LEDs per channel). Note that not connecting VREG to VCC and 5V can damage the chip when using a 5V microcontroller: for an "open" (capacitor only) VREG and VCC = 5V, the TLC's internal linear regulator is active, VREG is 3.3V, the absolute maximum voltage on SDTI and SCKI according to the datasheet is VREG + 0.6V, and the datasheet states "Stresses beyond those listed under absolute maximum ratings may cause permanent damage to the device."

Adafruit actually initially got this wrong in their "learn" article for the board -- the article had VREG (board's VCC) connected to the Uno's 3.3V -- but the text has been fixed after I posted the issue on their forum, and it sounds like they will even revise their board.