Multiplexing a LED matrix with shift registers

Hey there, I am currently on a project where I will need to controll a 12x12 led matrix. In theory this is “easy”: I have 74HC164N shift registers which circle through the rows of leds and ULN 2803 to power individual columns.
All of this should be controlled by a standalone atmega 328P.

At the moment I am trying to get the 74HC164N working like I want them to. One bit has to circle through the rows, so that only one is active at a time.

So this is what has to happen:

10000000
01000000
00100000
00010000
00001000
00000100
00000010
00000001
...
  • the other leds and a delay to controll the framerate.
    I know the 74HC164N have no latch but they should not really need one for this.

If I use shiftout() it always writes the whole 8 bits without me beeing able to controll the frame rate or use only 12 bits in general, not the whole 16. So I tried to write my own code:

// 74HC164N Testcode

int dataPin = 10;
int clockPin = 11;
int framerate = 1000 / 60; // = 60 frames

void setup() {  
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
}

void loop() {
  
  digitalWrite(clockPin, LOW);
  digitalWrite(dataPin, HIGH);
  digitalWrite(clockPin, HIGH);
  digitalWrite(dataPin, LOW);
  
  for(int i = 0; i < 7; ++i)
  {
    digitalWrite(clockPin, LOW);
    digitalWrite(clockPin, HIGH);
  }
  
  //delay(framerate);
}

This only lights the first led though… I feel like I made a fundamental mistake somewhere but I do not know where!?

You don't have enough code there.
The '164 with ULN2803 as output buffer would be useful for sinking current from common cathode columns.
With 12 columns, you could put the walking 1 pattern across two bytes feeding daisychained shift registers:
Upper Lower
00000000 00000001
00000000 00000010
00000000 00000100
00000000 00001000
00000000 00010000
00000000 00100000
00000000 01000000
00000000 10000000
00000001 00000000
00000010 00000000
00000100 00000000
00001000 00000000

Can store that data as an int array:
int cathodeArray = {
0b0000000000000001,
0b0000000000000010,
0b0000000000000100,
0b0000000000001000,
0b0000000000010000,
0b0000000000100000,
0b0000000001000000,
0b0000000010000000,
0b0000000100000000,
0b0000001000000000,
0b0000010000000000,
0b0000100000000000,
};

and use it as part of your pattern:

// time to update display? determine by blink without delay - update every 2700uS for 30Hz refresh rate: (1/30)/12 = 2777 microseconds
x=x+1; // point to next column
if (x==12){x=0;}  // reset to first column if reached the end
// turn off all cathodes
shiftOut (dataPin, clockPin, MSBFIRST, 0);
shiftOut (dataPin, clockPin, MSBFIRST, 0);

// send out anode data, however you decide to do that

// send turn on the next column
shiftout (dataPin, clockPin, MSBFIRST, lowByte (cathodeArray[x]);
shiftout (dataPin, clockPin, MSBFIRST, highByte (cathodeArray[x]);
}

You have no code that would be used for driving the anode rows tho.

Sure, you are right. This is just my first test, so I only used one 164 with its 8 outputs.
The important point is:

shiftOut (dataPin, clockPin, MSBFIRST, xxx);

As far as I understand this, shiftout always sets all 8 bits on the shift register. That would work too, but isn't that total overkill? I only need that one bit to circle through the positions. Your solution does a full "sweep" for every position.

Correct me if I am wrong here please :wink:
And thanks for your answer!

Yes, it sends out 2 bytes.
If you use SPI.transfer to send out those 2 bytes, it is really fast.
With SPI.divisor set to 2, the transfer happens at 8 MHz speed, so it takes just over 2uS to send out using the uC dedicated SPI hardware.
While shiftOut plods along at a much slower speed, the software controlling the clock going up & down and the setting the bit level that is to go out.
'164 has the disadvantage of not having a 2nd stage latch which the slave select would normally drive - you can add an AND gate (or a tristate buffer chip, 'HC125 for example) between the SCK on D13 and the CP pin on the shift register to act as slave select/clock enable pin and have multiple chips controlled with SPI's SCK & MOSI for sending out high speed data.

Be sure to have 0.1uF cap on the VCC pin of every device, right next to the pin and connected to the nearest Gnd.

LastSamurai:
As far as I understand this, shiftout always sets all 8 bits on the shift register. That would work too, but isn't that total overkill? I only need that one bit to circle through the positions. Your solution does a full "sweep" for every position.

I know what you mean. Walking a single "1" through the shift register should be possible just by toggling its clock input, as per the code on your first post. But should the delay (currently commented out) be inside the for loop? As it is, the "1" will move along the register in almost no time at all, so most of the leds get a tiny average current and are not visibly lit. I wonder if the led that comes on is actually the last, not the first? Perhaps it is getting higher average current because of the delay between calls to loop ().

