How To: Prevent from ghosting on seven-segment display - Page 1
Forum section: electronics / general

author: danchouzhou
author_rank: Contributor
timestamp: April 09, 2025, 12:54:51 pm
content: Many people encounter the issue of "ghosting" when implementing multiplexed scanning to drive seven-segment displays. Recently, I had a project that used multiplexed scanning, so I decided to write an article documenting the process for others to reference in the future.To explain the specifics of "ghosting," you can refer to the comparison images below. The left image shows each digit with a "ghost" of the adjacent right digit, whereas the right image demonstrates a properly handled multiplexed scanning process.This project's requirements were not complicated: the client wanted a square wave generator with an adjustable output frequency of 1~6 kHz, along with a power-off memory feature. After restoring power, the device should output the last adjusted frequency. Of course, the frequency adjustment should be visible to the user, so I opted for a four-digit seven-segment display.For this project, I chose theNuvoton MG51FB9AEbecause it operates at 24 MHz, whereas the commonly used N76S003 runs at 16 MHz. These two chips are pin-to-pin compatible. The higher clock speed is needed not for CPU performance but for output frequency resolution. The higher the input frequency for PWM or Timer, the greater the division steps for the output, and consequently, the higher the frequency resolution. Readers who require precise output frequency or duty cycle should consider this aspect in their selection criteria.What is multiplexed scanning?The schematic below is part of the project's circuit. It has anEverlight ELF-511- a standard four-digit common cathode seven-segment display. The segments a~g and dot of each digit are directly connected inside the display, while the common cathode for each digit is separate.The signal from P0 will only activate one of Q1~Q4 at a time, and while active, P1 sends corresponding data for segments a~g and dot. In short, this process lights up each digit in turn. When the switching speed exceeds the persistence of vision, all digits appear continuously illuminated.Advantages of multiplexed scanningIf each digit of the four-digit seven-segment display were independently controlled, its common points would directly connect to the power supply, and requiring 32 pins to control (4 × segments a~g and dot).With multiplexed scanning, only 12 pins are needed: 8 for segments a~g and dot and 4 for scanning lines. This reduction is significant for design, benefiting BOM cost, circuit size, and layout complexity. For example, with just 12 pins, the project can be completed using the MG51FB9AE in TSSOP20 package. If 32 pins were required, an MCU with at least a 48-pin package would be necessary.Firmware implementationFirst, an array `u8segments` is created to map 0~F (hexadecimal 0x0~0xF). Through modulo operations and a switch-case statement, the units, tens, hundreds, and thousands digits of a number `u32num` are extracted, looked up in `u8segments[]`, and sent to P1. The switch-case selects the current digit based on `u8digit`, which is simultaneously output to P0 as the scanning line signal. The variable `u8digit` shifts left one position with each iteration (e.g., 0x10 -> 0x20 -> 0x40 -> 0x80) before cycling back to 0x10. This process synchronizes the active digit with the number being output to P1. The entire routine is wrapped into a `showNum()` function, which is repeatedly called every 1 ms to complete the scanning process.Common mistakes: Incorrect sequence causing ghostingSwitching scanning lines before updating the segment data can lead to ghosting. Since the program executes sequentially, so when the scanning line switches to the next digit, the previous digit's data is still being output, causing the previous digit to briefly appear. Observing the implementation, the scan moves from right to left, which explains the shadowing from the adjacent right digit.Code:void showNum(uint32_t u32num){static uint8_t u8digit = 0x10;P0 = u8digit;switch (u8digit) {case 0x10:P1 = u8segments[u32num % 10];break;case 0x20:P1 = u8segments[u32num / 10 % 10];break;case 0x40:P1 = u8segments[u32num / 100 % 10];break;case 0x80:P1 = u8segments[u32num / 1000];break;default:u8digit = 0x10;}if (u8digit < 0x80)u8digit = u8digit << 1;elseu8digit = 0x10;}Correct implementationTo resolve this, the output must be briefly disabled before switching to the next digit to prevent the previous digit from momentarily displaying. In this case, I first set `P0 = 0;` to disable the scanning line output, then update `P1` with the segment data for the next digit. Finally, I set `P0 = u8digit;` to activate the scanning line for the new digit. Alternatively, the output can be briefly disabled by turning off the data lines. A possible sequence is as follows:Disable scanning lines > Output new data > Update scanning linesDisable data lines > Update scanning lines > Output new dataCode:void showNum(uint32_t u32num){static uint8_t u8digit = 0x10;P0 = 0;switch (u8digit) {case 0x10:P1 = u8segments[u32num % 10];break;case 0x20:P1 = u8segments[u32num / 10 % 10];break;case 0x40:P1 = u8segments[u32num / 100 % 10];break;case 0x80:P1 = u8segments[u32num / 1000];break;default:u8digit = 0x10;}P0 = u8digit;if (u8digit < 0x80)u8digit = u8digit << 1;elseu8digit = 0x10;}Full code:Code:#include "numicro_8051.h"__code const uint8_t u8segments = {// abcdefg.0b11111100, 0b01100000, 0b11011010, 0b11110010, 0b01100110, 0b10110110, 0b00111110, 0b11100000,0b11111110, 0b11100110, 0b11101110, 0b00111110, 0b10011100, 0b01111010, 0b10011110, 0b10001110};void showNum(uint32_t u32num){static uint8_t u8digit = 0x10;P0 = 0;switch (u8digit) {case 0x10:P1 = u8segments[u32num % 10];break;case 0x20:P1 = u8segments[u32num / 10 % 10];break;case 0x40:P1 = u8segments[u32num / 100 % 10];break;case 0x80:P1 = u8segments[u32num / 1000];break;default:u8digit = 0x10;}P0 = u8digit;if (u8digit < 0x80)u8digit = u8digit << 1;elseu8digit = 0x10;}void main(void){uint32_t u32num = 1234;TA = 0xAA;TA = 0x55;CKEN |= 0xC0; // EXTENwhile(!(CKSWT & 0x08)); // ECLKSTTA = 0xAA;TA = 0x55;CKSWT |= 0x02; // Switch to external clock sourcewhile(CKEN & 0x01); // CKSWTFTA = 0xAA;TA = 0x55;CKEN &= 0xDF; // Disable HIRCP0M1 &= 0x0F;P0M2 |= 0xF0;/* P1 as push-pull output mode */P1M1 = 0x00;P1M2 = 0xFF;CKCON |= 0x08; // Timer 0 source from Fsys directlyTH0 = (uint8_t)(41536 >> 8); // 65536 - 24000TL0 = (uint8_t)(41536 & 0xFF);TMOD |= 0x01; // Timer 0 mode 1TCON |= 0x10; // Timer 0 runwhile(1){showNum(u32num);/* 1 ms delay */while (!(TCON & 0x20)); // Wait until timer 0 overflowTH0 = (uint8_t)(41536 >> 8); // 65536 - 24000TL0 = (uint8_t)(41536 & 0xFF);TCON &= 0xDF; // Clear TF0}}

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 09, 2025, 04:34:32 pm
content: A small detail but that indeed many will overlook. Similarly when multiplexing any kind of signal:- open the previous channel- change the signal- close the new channelHere you have 4 distinctly controllable switches for multiplexing, but when the multiplexer is "always on" (meaning one path is always closed whatever the configuration), then you can't avoid ghosting, so that's something to keep in mind.

