Multiplexing two 7-Segment displays (Ghosting issues)

I am currently working on a simple scoreboard using two 7 segment displays, a shift register (74HC595N), two PNP transistors (2N3906) and an arduino uno.

Each 7 segment display is common anode, however one display is Blue with a forward voltage of ~3.3V, the other display is Red and has a forward voltage of ~2V.

I am using 220 Ohm current limiting resistors in series with the shift register and the cathodes of the LEDs. (I suspect this might be part of my problem as each display has a different voltage drop across the LEDs.)

I am attempting to multiplex the displays however I am experiencing issues with ghosting. I am using timer1 on the arduino in order to facilitate this behavior.

I have set up the timer with the following code:

// Setup TIMER2
/* First disable the timer overflow interrupt while we're configuring */
TIMSK2 &= ~(1<<TOIE2);

/* Configure timer2 in normal mode (pure counting, no PWM etc.) */
TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
TCCR2B &= ~(1<<WGM22);

/* Select clock source: internal I/O clock */
ASSR &= ~(1<<AS2);

/* Disable Compare Match A interrupt enable (only want overflow) */
TIMSK2 &= ~(1<<OCIE2A);

/* Now configure the prescaler to CPU clock divided by 128 */
TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits
TCCR2B &= ~(1<<CS21);             // Clear bit

/* We need to calculate a proper value to load the timer counter.
* The following loads the value 131 into the Timer 2 counter register
* The math behind this is:
* (CPU frequency) / (prescaler value) = 125000 Hz = 8us.
* (desired period) / 8us = 125.
* MAX(uint8) + 1 - 125 = 131;
*/
/* Save value globally for later reload in ISR */
tcnt2 = 5;

/* Finally load end enable the timer */
TCNT2 = tcnt2;
TIMSK2 |= (1<<TOIE2);


On timer overflow the following function executes:

ISR(TIMER2_OVF_vect){

// Turn off the active display
if(active_display == 0){
digitalWrite(BLUETRANS, LOW);
}
else{
digitalWrite(REDTRANS, LOW);
}

delayMicroseconds(1000);

// Toggle Display
active_display ^= 1;

// Shift out screen bits
if(active_display == 0){
displayDigit(blueByte);
}
else{
displayDigit(redByte);
}

// Turn display back on
if(active_display == 0){
digitalWrite(BLUETRANS, HIGH);
}
else{
digitalWrite(REDTRANS, HIGH);
}
TCNT2 = tcnt2;
}


I do not fully understand the use of Timer2, so any help there would be appreciated as well. I understand that the timer counts up until the specified overflow value and then executes the ISR. However, my attempts at reducing the refresh rate of the two displays seems to make my program un responsive.

I believe I am correctly multiplexing the display:

1. Turn off active display
2. Shift bits into the shift register for other display
3. Turn on other display

Unfortunately I cannot seem to reduce the ghosting in the red display (the ghosting does not seem to occur in the blue display).

Any help would be greatly appreciated!

I understand that I may not have included all relevant information, so ask and you shall receive!

Thanks!

EDIT 1

Thanks to Justing I now have a much better grasp on how Timer2 works on the Arduino. Thank you for that.

Unfortunately even at 60 Hz I can see a significant ghosting effect, along with a nasty, noticeable flashing as it alternates between the displays. With my new knowledge of Timer1 I was able to successfully increase the refresh rate up to 244Hz. My current circuit follows this basic design:

As stated previously my current limiting resistors are 220 Ohms. Could the differing forward voltages between the BLUE 7-seg display and the RED 7-seg display be causing this ghosting issue? Again, the only digit experiencing this issue is the RED display, the display with the lower forward voltage (2V [red] vs 3V [blue]). If this is indeed the cause would using an additional 8 resistors for the second display fix this issue? I was hoping I could get away with fewer resistors to save myself some soldering in the future but if it fixes this display issue then it would be worth it.

Any more ideas guys? Thanks!

EDIT 2

I am posting my displayDigit function:

void displayDigit(byte screen){
// Shift data into the shift register
digitalWrite(latchPin, 0); // LATCH LOW TO SHIFT
shiftOut(dataPin, clockPin, MSBFIRST, screen);
digitalWrite(latchPin, 1); // ENABLE SHIFTED BITS
}


EDIT 3

Could my 2N3906 PNP transistors be to blame? It seems to me that while the transistors allow the display to turn on very quickly, turning them off occurs much slower. Allowing the red digit to "see" the previous digits value. However, this does not explain why there is no ghosting on the Blue display. Can I easily "pull-up" the anode of the display with a resistor to aid the transistor in its "off" state? "Off" here is technically Vcc (5V) because of the Common Anode design. The display operates when the anode is pulled to ground.

FINAL EDIT

