Timing issues with RGB Display - TLC5940 and 74HC595

Hello, I am working on a project that will culminate in a 16x10 RGB display. As a proof of concept, I have built a small 4x4 display to test my multiplexing technique, and I am running into some problems that I hope you guys can help me out with:

The display consists of four rows of four columns each of common-anode RGB LEDs (anodes = columns), with the rows driven by a TLC5940. The columns are driven HIGH in sequence by a 74HC595 shift register. Here's my algorithm for pushing data to the display:

  • Clear TLC and blank it
  • Send data for rows in column n to TLC
  • Call Tlc.update(), pushing the actual data to the chip but keeping it blanked
  • Set column n driven HIGH with the 595
  • Un-blank the TLC, turning on the LEDs.

I am getting flickering lights, as well as some "bleed" from one column into the next; that is, I can see green elements in the blue column, and red in the green, etc. My theory is that the TLC is not changing the row data as fast as is necessary, and is therefore "bleeding" into the next column. When I slow down the switching (when COLOR_SWITCH_TIME is increased) to the point where I can easily follow the transitions, all the colors are vibrant and clear and there is no bleed, so I feel my theory is supported. Of course, when it is increased to the point where POV makes the entire matrix appear illuminated, the problem returns.
Here is my relevant code so far:

/*
4X4 RGB LED matrix using a single TLC5940 to control the 4 rows and one 74HC595 shift register to control the columns
Incorporates Adam Leone's TLC5940 library: http://code.google.com/p/tlc5940arduino/
*/
#define COLOR_SWITCH_TIME 5
void loop()
{



	for (int i = 0; i < 4; i++)
	{
		
		//transition();
		for(int j = 0; j < 4; j++)
		{
			//row data array for "R-G-B-White" display sequence
			int rSeq[] = {4095,0,0,4095};
			int gSeq[] = {0,4095,0,4095};
			int bSeq[] = {0,0,4095,4095};
			SetRow(j,rSeq[i],gSeq[i],bSeq[i]);
		}
		digitalWrite(10,HIGH);//Blank the TLC
		SetColumn(i);
		Tlc.update();
		digitalWrite(10,LOW);//un-blank it
		
		delay(COLOR_SWITCH_TIME);

	}

}

//Sends row data to the TLC but DOES NOT update
void SetRow(int row, int r, int g, int b)
{
	int startChannel = row*3;
	Tlc.set(startChannel,r);
	Tlc.set(startChannel+1,g);
	Tlc.set(startChannel+2,b);
}

void SetColumn(int column)
{
	digitalWrite(latchPin,LOW);//release the columns
	shiftOut(dataPin,clockPin,LSBFIRST,columns[column]);//shift out the data
	digitalWrite(latchPin,HIGH);//latch it in

}

