LED-Matrix flickering

Hi all!
I’m doing a 48x8 LED-matrix as a “getting started- project”. I’m using cascaded 74hc595 shift-registers (Yeah, I know about current-limitations, but it was cheap enough to try) for the columns and a CD4017 decade counter for the rows. -See attached circuitry (During test only 8x8 is used and thus, only one shift-register).
*Edit: I’m using 2N3904, not 2N2222 as shown in the schematics, since my 2n2222’s hasn’t arrived yet.
The whole thing is controlled by a Nano (powered through USB, but both the shift- register and 4017 are powered by an external voltage source)

I got 2 issues I could use some help with:

  1. I get a constant flickering of the LED’s! I’ve tried some delays, but it just seems to make it worse. When deactivating the 4017 clock pulse (only one row lights up), the flickering stops, so I assume this is caused by the 4017. During test (see simple test code code below), I only light up 3 LED’s per row (to lower total current drawn from shiftregister), but I get the same effect no matter how many LED’s are turned on.
  //Setup pins for 4017 Decade counter 
  #define DIO_decade_CLK 7		//4017 CLK pin 
  #define DIO_decade_reset 13		//4017 Reset pin

  //Setup pins for 74HC595 ShiftRegister
  #define DIO_shiftReg_shiftCLK 2	//Shift Register Shift Register CLK pin (SHCP) (Shifts bits and store in temporary memory)
  #define DIO_shiftReg_storeCLK 3	//Storage Register CLK pin (STCP) ("Latch" to output on rising trigger)
  #define DIO_shiftReg_reset 4		//Shift Register Master Reset pin (MR) ACTIVE LOW! -SET HIGH TO NOT CLEAR MEMORY (Use storeCLK after)
  #define DIO_shiftReg_data 5		//Shift Register Serial Data pin (DS)


  #define characterLength 8			//Total number of bits in a segment. Standard is 8.


void setup() {
  // put your setup code here, to run once:
  resetDecadeCounter();
  Serial.begin(9600); //Setup serial connection for display messages

  //Output (1), input(0) setup
  DDRD = DDRD | B10111100; //Set digital pin 2,3,4,5 as output (replaces pinMode)
  DDRB = DDRB & ~B00000001; //Set digital pin 8 as Input

//Set pin default values
  PORTD = PORTD | (1 << DIO_shiftReg_reset);  //Set Digital pin 4 high (replaces digitalWrite)
}

void resetPin(byte pin){	//Reset $param pin to LOW
  digitalWrite(pin, LOW);	//Replace with PORTx in live code!
}
void setPin(byte pin) { 	//Set $Param pin to HIGH
  digitalWrite(pin, HIGH);	//Replace with PORTx in live code!
}
void pulsePin(byte pin){ 	//Set $Param pin to HIGH then LOW
  digitalWrite(pin, HIGH);	//Replace with PORTx in live code!
  digitalWrite(pin, LOW);	//Replace with PORTx in live code!
}

void pulsePin_slow(byte pin, int duration){ 	//Set $Param pin to HIGH, wait $Param duration then set pin to LOW
  digitalWrite(pin, HIGH);	//Replace with PORTx in live code!
  delay(duration);			//Delay in milliseconds
  digitalWrite(pin, LOW);	//Replace with PORTx in live code!
}


void resetDecadeCounter(){          //Resets the 4017 Decade Counter
  pinMode(DIO_decade_reset,OUTPUT); //Set DIO_decade_Reset as output
  pulsePin(DIO_decade_reset);       //Reset
  pinMode(DIO_decade_reset, INPUT); //Set DIO_decade_Reset as input again (Don't actually remember why I'm doing this(?)
//  Serial.println("Reset");
//  Serial.println("");
}