My thanks go out to Myforwik for providing the most succinct and also the most helpful answer to my question. My ISR now looks like this:

ISR(TIMER2_OVF_vect){

// Turn off the active display
if(active_display == 0){
digitalWrite(BLUETRANS, HIGH);
}
else{
digitalWrite(REDTRANS, HIGH);
}

delayMicroseconds(1000);

// Toggle Display
active_display ^= 1;

// Shift out screen bits
if(active_display == 0){
displayDigit(blueByte);
}
else{
displayDigit(redByte);
}

// Turn display back on
if(active_display == 0){
digitalWrite(BLUETRANS, LOW);
}
else{
digitalWrite(REDTRANS, LOW);
}
TCNT2 = tcnt2;
}


It turns out that I was overlooking some of the most fundamental parts of my code. Both screens were on at any given instant, plaguing me with this ghosting issue which wasn't really a ghosting issue at all.

Many thanks to justing who provided possibly the best explanation I have ever seen on how to set up the timer on the Arduino. Mike DeSimone and Oli Glaser also helped a great deal when it came to troubleshooting my circuit. I cannot express to you guys how much I appreciate your help!

While my actual issue was extremely basic, I hope all of the responses found on this page can be of use to someone, somewhere in the future!

Thanks again!

• I don't think different resistors will change much (maybe make it brighter or dimmer, no "ghosting".) Try fine tuning the total refresh rate to about 100-150 Hz (ISR happens that often). – justing Sep 14 '12 at 4:46
• get rid of delayMicroseconds(1000); in the ISR....Nevermind, realized you dont have latching shift registers – justing Sep 14 '12 at 4:50
• I removed 'delayMicroseconds(1000) from my ISR and the ghosting has reduced in brightness but not to an acceptable amount. I also fine tuned the refresh rate to 150 Hz and now the screens display normally. 120 Hz was just a little to slow for my eyes. Now if we can only figure out this lingering ghosting issue.... – Tim Bueno Sep 14 '12 at 4:58
• Can you explain what you mean by ghosting? How about just do a non interrupt loop in you main finction that does the toggling with a delay in between and no other interrupts at all and no other code running. – Myforwik Sep 14 '12 at 7:59
• delay inside an ISR? that's just wrong, perhaps make an with dark states and light states. – Jasen Jan 12 '16 at 10:57

The problem is you have on and off mixed up.

The pnp transistor will be at cutoff when the output is high, not low.

So you are turning both of your displays on during the 1000us wait.

• Hmmm, now that's a good point :-) Can't believe I missed that... +1 – Oli Glaser Sep 14 '12 at 14:20
• Amazing! I cannot believe that I overlooked this myself. I wish I could give everyone here the credit for the answer to my ghosting problem. I learned a great deal about transistors, timers and interrupts over the past day. THANK YOU EVERYONE! – Tim Bueno Sep 14 '12 at 16:45
• Whenever you do circuits like this, it's best to remove all code and start with simple tests: turn one output on, and do nothing else. Then simulate interrupt with on delay off delay in a while(1) loop. Eliminate hardware problems, then move onto software. – Myforwik Sep 15 '12 at 0:56

To supplement Oli's answer I will go ahead and walk through the Timer2 setup code. I will assume the Arduino is running at a 16MHz clock (google search revealed this is prob default) and the chip is an ATMEGA328P.

TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
TCCR2B &= ~(1<<WGM22);


As the comment says, this part disables the special features of this counter (PWM, fast PWM, etc). It can be seen that the WGM2 bits are split between control registers A and B:

It can be seen that the initial values of these register upon reset are all zeros anyways, so the above code really isn't even needed.

Next we configure the clock source:

ASSR &= ~(1<<AS2);


The register and bit description above again show us clearing a bit, this time the AS2 bit. This states that the timer will be counting from the main system clock IO clock. The alternative is described as an external crystal that could be used. (may be slower, less power, better accuracy)

The next line diables an output compare interrupt:

 /* Disable Compare Match A interrupt enable (only want overflow) */
TIMSK2 &= ~(1<<OCIE2A);


The description describes what is being disabled. Why you code bothered to disable only out compare A and not output compare B (which are both off by default in the first place anyway) is beyond me! Next!

 /* Now configure the prescaler to CPU clock divided by 128 */
TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits
TCCR2B &= ~(1<<CS21);             // Clear bit


Time to set timer's clock speed! This will prescale (divide) the IO clock we selected earlier to feed the timer. This will allows for a lower clock speed that is more practical for our application for the timer.

The above table shows that clearing the CS20 and CS22 bits and setting the CS21 but will prescale the IO clock by 128. This will mean the clock feeding the timer2 will be:

 16MHz/128 = 125 kHz