author: xvr
author_rank: Frequent Contributor
timestamp: April 09, 2025, 07:28:17 pm
content: QuoteThen you can turn off segments (P1) instead of digits (P0). Ghosting will be avoided

author: PCB.Wiz
author_rank: Super Contributor
timestamp: April 09, 2025, 10:23:15 pm
content: Quote from: xvr on April 09, 2025, 07:28:17 pmYes, the main issue here is the storage time of the BJT - it just needs some small dead time of no-drive to avoid.Designers might choose to omit the base resistors and use NMOS instead, or they might use NMOS with RFI reducing gate resistors, to soften the switching edges, and use no-drive delays.Some MCUs have enough port capability to drive directly.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: April 09, 2025, 11:20:12 pm
content: Quote from: danchouzhou on April 09, 2025, 12:54:51 pmI notice Nuvoton has recently expanded their MG51 series, with a new die having more ADC and more PWM and more RAM/FLASH.Nuvoton MG51 Brief 2.0It is unclear if the new PWM can run at a higher CLK than the MCU Core?That faster PWM capability is becoming more common on MCUs.

author: danchouzhou
author_rank: Contributor
timestamp: April 10, 2025, 01:47:17 am
content: Quote from: PCB.Wiz on April 09, 2025, 11:20:12 pmFor MG51, the maximum clock rate is 24 MHz (same as core clock).I see some other models support higher frequency. For example, NUC1263 the maximum clock rate for PWM is 144 MHz which is higher than 72 MHz core clock.

