Using a 595 Shift Register and TLC5940 together

OK I have been trying to get an RGB LED Matrix running using the TLC5940 but as I only had 2 chips and really need 3 to multipliex an 8x8 array I thought of using a 595 shift register to control the common anode rows instead. With two TLC5940's I can then multiplex the 3 colours.

However, for some weird reason the column data gets carried down into the row below despite turning the row above off before displaying the next one. If I have a long delay inbetween rows it works fine but if I reduce the delay to a toy one I get the bleedover.

Here is some test code just to get the rows showing something different, but I am getting the column data showing up on the next row as well as on the one it is supposed to show on. Can anyone tell me why please :-

#include "tlc_config.h"
#include "Tlc5940.h"
//Pin connected to ST_CP of 74HC595
int latchPin = 2;
//Pin connected to SH_CP of 74HC595
int clockPin = 5;
////Pin connected to DS of 74HC595
int dataPin = 4;


void setup()
{
  /* Tlc.init() has to be called before using any of the library functions */
  Tlc.init();
  pinMode(latchPin, OUTPUT);

}

void loop()
{


    /*
     * Tlc.set(channel (0-15), value (0-4095)) sets the grayscale value for
     * one channel (15 is OUT15 on the first TLC, if multiple TLCs are daisy-
     * chained, then channel = 16 would be OUT0 of the second TLC, etc.).
     *
     * value goes from off (0) to always on (4095).
     */
   

    digitalWrite(latchPin, 0); 
    shiftOut(dataPin, clockPin, B10000000);   
    Tlc.clear();
    Tlc.set(0, 2095);
    Tlc.set(1, 2095);
    Tlc.set(2, 2095);
    Tlc.set(3, 2095);   
    Tlc.set(12, 2095);  
    Tlc.set(13, 2095); 
    Tlc.set(14, 2095); 
    Tlc.set(15, 2095); 
    Tlc.update();
    digitalWrite(latchPin, 1);
    delay(2);

 
    digitalWrite(latchPin, 0); 
    shiftOut(dataPin, clockPin, B01000000);   
    Tlc.clear();
    Tlc.set(0, 2095);
    digitalWrite(latchPin, 1);
    Tlc.update();
    delay(2);
    
    digitalWrite(latchPin, 0); 
    shiftOut(dataPin, clockPin, B00100000);   
    Tlc.clear();
    Tlc.set(1, 2095);
    Tlc.update();
    digitalWrite(latchPin, 1);
     delay(2);
    
    digitalWrite(latchPin, 0); 
    shiftOut(dataPin, clockPin, B00010000);   
    Tlc.clear();
    Tlc.set(2, 2095);
    Tlc.update();
    digitalWrite(latchPin, 1);
    delay(2);

}



void shiftOut(int myDataPin, int myClockPin, byte myDataOut) {
  // This shifts 8 bits out MSB first, 
  //on the rising edge of the clock,
  //clock idles low

  //internal function setup
  int i=0;
  int pinState;
  pinMode(myClockPin, OUTPUT);
  pinMode(myDataPin, OUTPUT);

  //clear everything out just in case to
  //prepare shift register for bit shifting
  digitalWrite(myDataPin, 0);
  digitalWrite(myClockPin, 0);

  //for each bit in the byte myDataOut[ch65533]
  //NOTICE THAT WE ARE COUNTING DOWN in our for loop
  //This means that %00000001 or "1" will go through such
  //that it will be pin Q0 that lights. 
  for (i=7; i>=0; i--)  {
    digitalWrite(myClockPin, 0);

    //if the value passed to myDataOut and a bitmask result 
    // true then... so if we are at i=6 and our value is
    // %11010100 it would the code compares it to %01000000 
    // and proceeds to set pinState to 1.
    if ( myDataOut & (1<<i) ) {
      pinState= 1;
    }
    else {      
      pinState= 0;
    }

    //Sets the pin to HIGH or LOW depending on pinState
    digitalWrite(myDataPin, pinState);
    //register shifts bits on upstroke of clock pin  
    digitalWrite(myClockPin, 1);
    //zero the data pin after shift to prevent bleed through
    digitalWrite(myDataPin, 0);
  }

  //stop shifting
  digitalWrite(myClockPin, 0);
}

Without the use of an oscilliscope I am guessing that the TLC library is doing whatever it does a lot slower than the 595 is and is therefore not clearing it's data before the next row is opened for use by the shift register?

I'll post a picture later today of what the array looks like when this code is running so you can see the problems I am experiencing.

If I understand correctly, you're trying to drive an 8x8 RGB led array with 2 tlc's (first 8 outputs red, second 8 blue, ...), by setting a row and switching rows really fast, essentially using persistence of vision to drive the entire grid. Awesome!

First let's make sure that the tlc library is fast enough. Each display cycle is 1/976.5625Hz, so each row will be updated at (976.5625/8) = 122Hz, which is plenty fast. The tricky part is how to update each row in exactly one display cycle. I haven't measured how long it takes to send in data using SPI, but bit-banging one tlc took ~260 microseconds (one display cycle is exactly 1024 microseconds). So we have >700uS to set up the data for the next row (SPI is faster).

The reason why your code doesn't work is because Tlc.update() sets the data for the next display cycle. Ideally, here's what the control flow would be:

  1. Start of display cycle
  2. Switch anode to next row
  3. Calculate/load data for the next next row (not the one we just switched to, but the next one)
  4. Send the data to the tlc's
  5. Wait till the end of display cycle
  6. (loop back to 1)
    Translated, we would have something like
#include "tlc_config.h"
#include "Tlc5940.h"