which means a clock pulse every:

 1/125kHz = 8us


which would overflow an 8 bit timer every:

 8us*2^8 = 2ms -> 488 Hz


which is way faster than the 50 Hz that Oli suggests!

The first thing we could try would be to increase the prescaler from above to 1024 (the maximum):

 TCCR2B |= (1<<CS22) | (1<<CS21) | (1<<CS20); // Set bits


Using this prescaler would would give an interrupt interval of:

 16MHz / 1024 = 15.625 kHz


giving a clock pulse of:

 1/15.625kHz = 64us


Overflowing the timer every:

 64us * 2^8 = 16.384 ms --> 61 Hz


This gives a value very close to Oli's recommendation of 50 Hz and should give you a reasonable looking display.

If you still need want to fine tune the display you have two options:

1. Increment a variable every interrupt. Once this variable reaches a certain value perform the desired interrupt functions (update the display) and set the variable back to zero. This will give you the ability to perform the interrupt any integer multiple of the overflow interval.

2. The interrupt interval can be decreased from the prescaled value by preloading the timer counter register (tcnt2). This means the timer register will start counting at that specified value rather than zero. This value will need to be reloaded at every ISR. This method allows you to decrease the interrupt interval by an integer multiple of the prescaled IO clock being fed into the timer. (this method will not let you increase the delay)

• Thank you. I believe that I have a better grasp on on these timers work on the Arduino. Unfortunately at 60 Hz the screens flash pretty noticeably. I changed the prescaler to 256 and (with a refresh rate of 244 Hz) things look ok. Interestingly even at the relatively low refresh rate of 60 Hz I still noticed a very prominent ghosting effect on the red 7 segment display. Now I am leaning toward my circuit being the problem. Is there a way to make the "pull up" the common anode very quickly? – Tim Bueno Sep 14 '12 at 4:30
• It makes sense that 60Hz is not enough. Oli's statement I believe refers to an individual displays refresh rate. Yours is divided by 2 since you are moving between 2 displays. Maybe try getting the ISR timing to about 100-150Hz (using the tcnt2 register as I described.) – justing Sep 14 '12 at 4:39
• try using the 1024 prescaler and load tcnt2 = 100 every ISR. should give about 100Hz. – justing Sep 14 '12 at 4:49
• "50 Hz refresh" means you redraw everything every 20 ms. So if it takes you 4 steps to complete a redraw cycle, you'd need to have a step every 5 ms. I prefer refresh rates at around 100-200 Hz myself; it reduces odd illusion effects when the display or my head moves, and you don't have to worry about annoying someone else whose eyes can see rapid flicker. (You know, the kind of person that sees fluorescent lights strobe.) – Mike DeSimone Sep 14 '12 at 5:14
• @MikeDeSimone Or while you are using an electric toothbrush :P – justing Sep 14 '12 at 5:19

It appears your interrupt routine is being triggered too fast, and any faster leaves no time left to execute the main code so the uC will appear unresponsive.

For a typical display, a refresh rate around 50Hz is suitable to trick the eye into thinking it is continuous.
Adjust your timer for a period of around 10ms (100Hz divided by two displays equals 50Hz update for each display) between interrupts (if it's only an 8-bit timer use the prescaler to divide the clock down some more, or if the divider doesn't go high enough then as Mike says in the comments use a count variable in the ISR and execute the event every n interrupts. Or use the 16-bit timer)

EDIT - okay, so you fixed the interrupt timing to a more reasonable value but the flicker persists. The way you have the code in the ISR it seems that the period between turn off of one display and turn on of the other is fixed to the 1000us delay anyway, so as long as you are not calling the ISR so quickly as to make the uC unresponsive then it shouldn't matter too much (although obviously too quickly limits the time you have in your main loop to perform other stuff, so only go as fast as you need to stop flicker, say <400Hz or so)

Anyway, I would have thought the 1ms delay should be plenty to turn the PNP back off again. I'm assuming here that the pin is a push pull output with a reasonable drive for high and low (will check shortly)
So couple of things to try:

• Try changing the 2k2 base resistors to say, 470Ω Assuming the pin is capable of it, this will provide a stronger drive to charge/discharge the capacitance (do you have a long trace/wiring?)
• It's possible the PNP on the red display is "leaky". Check whether the red display ever turns off fully with all the digits on on the blue display. Changing the transistor wouldn't hurt anyway to make sure of this.

It would make some sense that the ghosting doesn;t happen on the blue display since that will have a higher forward voltage than the red display, so will turn off more quickly (and on more slowly)

If you have a scope probe the PNP base and collector lines whilst switching to see how long the transition is taking.