Paul

PaulRB:
I know what you mean. Walking a single “1” through the shift register should be possible just by toggling its clock input, as per the code on your first post. But should the delay (currently commented out) be inside the for loop? As it is, the “1” will move along the register in almost no time at all, so most of the leds get a tiny average current and are not visibly lit. I wonder if the led that comes on is actually the last, not the first? Perhaps it is getting higher average current because of the delay between calls to loop ().

Paul

Exactly! That’s what I do wanna know/try. If I do try it like that the LED on QA (=1?) is on. If I move the delay into the for loop though… should’t it work? But what delay would be the right one? 1/60/8 for 60 frames and the 8 outputs of the shift register (if I only use that one for testing) ?
It only really works with values <250 and better values are <100. That means framerates of 500 and higher (right?!). Why do these values have to be so high?

PS The following code seems to work. All leds are working and you don’t see too much flickering. If I use higher values the flickern gets worse. If the values get too high only the first led lights up.
How do I calculate the value needed for the framerate? At the moment I can only guess.

// 74HC164N Testcode 2

int dataPin = 10;
int clockPin = 11;
int framerate = 20; // = 60 frames

void setup() {  
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
}

void loop() {
  
  digitalWrite(clockPin, LOW);
  digitalWrite(dataPin, HIGH);
  digitalWrite(clockPin, HIGH);
  digitalWrite(dataPin, LOW);
  delayMicroseconds(framerate);
  
  for(int i = 0; i < 7; ++i)
  {
    digitalWrite(clockPin, LOW);
    digitalWrite(clockPin, HIGH);
    delayMicroseconds(framerate);
  }
}

Yes, that's almost what I meant, except now you have changed delay() to delayMicroseconds(), you are probably getting over 6,000 frames per second!

If you want 60 frames per second, each frame will be 1/60 = 0.0167 seconds or 16.7ms (milliseconds). Your for-loop counts up to 8 in each frame, so the individual delays need to be 16.7 / 8 = 2ms = 2,000us (microseconds).

Also just noticed what I assume is an error? Your code at the start of loop() shifts the "1" into the first bit of the register and then there is a delay. The for-loop then shifts this "1" along 8 times, so in the final execution of the for-loop, the "1" has dropped out of the shift register and all outputs are zero. So you probably want to make your for-loop run for one less time.

I did that on purpose. My goal is to get a "static" picture, without flickering. I assumed that 60 fps would be enough, cause that works on monitors. But if I use delayMicroseconds(2000); as you suggested the leds flicker like crazy. I have to use values below ~200 to make it work. I still don't understand why though...

LastSamurai:
if I use delayMicroseconds(2000); as you suggested the leds flicker like crazy.

Odd...

I updated my earlier reply without realising you had replied. Check out my suggestion. If it still flickers, post a schematic or a picture of the circuit.

Also try this: change delayMicroseconds() back to delay() and set framerate to 500. This should then do the shifting in slow motion, so you can verify that each led is lit once in the correct sequence and there a no gaps in the sequence. If so, change framerate to 250, then 125, then 50, 25, 12, 6, 3, 2 etc and see that (hopefully) the whole thing speeds up until the flickering disappears.

Do you have a 0.1uf decoupling cap close to the supply pins of the 74164?

Ok, it’s gettin better but it’s still kinda odd. I did what you suggested and cut the for loop down to 0-5. I then changed delayMicroseconds() back to delay() and tried the values. So in theory everthing works. All leds light up and if I use values <=3 it stops flickering.
But then it sometimes suddenly goes wrong. Only the first led lights up for some seconds or it starts to flicker, but then it goes back to normal. Its not even a regular event. Most of the times it works though.

I used a decupling cap, but the curcuit is on a breadboard (only testing atm) and I am using different leds (red green and yellow ones). May that be a problem?

No, the led colours should make no difference. Try moving the circuit to another part of the breadboard. Then try changing to use different Arduino outputs. It sounds like a dodgy connection somewhere to me.

Yeah, it seems to be one cable. Thank you very much for your help Paul.
I think this part works now. I will only need to add another 8 to the for loop. Although I do not use all of the pins on the second register. Hope that wont be visible in the led pattern (cause there is a bigger delay at the end, when it circles through the last ones).

No, the unwanted 4 outputs on the 2nd register should have no impact. You will simply introduce another "1" to the first register as the last "1" reaches the 13th position. Suggest you use the "slow motion" technique to check you've got it right.

Good luck with the next stage!

Paul