#define  ST_CP_DDR    DDRD
#define  ST_CP_PORT   PORTD
#define  ST_CP_PIN    PD2
#define  SH_CP_DDR    DDRD
#define  SH_CP_PORT   PORTD
#define  SH_CP_PIN    PD5
#define  DS_DDR       DDRD
#define  DS_PORT      PORTD
#define  DS_PIN       PD4

#define  pulse_pin(port, pin)  port |= (1 << (pin)); port &= ~(1 << (pin))

#define  RED_ON_LEVEL  2048
#define  GREEN_ON_LEVEL  2048

uint8_t row = 7; // 7 so the first pass through loop will be row = 0

inline void next_row() {
  // shift a byte out, MSB first
  uint8_t byte = (1 << row);
  for (uint8_t bit = (1 << 7); bit; bit >>= 1) {
    if (bit & byte) {
      DS_PORT |= (1 << DS_PIN);
    } else {
      DS_PORT &= ~(1 << DS_PIN);
    }
    // pulse clock
    pulse_pin(SH_CP_PORT, SH_CP_PIN);
  }
  // pulse latch
  pulse_pin(ST_CP_PORT, ST_CP_PIN);
  
  // switch rows
  if (row == 7) {
    row = 0;
  } else {
    row++;
  }
}

void setup() {
  // set the shift register pins to output
  ST_CP_DDR |= (1 << ST_CP_PIN);
  SH_CP_DDR |= (1 << SH_CP_PIN);
  DS_DDR |= (1 << DS_PIN);
  Tlc.init();
}

void loop() {
  // 1.  Start of display cycle
  // 2.  Switch anode to next row
  next_row();

  // 3.  Calculate/load data for the next next row (not the one we just switched to, but the next one)
  // let's display two circles
  // I'm assuming that outputs 0-7 are red, 8-15 are green
  Tlc.clear();
  switch (row) {
  case 0:
  case 7:
    Tlc.set(2, RED_ON_LEVEL);
    Tlc.set(3, RED_ON_LEVEL);
    Tlc.set(4, RED_ON_LEVEL);
    Tlc.set(5, RED_ON_LEVEL);
    break;
  case 1:
  case 6:
    Tlc.set(1, RED_ON_LEVEL);
    Tlc.set(6, RED_ON_LEVEL);
    break;
  case 2:
  case 5:
    Tlc.set(0, RED_ON_LEVEL);
    Tlc.set(7, RED_ON_LEVEL);
    Tlc.set(11, GREEN_ON_LEVEL);
    Tlc.set(12, GREEN_ON_LEVEL);
    break;
  case 3:
  case 4:
    Tlc.set(0, RED_ON_LEVEL);
    Tlc.set(7, RED_ON_LEVEL);
    Tlc.set(10, GREEN_ON_LEVEL);
    Tlc.set(13, GREEN_ON_LEVEL);
    break;
  }

  // 4.  Send the data to the tlc's
  Tlc.update();

  // 5.  Wait till the end of display cycle
  while (tlc_needXLAT);
}

Try that and see what happens. I want pictures!

Thanks for that. However, I have tried it and I get just one row lit up :-

Can you please post up ALL of the connections that I need so I can check them (inc. +5 and GND) as the connections in the example file and the ones in the documentation differ. Thanks.

Just a niggle, but at the very end you have:

while (tlc_needXLAT);

which hides the semi-colon. I always lay it out like this:

while (tlc_needXLAT)
   ;

to make the semi-colon obvious to the reader.

Ok, here's the tlc pin-out:

28 -> out 0
27 -> GND
26 -> SIN (Arduino pin 11)
25 -> SCLK (Arduino pin 13)
24 -> XLAT (Arduino pin 9)
23 -> BLANK (Arduino pin 10)
22 -> GND
21 -> +5V
20 -> 2K resistor -> GND
19 -> +5V
18 -> GSCLK (Arduino pin 3)
17 -> SOUT -> SIN of next TLC
16 ->
15 -> out 15
...
1 -> out 1

Try running the BasicUse with just 1 tlc plugged in, and don't use the shift register; just hook it up to the first row. (so +5V into the anode of all the first row leds). Download the library again (link). If you've made any changes to the config file, move it somewhere else, delete the library folder (/hardware/libraries/Tlc5940) and extract the library (so we have a clean version). If everything has been wired up correctly, it should cycle through all the leds in the first row.

Can you post a link for the led matrix datasheet?

OK I have connected it up exactly as listed above and ran BasicUse with row 1 connected to +5v and it successfully displays each R then G LED in turn.

I then try the code above and the LED's light up pretty much the same as in the photo above, with all LED's in the row displayed (though a mix of R & G and brightnesses).

D2 is connected to pin 12 on the 595
D4 to Pin 14
D5 to Pin 11

Hmm, try turning on the first led (Tlc.set(0, 4095))and cycling the rows with the shift register. I'll take a look at the shift register datasheet.

Has anyone had any luck figuring this issue out? I kind of want to do the same thing, but with only an 8x2 RGB matrix. I read somewhere that you could use a decade counter (4017) to set one row high at a time. Sounds more straightforward to me... and I honestly haven't ever used a shift register.

Anyway... I'm definitely not here to give my two cents, I just need a way to create scrolling/fading effects on an 8x2 RGB array without having to run a 50 pin cable.... 14 or 26 would be much nicer if I can find a multiplexing solution.

Hardware-wise, this is definitely possible. Software-wise it's also very doable if we work out a few minor kinks. I would be willing to write the code if you put the thing together.

Hello,

Is it possible to multiplex 2 TLCs with a 4017?
The decade counter (4017) turns one row high and the TLCs update the different columns. I guess the outputs of the 4017 need a transistor to supply the current for all the LEDs in the row.

Do you think that make sense? Is it doable with a NPN transistor?

Thank you guys!

i don't see any reasons why not. my going project is using 595 with NPN transistor with 6 tlc5941.