void sendByte(byte rowByte, unsigned int amount) { 				//Send one byte to 74hc595 temporary memory, one bit at a time. rowByte = data to print, amount = number of bits to print
	byte bitsToPrint = amount;
	unsigned int zeroCharactersWanted = 0;						//Number of bytes full of zeros wanted (Changes if amount>characterLength)
	
	if(amount>characterLength){									//if Too many bits are requested (More than characterLength), then fill with zeroes until all bits are set
		bitsToPrint = amount%characterLength;					//wanted number of bits that are exceeding full bytes
		zeroCharactersWanted = amount/characterLength;			//Number of full zero-characters
	}
	for (byte i = characterLength; i>(characterLength-bitsToPrint); i--) {	//While there are bits to set (up to one full byte)
//		Serial.print(!!(rowByte & (1<<(i-1))));
		digitalWrite(DIO_shiftReg_data, !!(rowByte & (1<<(i-1))));			//Set databit to 0 or 1 as per MSG
		pulsePin(DIO_shiftReg_shiftCLK);									//store bit to ShiftReg input memory
	}

	resetPin(DIO_shiftReg_data);									//Reset databit to LOW
}

void loop() {
	byte number = B11100000;
	sendByte(number,8);		
	pulsePin(DIO_decade_CLK);
	pulsePin(DIO_shiftReg_storeCLK);

}
  1. When playing around with delays (10-500ms) between shift-reg.latch- and decade clock, some LED’s doesn’t turn on when using scrolling effect (See code below, it’s the same as above, but a little different loop() ). It seems that the same LED’s are always skipped.
    I would like to see if anyone has any hint on what might be causing this?
  //Setup pins for 4017 Decade counter 
  #define DIO_decade_CLK 7		//4017 CLK pin 
  #define DIO_decade_reset 13		//4017 Reset pin

  //Setup pins for 74HC595 ShiftRegister
  #define DIO_shiftReg_shiftCLK 2	//Shift Register Shift Register CLK pin (SHCP) (Shifts bits and store in temporary memory)
  #define DIO_shiftReg_storeCLK 3	//Storage Register CLK pin (STCP) ("Latch" to output on rising trigger)
  #define DIO_shiftReg_reset 4		//Shift Register Master Reset pin (MR) ACTIVE LOW! -SET HIGH TO NOT CLEAR MEMORY (Use storeCLK after)
  #define DIO_shiftReg_data 5		//Shift Register Serial Data pin (DS)


  #define characterLength 8			//Total number of bits in a segment. Standard is 8.


void setup() {
  // put your setup code here, to run once:
  resetDecadeCounter();
  Serial.begin(9600); //Setup serial connection for display messages

  //Output (1), input(0) setup
  DDRD = DDRD | B10111100; //Set digital pin 2,3,4,5 as output (replaces pinMode)
  DDRB = DDRB & ~B00000001; //Set digital pin 8 as Input

//Set pin default values
  PORTD = PORTD | (1 << DIO_shiftReg_reset);  //Set Digital pin 4 high (replaces digitalWrite)
}

void resetPin(byte pin){	//Reset $param pin to LOW
  digitalWrite(pin, LOW);	//Replace with PORTx in live code!
}
void setPin(byte pin) { 	//Set $Param pin to HIGH
  digitalWrite(pin, HIGH);	//Replace with PORTx in live code!
}
void pulsePin(byte pin){ 	//Set $Param pin to HIGH then LOW
  digitalWrite(pin, HIGH);	//Replace with PORTx in live code!
  digitalWrite(pin, LOW);	//Replace with PORTx in live code!
}

void pulsePin_slow(byte pin, int duration){ 	//Set $Param pin to HIGH, wait $Param duration then set pin to LOW
  digitalWrite(pin, HIGH);	//Replace with PORTx in live code!
  delay(duration);			//Delay in milliseconds
  digitalWrite(pin, LOW);	//Replace with PORTx in live code!
}


void resetDecadeCounter(){          //Resets the 4017 Decade Counter
  pinMode(DIO_decade_reset,OUTPUT); //Set DIO_decade_Reset as output
  pulsePin(DIO_decade_reset);       //Reset
  pinMode(DIO_decade_reset, INPUT); //Set DIO_decade_Reset as input again (Don't actually remember why I'm doing this(?)
//  Serial.println("Reset");
//  Serial.println("");
}