author: SiliconWizard
author_rank: Super Contributor
timestamp: April 10, 2025, 12:01:12 pm
content: Not to start a flamewar at all, but just curious about what made you pick a MCU with a 8051 core when there are now thousands of very cheap 32-bit MCUs, much more capable and supported by mainstream toolchains.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 10, 2025, 12:39:23 pm
content: Quote from: PCB.Wiz on April 09, 2025, 10:23:15 pmNot here, just bad programming.Code:void showNum(uint32_t u32num){static uint8_t u8digit = 0x10;P0 = u8digit; // <----- Enable the segment first, before the new data is loadedswitch (u8digit) { // <----- Load segment dataDo we need a full article for this? What's next? How to not burn leds with a novel technique, using current limiting resistors?

author: danchouzhou
author_rank: Contributor
timestamp: April 10, 2025, 01:51:52 pm
content: Quote from: SiliconWizard on April 10, 2025, 12:01:12 pmI have been using Nuvoton's MCU for more than 10 years. Each series has similar peripherals, and I am already very familiar with them.Although I know there are many cheap MCUs on the market, the Nuvoton works fine and I have no motivation to replace it.Quote from: DavidAlfa on April 10, 2025, 12:39:23 pmMany people have asked me the same question, and even some inexperienced think that ghosting is an inevitable phenomenon. This lead me to write an article to explain it. Of course this article is not for a pro like you.

author: RoGeorge
author_rank: Super Contributor
timestamp: April 10, 2025, 02:00:48 pm
content: Quote from: danchouzhou on April 09, 2025, 12:54:51 pmMany people? More like nobody. Looks like a disguised commercial.Addressing a non existing problem with a long and tedious first post.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 10, 2025, 02:37:26 pm
content: @danchouzhou you're absolutely right, sorry, sometimes I take things for granted, or think they're obvious, but it wasn't always the case!Everyone had to start from the very bottom, the forum is and should be place for everyone, from rookie to pros.BTW I'm just a rookie "plus", nowhere close to pro!I remember the time where I struggled with delays in assembler, so I used a potentiometer to adjust the speed of the PIC RC oscillator and thus the motor speed.

author: danchouzhou
author_rank: Contributor
timestamp: April 10, 2025, 04:34:12 pm
content: Quote from: DavidAlfa on April 10, 2025, 02:37:26 pmThank you David! Don't be so modest, I saw your project it's amazing.

author: jnk0le
author_rank: Regular Contributor
timestamp: April 10, 2025, 05:29:34 pm
content: Quote from: PCB.Wiz on April 09, 2025, 10:23:15 pmIn this case issue was switching columns before segments, but those division/modulo ops provided enough dead time.Even with the proper method, common emitter will ghost if segments come from precalculated buffer.

author: peter-h
author_rank: Super Contributor
timestamp: April 10, 2025, 05:54:37 pm
content: LEDs produce a visible output even with a miniscule % of the "normal" current, so proper off slots during muxing are important.

author: prosper
author_rank: Regular Contributor
timestamp: April 16, 2025, 09:00:16 pm
content: Quote from: PCB.Wiz on April 09, 2025, 10:23:15 pmI recently had the same issue driving those 8x8 matrix LEDs. Indeed, the holding time of the BJT is the culprit. I found a few solutions. In the end, I went with a combination of #1 and #4 for my first revision, and #2 for my second rev boards.1. Slow down your multiplexing. Cycle through the common pins more slowly. Reduces ghosting, but won't eliminate it entirely (but maybe enough to be un-noticeable)2. Use FETs intead of BJTs3. Add a delay between shutting off your BJT, and updating your segments. Hold time is usually only a few hundred nanoseconds depending on base drive, so it doesn't take much4. Add a speedup capacitor in series (edit: parallel) with the base resistor (100pF is a good place to start)5. use an emitter follower configuration (though this will cost you a few hundred millivolts, and potentially lower your brightness)6. Or maybe an anti-saturation diode

author: pqass
author_rank: Super Contributor
timestamp: April 16, 2025, 09:59:00 pm
content: "...storage time of the BJT" Duh! Of course!I'd been prototyping a couple of large DIY 4" LED displays for an arcade game; each segment driven by a single white LED @20mA indirectly lighting the cavity.And I was noticing that sometimes an LED was lit by a tiny dot when it should be off completely.I'm using two cascaded 74HC595 - one for the NPN-driven segments and the other for PNP-driven common anodes - for a total of 8, 7-seg. displays.The '595s are latched at the same time so there shouldn't be misdirected segment info with the wrong digit.It's a tiny dot so it isn't noticeable with the red diffuser sheet so there wasn't any urgency to change my multiplex algorithm.For others, where even the tiny amount of light is noticeable, you can add a small cap (100pf?) across the base resistor of the digit driver (seethis link) to remove this off delay.