I thought I could remedy the problem by adding the blanking operations (theoretically keeping the TLC off until all the data was "in," but apparently not. Attached is a relative schematic of my setup.

I am receptive to any ideas you guys have about fixing this.
Many thanks!

Is there any decoupling capacitors on those chips?

Look what I did here, I have no such issues.
http://www.thebox.myzen.co.uk/Hardware/Mini_Monome.html

I apologize for being a bit shaky on decoupling capacitors - are those the ones that go directly in the path of the signal and act as a filter, or the ones that go between the signal and ground to get rid of ripple?

I got slightly better results by replacing the 595 with a 4017 decade counter and clocking it on the pulses of XLAT, which reduced the flicker a bit but didn't help with the bleed issue.

On your Monome, I see that you haven't used an extra chip to activate the anodes. I'll try implementing this and see how it works. I'm apprehensive since I plan to scale it up to 16x10 and the Uno won't have enough extra pins to do that.

I apologize for being a bit shaky on decoupling capacitors - are those the ones that go directly in the path of the signal and act as a filter, or the ones that go between the signal and ground to get rid of ripple?

Nether they are capacitors that go between the power an ground on each chip.
http://www.thebox.myzen.co.uk/Tutorial/De-coupling.html

I'm apprehensive since I plan to scale it up to 16x10 and the Uno won't have enough extra pins to do that.

I would use an MCP23S17 to get extra outputs.

Also see how I scaled it up to 64 LEDs in this project
http://www.thebox.myzen.co.uk/Hardware/Hexome.html

Thanks. No, I wasn't using decoupling capacitors, but I'll put them in.
Using outputs instead of a discrete chip didn't really help with the bleed at all.

I'm having a bit of trouble understanding your schematic - is the 23S17 expanding the I/O for the switches, or for driving the LEDs? It seems like the MOSFETS are doing that.

My biggest problem is still the "bleed" from one column to the next. Any idea on what could be causing that?

My biggest problem is still the "bleed" from one column to the next. Any idea on what could be causing that?

That was only a partial schematic you posted, it left a lot of things out. Also you did not post all the code.
What you are doing wrong is not synchronizing the multiplexing of the anodes with the switching of the TLC5940.

I thought I could remedy the problem by adding the blanking operations (theoretically keeping the TLC off until all the data was "in," but apparently not.

The TLC drivers will change the value on the pins when it is time despite you changing them in your code.

The columns are driven HIGH in sequence by a 74HC595 shift register.

You can't get enough current sourced from a 74HC595 to drive a whole column. You need P-channel FETs to drive them.

is the 23S17 expanding the I/O for the switches

Yes. You said you were worried about running out of pins, that chip is your solution.

I've put in decoupling capacitors, similar to what is shown on your site - a 0.1 uF ceramic capacitor across the positive and negative leads of each chip, and a 47uF electrolytic capacitor across the "main" power supply. It has helped a little, with less flicker being noticeable, but the bleed is still there.

I have attached an updated schematic hopefully illustrating what I have now. I couldn't find a TLC5940 part in EAGLE, so I just used a 28-pin DIP generic.

You should be using PNP transistors not NPN, with what you have their is only ever 4.3V across the LEDs.

You still have not addressed the synchronisation problem that is where the bleed is coming from.

As far as I can reason, NPN drivers work with this because the currently "selected" output of the 4017 is HIGH, which would switch on the NPN. I tried replacing them with PNP drivers, but it went crazysauce.

Here's my current code:

#include "Tlc5940.h"
#include "tlc_fades.h"

#define NUM_ROWS 4	//number of rows (0-n)
#define NUM_COLUMNS 4

#define Clk4017 7	//Clock pulse pin for decade counter
#define Clr4017 4


//row data array for "R-G-B-White" display sequence
int rSeq[] = {4095,0,0,4095};
int gSeq[] = {0,4095,0,4095};
int bSeq[] = {0,0,4095,4095};

void setup()
{
	Serial.begin(9600);
	pinMode(Clk4017,OUTPUT);
	pinMode(Clr4017,OUTPUT);
	Tlc.init(0); 
	Serial.println("Begin");
	Pulse(Clr4017);
}

void loop()
{
	Pulse(Clr4017);
	for(int i = 0; i < NUM_COLUMNS; i++)
	{
		
		for (int j = 0; j < NUM_ROWS; j++)
		{
			SetRow(j,rSeq[i],gSeq[i],bSeq[i]);
			
		}
		Tlc.update();
		delay(5);
		Pulse(Clk4017);

		
	}
}

//Sends row data to the TLC but DOES NOT update
void SetRow(int row, int r, int g, int b)
{
	int startChannel = row*3;
	Tlc.set(startChannel,r);
	Tlc.set(startChannel+1,g);
	Tlc.set(startChannel+2,b);
}

void Pulse(int pin)
{
	digitalWrite(pin, HIGH);
	digitalWrite(pin, LOW);
}

I feel like the synchronization problem is stemming from the order in which the TLC is updated and then the 4017 is pulsed, but I have tried different orders to no effect.

I almost have it!

I tied the 4017's clock to XLAT (every time there is an XLAT pulse, it sends one to the 4017 as well) and modified my loop slightly:

void loop()
{
	Pulse(Clr4017);
	for(int i = 0; i < NUM_COLUMNS; i++)
	{
		
		for (int j = 0; j < NUM_ROWS; j++)
		{
			SetRow(j,rSeq[i],gSeq[i],bSeq[i]);
			
		}
		Tlc.update();
		delay(3);
	}
}

That has almost entirely eliminated the bleed! Now, there is still a slight flicker. Should the value of my decoupling capacitors perhaps be different?

As far as I can reason, NPN drivers work with this because the currently "selected" output of the 4017 is HIGH, which would switch on the NPN.

Yes this means you will have to invert the output or use another device, a 4017 is an odd choice for this sort of thing.

Should the value of my decoupling capacitors perhaps be different?

No.

That has almost entirely eliminated the bleed!

That is because you are better synchronized but not yet actually synchronised.

As far as I can see you can't do it the way you are trying, you have to get into the library and change that. Otherwise there is a multiplexed version of the library.

Grumpy_Mike:

As far as I can reason, NPN drivers work with this because the currently "selected" output of the 4017 is HIGH, which would switch on the NPN.

Yes this means you will have to invert the output or use another device, a 4017 is an odd choice for this sort of thing.

Thanks for bearing with me. Why should I use PNP drivers as opposed to NPNs?
I chose the 4017 with the thinking that it would be the quickest to change, as it only requires a single pulse to change from one output to the next, whereas, say, a 595 needs to shift data out which is slower.

As far as I can see you can't do it the way you are trying, you have to get into the library and change that. Otherwise there is a multiplexed version of the library.

I have looked at the multiplexed library a little, but not much - what does it do that my way of doing it does not?

It is no slower using a shift register, you don't have to clock in all 8 bits you know, just clock in a single low bit and on each multiplex switch just pulse it once on the clock.

With an NPN you will only ever get the emitter to be 0.7V lower than the base so you will not get the full voltage out.

As I keep saying your way is not synchronised to the cycle time of the chip. Also I thnk the signals out are defined by the timers in this mode digital write does not work because the output pins are switched to another thing inside the arduino's processor chip.

Aha, I didn't think about that with the shift register. Thanks! I'm not entirely sure how to put that in code though. Perhaps something like:

int data[] = {0,1,1,1,1,1,1,1};

for (int i =0; i < 8;i ++)//of course it wouldn't be a for loop, I just want it to trigger 8 times
{
     digitalWrite(DATAPIN, data[I]);
     pulse(SHIFTCLK);
}

It is synchronized, though - XLAT gets pulsed when the TLC's new cycle is completely clocked in and I physically tied the 4017 to that pulse. There is still a tiny bit of flicker, though, so I may be wrong. If the chip's "cycle" isn't complete on the XLAT pulse, when is it? That's what I glean from the datasheet, anyway.
How is digitalWrite tied to the timers? Will I have to use direct port access for everything instead?

Even direct port access will not help you control a pin that has been switched over to a timer. The switch is deep inside the hardware of the processor. I think the only answer is to hack the code inside the libary.

What you would do with the shift register is set it up by shifting in all ones in the setup function. Then shift in one zero for the first multiplex cycle and shift in a one for the seven cycles following it. Then shift in a zero again.

Your ring counter is synchronised but it is the data that is not which is why you see a bleed.

It would be nice to get a proper solution for this.

Here's the best I can do without someone fixing the driver:

void clearLEDS()
{
  digitalWrite(layerLatchPin, LOW);
  shiftOut(layerDataPin, layerClockPin, MSBFIRST, B00000000 );
  shiftOut(layerDataPin, layerClockPin, MSBFIRST, B00000000 );
  shiftOut(layerDataPin, layerClockPin, MSBFIRST, B00000000 );
  digitalWrite(layerLatchPin, HIGH);
}
void openCol(int col)
{
  digitalWrite(layerLatchPin, LOW);
  shiftOut(layerDataPin, layerClockPin, MSBFIRST, colMasks[col] >> 16 );
  shiftOut(layerDataPin, layerClockPin, MSBFIRST, colMasks[col] >> 8 );
  shiftOut(layerDataPin, layerClockPin, MSBFIRST, colMasks[col] );
  digitalWrite(layerLatchPin, HIGH);
}



void printBuffer(int counter)
{
  int r,g,b;
  int ndx = 0;
  
  for(int x = 23; x >= 0; x--)
  {

    clearLEDS();
    openCol(x);

    for(int y = 0; y < 15; y++)
    {
      //each byte holds a value from 0-255           
      r = buffer[ndx] ;  
      g = buffer[ndx+1] ;  
      b = buffer[ndx+2] ;

      int red = getRedSinkValue(y);
      int green = red+1;
      int blue = red+2;  
      
        Tlc.set(blue, b <<2);
        Tlc.set(red, r <<1);
        Tlc.set(green, g <<2);
      
      ndx+=3;
    }

    Tlc.update(); 
  }
}