Multiplexed 6 digit 7-seg flashing (4 x 74HC595)

I'm currently creating a button box that features 9 2-digit displays. These will be split into banks of 3 to display 3 different 6 digit numbers pulled from whatever game it's attached to (KSP is up first).

The digits are connected to the first 3 shift registers, and segments all connected to the 4th shift register.

Currently I am trying to get the segment code working.

I had 2 digits set up to test and was able to give it a value which it prints on the displays.
At first I had ghosting problems, but fixed it by creating a function to turn all pins off before moving to the next digit (as suggested in many other similar posts on this forum).

I couldn't find a library supporting sev seg displays and more than 2 shift registers but did find one that allowed me to treat the 74HC595 pins as normal pins using something similar to digital write

reg.pinOn/Off(shPin#)`

vs.

 digitalWrite(#, HIGH/LOW) 

Using this as a base I (attempted to) write my own seven seg driving code, which worked for 2 digits! I then moved up to 6 digits aaaand hello flickering.

Previously there were delays of 1ms after switching all digits/segments off, and another delay of 1ms after switching them back on. I've since removed these and it's appeared to have no impact on the visual output.

This is my first proper coding project, having avoided it most my life due it generally confusing me. I did look at writing directly to the shift register and shifting the 0's and 1's out directly but made a mess of it which prompted me to find the ShiftOutX library.

I feel like the Arduino isn't able to process my code fast enough / my code is too badly written to refresh the display at the rate required due to the fact that removing the delay function had no effect. Or I've put something somewhere I shouldn't.

So far I've;

  • Swapped the order that the digit and segments pins get switched on/off - segments first is darker but flickers at the same rate.
  • Added 0.47uf caps between the vcc and gnd of each 74HC595. Unfortunately I don't have any 0.1uf caps to hand so this could also be a culprit but I'd like to check the code out before ordering more parts (again).
  • Split all off <sdOff()> into segOff() and digOff() but this did nothing more than increase the size of the code.

Any thoughts or input would be greatly appreciated!

Hardware:
Arduino Pro Micro
4 x 74HC595 Shift Registers
3 x Common Cathode 18 pin 7 seg displays (E20561-J-PG-0-W) (Datasheet here)
6 x 1k resistors (1 on each digit, 1k just used for testing)

Code:

#include <ShiftOutX.h>
#include <ShiftPinNo.h>


shiftOutX reg(4, 6, 5, MSBFIRST, 4);

uint64_t value = 123456;

uint64_t segPin[8] = {shPin26, shPin27, shPin28, shPin29, shPin30, shPin31, shPin32, shPin25};
uint64_t digPin[6] = {shPin1, shPin2, shPin3, shPin4, shPin5, shPin6};

//int nSegs = 2;

int i = 0;

int digit1 = 0;
int digit2 = 0;
int digit3 = 0;
int digit4 = 0;
int digit5 = 0;
int digit6 = 0;

int segVal = 0;
int digVal = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void sdOff() {
  reg.pinOn(digPin[0]); // digits vvv
  reg.pinOn(digPin[1]);
  reg.pinOn(digPin[2]);
  reg.pinOn(digPin[3]);
  reg.pinOn(digPin[4]);
  reg.pinOn(digPin[5]);

  reg.pinOff(segPin[0]); // segments vvv
  reg.pinOff(segPin[1]);
  reg.pinOff(segPin[2]);
  reg.pinOff(segPin[3]);
  reg.pinOff(segPin[4]);
  reg.pinOff(segPin[5]);
  reg.pinOff(segPin[6]);
  reg.pinOff(segPin[7]);
  delay(1);
}

void segTable(int segVal) {
  if (segVal == 48) { //0
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[3]);
    reg.pinOn(segPin[4]);
    reg.pinOn(segPin[5]);
  }
  else if (segVal == 49) { //1
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
  }
  else if (segVal == 50) { //2
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[3]);
    reg.pinOn(segPin[4]);
    reg.pinOn(segPin[6]);
  }
  else if (segVal == 51) { //3
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[3]);
    reg.pinOn(segPin[6]);
  }
  else if (segVal == 52) { //4

    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[5]);
    reg.pinOn(segPin[6]);
  }
  else if (segVal == 53) { //5
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[3]);
    reg.pinOn(segPin[5]);
    reg.pinOn(segPin[6]);
  }
  else if (segVal == 54) { //6
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[3]);
    reg.pinOn(segPin[4]);
    reg.pinOn(segPin[5]);
    reg.pinOn(segPin[6]);
  }
  else if (segVal == 55) { //7
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
  }
  else if (segVal == 56) { //8
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[3]);
    reg.pinOn(segPin[4]);
    reg.pinOn(segPin[5]);
    reg.pinOn(segPin[6]);
  }
  else if (segVal == 57) { //9
    reg.pinOn(segPin[0]);
    reg.pinOn(segPin[1]);
    reg.pinOn(segPin[2]);
    reg.pinOn(segPin[5]);
    reg.pinOn(segPin[6]);
  }
}