void sendByte(byte rowByte, unsigned int amount) { 				//Send one byte to 74hc595 temporary memory, one bit at a time. rowByte = data to print, amount = number of bits to print
	byte bitsToPrint = amount;
	unsigned int zeroCharactersWanted = 0;						//Number of bytes full of zeros wanted (Changes if amount>characterLength)
	
	if(amount>characterLength){									//if Too many bits are requested (More than characterLength), then fill with zeroes until all bits are set
		bitsToPrint = amount%characterLength;					//wanted number of bits that are exceeding full bytes
		zeroCharactersWanted = amount/characterLength;			//Number of full zero-characters
	}
	for (byte i = characterLength; i>(characterLength-bitsToPrint); i--) {	//While there are bits to set (up to one full byte)
//		Serial.print(!!(rowByte & (1<<(i-1))));
		digitalWrite(DIO_shiftReg_data, !!(rowByte & (1<<(i-1))));			//Set databit to 0 or 1 as per MSG
		pulsePin(DIO_shiftReg_shiftCLK);									//store bit to ShiftReg input memory
	}
	
	resetPin(DIO_shiftReg_data);									//Reset databit to LOW
}

void loop() {
	byte number =0;
	for (byte i=0; i<8; i++){
		number = 3<<i;
		sendByte(number, 8);
		pulsePin_slow(DIO_shiftReg_storeCLK,100);	//I've tried without delay too	//latch shiftregister output 
	}

}

Hi and welcome to the forum.

I'm doing a 48x8 LED-matrix as a "getting started- project".

This is certainly not a"getting started" project! Is it truly the first Arduino circuit you have tried to build? If so, you need to spend some weeks learning the basics and come back to this project later.

I'm using cascaded 74hc595 shift-registers for the columns and a CD4017 decade counter for the rows

Appropriate chips for this project would include max7219 or ht16k33. You seem to be going for a much more complex and ultimately inferior circuit. Can you please explain why? Helping you to get this complex circuit working, when you could be using a much better and simpler one, really needs to be justified before we continue.

See attached circuitry

+1 karma for attempting to provide a proper schematic diagram and learning an appropriate tool to produce it. However, I can already see what I suspect are simply drawing mistakes that do not reflect the actual circuit you have built so far. No-one wants to waste their time telling you what's wrong with your circuit only for you to reply that it was just a drawing error, so please double check that all the connections drawn truly reflect your actual wiring.

PaulRB:
Hi and welcome to the forum.

Thanks! I’m very excited :slight_smile:

PaulRB:
This is certainly not a"getting started" project! Is it truly the first Arduino circuit you have tried to build? If so, you need to spend some weeks learning the basics and come back to this project later.

Ok, sorry, “Getting started” might have been a bit misleading: I’m actually studying electronics engineering, so I have some theoretical knowledge about circuitry, but we haven’t done very much actual circuitry construction. We also have done programming in C and PIC-programming, but haven’t used Arduino before. So what I meant was to get started with arduino and building own projects although I do have some experience and I do think this project is on the right level for me.

PaulRB:
Appropriate chips for this project would include max7219 or ht16k33. You seem to be going for a much more complex and ultimately inferior circuit. Can you please explain why?

I have chosen a more complex circuitry partly on puropse: The main goal of this project is to construct a circuit from scratch and learning all the steps throughout. I didn’t want a “plug-and-play” solution. In the selected design, I get to play with and learn about more components. However, I haven’t looked too much into max7219 or ht16k33, maybe it wouldn’t make a lot of difference in that regard, maybe.
But two other reasons are that the chosen components are cheap. -Since I’m a student I don’t have a lot of money to spend. And secondly, when getting started, I found a few instructables that were using my approach. So if/when I got stuck, I would have somewhere to look for inspiration.
Also, since these were the projects I found (others used only 595’s), I thought they would be “good enough”. -I didn’t know this would make a remarkably “inferior circuit”.

Besides the limitations of current through the 595’s, what is it that makes this circuit inferior to one using for example the ht16k33?

PaulRB:
+1 karma for attempting to provide a proper schematic diagram and learning an appropriate tool to produce it. However,

please double check that all the connections drawn truly reflect your actual wiring.

Thanks for the karma :slight_smile:
I’ve looked through my drawing and corrected the things mentioned in my previous post (also changed labels to make it clearer). But I couldn’t find anything that differ from my connected circuit…?

OK, without quoting your last comments I will go through a few things.

Flickering.

pinMode(DIO_decade_reset, INPUT); //Set DIO_decade_Reset as input again (Don't actually remember why I'm doing this(?)