• I figured this was the case. However I am having trouble understanding how to bring the period down to 20ms. From what I understand the prescaler works as such: 16MHz/128 = 125000 Hz Which is once every 8uS. Now if I want it to execute every 20ms: 20ms/8us = 2500. However I cannot use (256 - 2500) for the interrupt interval. Should I consider using a 16 bit timer? – Tim Bueno Sep 14 '12 at 3:41
• Either use a 16 bit timer, or set timer 0 to 1 ms (divider = 125) and have it update the display every 20 interrupts. BTW, typical usage is to make timer 0 a system tick interrupt at 1000 Hz like this, and have it kick off various events that need to happen every N ticks or whatever. – Mike DeSimone Sep 14 '12 at 5:11

I'd like to expand on Oli's answer regarding the transistors.

BJTs, in a common emitter configuration like you're using, are basically current amplifiers: they take their base current, multiply it by their $h_{FE}$, and pull up to that much current from their collector.

So your transistor has to be able to source the maximum current (all segments) from 5 V out the collector. Given your 220 $\Omega$ segment resistors and the 2 or 3 V forward voltages, we get:

$$I = \frac{V}{R} = \frac{V_{SUPPLY} - V_{F} - V_{CESAT}}{R}$$

$$I_{RED} = \frac{5 \mathrm{V} - 2 \mathrm{V} - 0.4 \mathrm{V}}{220 \Omega} = 12 \mathrm{mA}$$

$$I_{GREEN} = \frac{5 \mathrm{V} - 3 \mathrm{V} - 0.4 \mathrm{V}}{220 \Omega} = 7 \mathrm{mA}$$

(IMHO, these seem rather high. I usually only need 2 mA average -- which would be 4 mA here due to your 50% duty cycle -- to illuminate older LEDs, and much less for newer "high-efficiency" LEDs.)

So the total currents are 84 mA for red and 49 mA for blue. Looking at the 2N3906 datasheet, I see a couple potential problems, looking at the graphs on page 3:

• The Collector-Emitter Saturation Voltage vs. Collector Current graph shows $V_{CESAT}$ sloping a lot at 50 to 100 mA. Referring to the current equations above, you can see that the segment currents will change a lot if the $V_{CESAT}$ changes even 0.1 V.
• The Typical Pulsed Current Gain vs. Collector Current graph shows $h_{FE}$ sloping down rather fast in the 20-100 mA range.

So let's find out what your worst-case base current needs to be. Let's assume a minimum $h_{FE}$ of 30 (from the "On Characteristics" table on page 2, using $I_C$ = -100 mA). That means you need 2.8 mA of drive on the base. Using a $V_{BESAT}$ of -1.0 V from the same table:

$$R = \frac{V}{I} = \frac{5 - 1}{2.8} = 1.43 \mathrm{k} \Omega$$

So the 2.2 k$\Omega$ resistors are not enough (but are enough for typical rather than minimum $h_{FE}$; typical is at least 75 from the graph on page 3) and the 470 $\Omega$ resistor should be plenty strong. Also check that your I/O pin can sink that much current.

IMHO, you're either putting too much current through each segment or just asking too much of the 2N3906. You really want your transistor to have a reasonably flat $V_{CESAT}$ across your current range.

Further, when driving high currents into 7-segment displays, I've seen "ghosting" effects where the light from one segment is simply spilling over into physically neighboring segments. Make sure this isn't happening to you: drive all segments on one display and one on the other, and see if the 6 "off" segments show different ghosting brightnesses depending on proximity to the lit segment.

Personally, I use MOSFETs for these applications. Typical $R_{DSON}$ of 1 $\Omega$ gives a lot lower voltage drop and current-change-vs.-segments-lit (as long as $R_{DSON} << R_{SEGMENT}$), gate current is so low anything can drive it (it's voltage driven, not current driven), and there's no saturation voltage to deal with.

• plus the 84mA looks to be higher then the maximum current for the 74HC595N – justing Sep 14 '12 at 16:04
• I reduced the resistor to 470 Ohms and the ghosting effect still occurs. I have ruled out light bleeding from one segment to the next as well. I am leaning toward the idea that the 2N3906 is being asked to do too much. Any recommendations on the correct PNP transistor to use? I'd be fine with using MOSFETS as well. Thank you for all of your help! – Tim Bueno Sep 14 '12 at 16:35

Often for things like this, the LED multiplexing doesn't require a complete write to the LED with every switch. Personally, I'd latch what needs to be in BOTH 7-segments, and then just use the timer interrupt ONLY to switch which LED you're driving-- and just let that interrupt run free. Upload new numbers to the latches ONLY when you need to change what's on the 7-segments.

Having to deal with the complete 7-segment right with each multiplex switch just seems like you're embedding this task at the wrong level, resulting in big inefficiencies.

Alternatively, there are IC's that do this specifically for 7-segments, like the SAA1064.