void digTable(int digVal) {
  if (digVal == 0) {
    reg.pinOff(digPin[0]);
  }
  else if (digVal == 1) {
    reg.pinOff(digPin[1]);
  }
  else if (digVal == 2) {
    reg.pinOff(digPin[2]);

  }
  else if (digVal == 3) {
    reg.pinOff(digPin[3]);

  }
  else if (digVal == 4) {
    reg.pinOff(digPin[4]);

  }
  else if (digVal == 5) {
    reg.pinOff(digPin[5]);

  }
}


void loop() {
  // put your main code here, to run repeatedly:

  char valueArray[7];
  dtostrf(value, 6, 0, valueArray);

  int digit1 = valueArray[0];
  int digit2 = valueArray[1];
  int digit3 = valueArray[2];
  int digit4 = valueArray[3];
  int digit5 = valueArray[4];
  int digit6 = valueArray[5];

  int digit[6] = {digit1, digit2, digit3, digit4, digit5, digit6};

  for (int i = 0; i <= 5; i++) {

    digTable(digVal = i);
    segTable(segVal = digit[i]);
    sdOff();

    i++;
    segTable(segVal = digit[i]);
    digTable(digVal = i);
    sdOff();

    i++;
    segTable(segVal = digit[i]);
    digTable(digVal = i);
    sdOff();

    i++;
    segTable(segVal = digit[i]);
    digTable(digVal = i);
    sdOff();

    i++;
    segTable(segVal = digit[i]);
    digTable(digVal = i);
    sdOff();

    i++;
    digTable(digVal = i);
    segTable(segVal = digit[i]);
    sdOff();
  }
/*  value = value + 1;
  if (value == 999999) {
    value = 0;
  }*/
}


void disp(char valueArray[]) {


}