No. I myself had no idea why you would do this but I have only just realised you have reset tied to "Q8" and are using the Arduino to "crowbar" the reset pin since the Arduino driver is stronger than the antique CD4017. Not what anyone here would call sensible design but I suppose it it will sort of work. I initially thought that would cause erratic behaviour including flickering and just about any other problems you describe - maybe, maybe not.

Your suggestion that "the chosen components are cheap" is simply nonsense. The MAX729 modules that are (were) readily available from China on eBay are "dirt cheap". I won't bother illustrating them for you - you can sort that out yourself, or you can find what I have written in other posts. A single MAX7219 drives an 8 by 8 LED array using only one resistor and a couple of bypass capacitors which incidentally are curiously missing from your illustration. Did you forget them? You just add one MAX7219 and the resistor and bypass capacitor for each successive 64 LEDs.

So one MAX7219 replaces and performs the task immeasurably better than two HCMOS ICs, eight transistors and 16 resistors. Cheap?

We really do have to chuckle at your "I found a few instructables that were using my approach. So if/when I got stuck, I would have somewhere to look for inspiration." You need to realise that "instructables" is (correctly) regarded on these fora with complete derision as the repository of some of the worst possible design failures imaginable and where it is not possible to make any comment to point out such errors. Misuse of 74HC595s is just one of those common illustrations.

There are two primary reasons that the approach you have given is inferior; the first that the 74HC595 does not have the drive capability of a purpose-designed IC such as the MAX7219.

The second is that while the MAX7219 performs the necessary multiplexing of the array in hardware, without a glitch and requiring no software support to do so, multiplexing in software requires a strict discipline to the code in order to perform the precision timing in concert with whatever else you expect the code to do at the same time. The code has to be carefully thought out with a view to that timing; processor cycles are too valuable to be wasted using "delay()".

Your code in this respect appears to be quite random and disordered, some parts suggest the multiplexing is being performed unnecessarily fast while others may - I am having some difficulty following it - be slowing it down inappropriately. There is a reference thread in one forum here regarding how to perform timing. A multiplex cycle should take somewhere between 10 and 20 ms (50 to 100 Hz) so for an 8-way multiplex, you should advance one step (exactly) each 2 ms.

Since as you say, you are doing this only for the intellectual exercise, I invite you to look into a few of these coding aspects and revisit your 4017 reset circuitry. :grinning:

Some other suggestions:

Replace the cd4017 with another 74hc595. You can chain it to the other 74hc595(s) so that no other Arduino pins are needed. You will still need the transistors.

Replace the cd4017 with a tpic6b595 (or tpic6595 or tpic6a595, whichever is appropriate, but not tpic6c595 or tpic6d595). These chips are basically a 74hc595 with 8 high-current transistors built-in, so you won’t need the discrete transistors. The built-in transistors are n-channel MOSFETs, so you can only use this chip on the matrix cathode connections only. Each output pin can sink 150mA~350mA, depending on the model, but cannot source any current (hence useless for the anode connections). Tpic6c595 and tpic6d595 would also work but their current capability is lower and you plan to have a 48x8 matrix, so the more current the chip can handle, the better.

(These suggestions are still not as good as max7219/ht16k33)

PaulRB:
(These suggestions are still not as good as max7219/ht16k33)

No, not cheap either, but that is not the point. It is purely an educational exercise. :grinning:

Thank you both for the tips!
I want to make my current design work as good as possible, however I’ll probably get a Max7219 or similar for future projects, since “my” way obviously is not ideal.
And I will try to stay away from the instructables :smiley: I had my suspicions regarding those, especially after looking at some of the code that they posted. Gotta learn to look around in arduino- forums instead.

Paul__B:

pinMode(DIO_decade_reset, INPUT); //Set DIO_decade_Reset as input again (Don't actually remember why I'm doing this(?)

No. I myself had no idea why you would do this but I have only just realised you have reset tied to “Q8” and are using the Arduino to “crowbar” the reset pin since the Arduino driver is stronger than the antique CD4017.

I think I did this mainly to be able to read Q8 signals to keep track of active row. Had another reason to do this, but I think that was just another bad idea, that I removed from the code.
The reason Q8 is connected to reset is ofcourse to start over at row1 by hardware. And I connected it to the arduino too, to be able to do resets from code too (for instance if a new message is sent).
I guess I could do both of these from code and loose the connection between Q8 and reset.

Regarding the missing bypass caps, I didn’t know I needed them. Now I do, and they will be added. :slight_smile:

I didn’t know the max7219 handled multiplexing in hardware. -Sounds interesting and something I will look into.
I expected timing were the main issue with my setup and I was wondering how to solve “simultaneous operations”, but didn’t know how to handle this. The use of millis() seams like a very good place to start. -Thank you very much for this link.

Paul__B:
A multiplex cycle should take somewhere between 10 and 20 ms (50 to 100 Hz) so for an 8-way multiplex, you should advance one step (exactly) each 2 ms.

Why is this? I supposed timing had to do with the maximum clock-cycle of the 74hc595 (among other things), but you seem to say this is a general rule?

A bit of a sidenote:
Regarding the costs: I live in Sweden, where we have to pay an extra postage fee of about $12 for every package received from china (or anywhere else outside of EU). 74hc595 and some of the other components, I found really cheap locally. The cheapest max7219 I found within the EU were about 4x the cost of all components needed to drive (albeit poorly so) 64 LEDs. So, yeah it actually were cheaper this way…

Thanks again! I’m really learning a lot from this as I go along :slight_smile:

I'm actually studying electronics engineering, so I have some theoretical knowledge about circuitry

bypass caps, I didn't know I needed them

What are they teaching you on this course?

in Sweden, where we have to pay an extra postage fee of about $12 for every package received from china (or anywhere else outside of EU).

Well, buy some from within the EU, such as UK (be quick!). In the UK we don't have to pay such charges for packages from China. If we are unlucky, we sometimes have to pay an import duty, but usually it is not very much.

slugg0:
Regarding the costs: I live in Sweden, where we have to pay an extra postage fee of about $12 for every package received from china (or anywhere else outside of EU).

Actually, outside of America, it is not possible to buy parts from there worth less than hundreds of dollars, as the minimum US overseas postage fee is of the order of $40. :astonished:

slugg0:
The cheapest MAX7219 I found within the EU were about 4x the cost of all components needed to drive (albeit poorly so) 64 LEDs.

Probably you are being quoted for genuine ones! :roll_eyes:

Hi again!
Sorry to bother you about this again, but I can't seem to make the timing work:

First, I found two major causes for the irratic behaviour from before: First off, the wire to the 74hc595 reset port was bad and left that port floating (which of course caused flickering, when the IC randomly resetted). Secondly, I had a Serial.print in the code, that slowed everything down a LOT..

After fixing these two errors, everything seems fine in my small example code as is. However, I would like to use millis() to avoid future timing errors. But when adding this, I get flickering again, even if I only "wait" by 1 ms (And I want 2 ms for a multiplex- step, as Paul said earlier).

In order to investigate the flickering, I made a testprogram with the following main loop:

void loop() {
	byte number = B11100000; //Data to display
	sendBits(number,8);  //Send data to 74hc595
	pulsePin(DIO_shiftReg_storeCLK);//Latch to 74hc595 output

	while(1){	
		currentMillis = millis();    //Capture current time
		if(currentMillis - previousDecadeCLKMillis >= multiplexRowTime) {    //multiplexRowTime set to 1 or higher
			previousDecadeCLKMillis += multiplexRowTime;
			if (rowCounter >= numberOfRows-1) {    //If no more rows...
				pulsePin(DIO_decade_reset);    //Reset decade counter (light up first row)
				rowCounter = 0;
			} else {
				pulsePin(DIO_decade_CLK);    //Light up next row
				rowCounter ++;
			}
		}
	}
}

This way, the 74hc595- part of the circuit is constant and should not affect the result.
The only thing that would cause flickering now is the behaviour of the 4017 IC.
And what ever value I set multiplexRowTime to, I get a nasty flickering. -Set it to zero = no flickering (Even when throwing the 74hc595 into the loop).
Why is this? Should even a 1ms difference be able to cause a visible effect?

Try using micros() instead of millis(). Obviously, multiplexRowTime will need to be increased by a factor of 1000.