Here's a roughly done schematic covering how the displays and shift registers are wired up
Latch goes to pin 4 on the pro micro, Clock to 5 and Data/Ser to 6.
Not shown is a 1k resistor on each digit line. (1k used for testing purposes as I'd had a mountain of them to hand)

First time posting on a forum so if I've missed something please let me know.

I'm also aware I could just get a MAX LED driver and be done with it, but so far on this part of the project I've gone through MCP23018's, MCP23017's and now finally these shift registers, so it's more a point of making it work with what I've got instead of giving up and buying another chip again.

Hi,
Before talking about coding, I would like to understand your project better to see
if I can help you.

You posted some information but I need more.

"9 2-digit displays" Ok
"split into banks of 3 to display 3 different 6 digit numbers" Ok

What is the change time of values in each display bank?
Or what is the retention time of the value in each digit?

Does the arduino need to be the micro pro or can it be the mini pro?

The 3 banks will display the following for the first game (kerbal space program)

  1. Surface Speed (m/s)
  2. Altitude (m)
  3. Some other info like target velocity, orbital velocity etc..

These values are requested from the game via serial using the KSP SimPit library and mod.

So the process for 1 bank would be something like this;

  1. Request value
  2. Parse value to 6 digit array
  3. Display array over all the digits
  4. Request new value
  5. Parse to array
  6. Display new digits

And so on

So ideally the value would only be retained for as long as the display takes to show all the digits before then requested a new value to be displayed.

As for the hardware, I’ve got a few pro micros but no mini pros unfortunately. Beyond a couple extra pins is there much difference between the two?

I mean, seriously!

Why not do the job properly and get some MAX7219s? :roll_eyes:

Modules are fairly inexpensive on Aliexpress.

There are so many things wrong with your code and your circuit that at this point it would still be easier to start over and do it the right way. Don't be stubborn and make things more difficult for yourself and those who are willing to help you. You should have asked for advice earlier, then you wouldn't have wasted time and money on 3 different types of chips, none of which are intended as driver chips for multiplexed displays.

As an alternative to max7219, I would suggest considering an ht16k33 module. A single module could drive 8 of your 2-digit displays, so 2 modules would be needed.

I'll keep messing about with it till I can afford some new modules in a few weeks and start over then. I've never taken a programming class, the code is slapped together from things I've picked up out of different libraries, sketches and forum posts over the last week or so - So I expected it to be a total mess :sweat_smile:

2 of the mcp23017's ended up being used over i2c for all the buttons on the box so not a total loss there. mcp23018 I've had issues getting to do anything at all though, so they've been shelved for some future project until I understand more about all this. I won't be complaining about a shortage of I/O pins for a while though at least..

As for the circuit though, beyond using the wrong module for the job, is there an issue with the way it's wired up? A full diagram can be drawn up but unsure if it's worth it at this point.

If I get bored enough before ordering new stuff, I may end up wiring up 18 of these registers off a separate dc supply to run them individually just to see what happens.. it'd never fit in the box I printed so don't worry, it won't leave the breadboard! :laughing:

Appear to have replied to the wrong person in my previous post, still getting the hang of the forum :sweat_smile:

Easily done, there are quite a few of us named Paul on the forum :wink:

shiftOutX seems to update the shift registers as soon as you set a pin. Its better to collect all changes and send them out all at once. Also you can connect output enable to the Arduino. This allows you to turn off everything. This should eliminate ghosting.

You need to the following:

  • (select digit)
  • shift out data
  • output off
  • latch
  • output on
  • wait

Could be something like this:

#define dataPin  2 
#define clockPin 3
#define latchPin 4
#define oePin    5

#define numDigits 6

byte displaydata[numDigits][4];
byte currentDigit = 0;

void setup(){
  // init displaydata
  //                    .gfedcba
  displaydata[0][0] = 0b00111111;
  displaydata[1][0] = 0b00000110;
  displaydata[2][0] = 0b01011011;
  displaydata[3][0] = 0b01001111;
  displaydata[4][0] = 0b01100110;
  displaydata[5][0] = 0b01101101;

  for(byte digit = 0; digit < numDigits; digit++){
    // set bit for the selected digit
    displaydata[digit][3] = 0xff - (1 << digit);

    displaydata[digit][1] = displaydata[digit][0];
    displaydata[digit][2] = displaydata[digit][0];
  }


  // init pins
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(oePin, OUTPUT);
}

void loop(){
  // select digit
  currentdigit = (currentdigit + 1) % numDigits;

  // shift out data
  for(byte sr = 0; sr < 4; sr++){
    shiftout(dataPin, clockPin, MSBFIRST, displaydata[currentdigit][sr]);
  }

  // output off
  digitalWrite(oePin, HIGH);

  // latch
  digitalWrite(latchPin, HIGH);
  digitalWrite(latchPin, LOW);

  // output on
  digitalWrite(oePin, LOW);

  // wait some time
  delay(10);
}

Several issues.

Missing 0.1uF ceramic bypass caps for each chip, as you already know.

1K series resistors on digits... Are you multiplexing segment-by-segment? In other words all the "a" segments lit together across all digits, followed by all the "b" segments and so on. If so, resistors on the digits is ok. But mostly displays are multiplexed digit-by-digit. In other words, all segments of digit 1 are lit together, followed by all segments of digit 2 and so on. If so, you need resistors on each of the segment pins, and not on the digit pins. If you only have a resistor on the digit, the brightness of the digit will vary depending which segments are lit. So when an "8" is displayed, it will be dimmer than when "1" is displayed, because more segments have to share limited current.

The brightness of your displays will be quite limited, because you will have to choose resistor values to limit the current sourced or sunk by the '595 chips. The limits given in the data sheet are 35mA for any single pin, and 80mA for the chip as a whole. Do you know how to work out how to calculate the resistor values to achieve that? Assuming you do, would the displays be bright enough? If not, some means of boosting the current will be needed, such as using transistors.

I'm multiplexing digit by digit and you've just explained why I've been seeing some segments and digits light up at varying brightness levels. I wasn't sure what the difference between the two was so thanks for clearing that up.
I've been putting off calculating the R values because I'm using 3 different colours which I figured would have different values. Just did the calculations based off the data sheets and it turns out blue and green have the same specs.
Leaving the current limiting aspect out, red wants a ~90ohm resistor and blue/green a ~70ohm resistor for normal operation.

With one pin only being able to sink 35mA current, and each digit requiring a pin for all 8 LED's to sink to, I'm guessing the 35 needs to be divided by 8. For simplicity I'll drop the decimal point for the experiment, for a total of 7 LED's to divide it into a nicer value of 5. Only one segment is on at a time so the 80mA max shouldn't be reached even if there's a crossover with 2 digits.
This gives us ~360ohm for red and ~550ohm for blue & green.
I've got a few pnp transistors laying about but there's not much point adding them in for now since this won't be going in the box anyway.

Does the value of the bypass capacitor have any sort of tolerance either way or does it need to be
0.1uf? Just wondering if the 0.47uf's are doing more harm than good at the minute..

This goes back to shifting out the 0's and 1's that confused me initially, but seems necessary as ShiftOutX updating the registers each time a pin is set would easily cause the flickering. I'll give it a go with your code as the base and report back. Hoping I'll understand it a bit better this time around :grinning:

Did you mean digit?

Is that right?

Or is it 3 x 8 LEDs?

Good catch, yes digit not segment. Apologies for being unclear.

As only one digit is on at a time though, shouldn't it only be 8 LED's being sunk at a time? There's 3 separate modules, all wired into the same pins, but none of the individual digits are powered on at the same time

I guess those might be ok. I assume they are ceramic? They need to be placed close to the Vcc & ground pins of each chip. When breadboarding, I often put them on the breadboard power rails, next to each chip.

They are indeed ceramic. I've got them placed over the top of the IC going from pin 16(vcc) to pin 8(gnd).

1 Like

Ah, ok, yes 8 LEDs. But that means each digit is only on for 1/18th of the time, ie about 5% of the time. Very dim, and possibly very flickery!

That glaringly obvious aspect I hadn't actually considered, I thought that these type of displays worked in that manner anyway so didn't think the timing would be visible.. I'll strip it back to two to confirm the code still works without visible flashing on one module, then I'll add a 3rd digit and see if it reintroduces the flashing at a different rate.
If it does then I don't think it'll matter what code I use with this circuit set up, more digits will always increase the flashing.

When I first saw your schematic, the way I interpreted it was that the 3 74hc595 connected to the segments would enable you to drive 3 digits simultaneously, so that each digit would be lit for 1/6th of the time, ie about 17% of the time, which would increase brightness and reduce flickering. But you would then have the problem that the 4th 595's pins would need to sink current from up to 3x8 segments at a time, forcing you to increase the resistor values and losing the brightness advantage, unless transistors were used.

As all the segments are driven by a single 74hc595 if I powered 3 digits on at the same time they'd all show the same number. I could use one shift register per module to power the segments and drive 3 digits at a time but then I'm going to need a total of 12 shift registers for the whole thing. I may try it this way after testing the code on 2 digits again.
Lots of wire and breadboard needed though so definitely not going in the box that way, even if it works haha