Driving 7-segment LED displays with a single resistor


What's the point?

Okay, we know that when controlling a 7-segment LED from a PIC or similar, the resistors need to be on the segment lines (one for each) and not a single resistor on the common line. The display intensities would vary significantly based on which character is being displayed (# of segments lit actually) if the latter is done. So this is the common LED-driving circuit used...

Let me state for the record, that I hate resistors. Sure, they serve their purpose, but compared to semiconductors for instance, they take up too much board space, add too many holes to be drilled, and come with a cost, albeit not much. And let's not forget the time to place these. Add to this, that my projects always seem space-constrained (sometimes by my own imposition). With that said, I'm always looking to reduce parts-count on any circuit, and resistors are always at the top of the list. Until now, I've not found a way to do this.

A lightbulb just went off in my head

I was recently using a 3-digit 7-segment display, which was quite bright. So bright that I created a software-based (PIC assembly) PWM-dimmer for it, so I could dim it with different intensities for day and night. I was trying to match the red-LED instruments in my Audi and realized that not only did I have to dim it at night, but I also had to dim it during the day to match. But this was a common Lite-on 7-segment display, and I found similar brightness with a small Stanley 7-segment display. So the idea was born...
What if I dynamically varied the intensity of the LED's based on what character was being displayed? I could compensate for the varying intensities of the various characters. Sounds good in theory, but could it be pulled off?

On paper

First thing I did was to change the circuit as follows...

For the common resistor, Rled, with only one segment lit... Vres = (Vdd-Vss)-Vled = 3V (for LED's with a 2V forward voltage drop). Ires = 25mA Therefore, R = 3/.025 = 120 ohms. With all 8 segments lit, there is still 25mA going thru Rled because the forward voltage drop of each LED segment is still about 3V, but this current needs to be shared between all segments, so each segment gets 25mA/8 = 3.125mA. Ouch!

I've divided by 8 because the decimal point was included. The Lite-On display's parameters on the datasheet were measured at 20mA, and permitted a peak of 90mA. So 3.125mA would be way too low to be useful. Or would it? In my original/traditional circuit, I was using 270-ohm resistors (which is what I had on hand), which calculated to roughly 11mA. About half of what the "normal" segment drive current for this display. And I found that to be bright. So I changed the 8 segment-drive resistors (in the original circuit) to 1k each for a quick test, yielding about 3mA per segment. And it was still quite decent! This was amazing, because it was also being multiplexed as one of 3 digits, a 33% duty-cycle.

Design details

So everything else is now a software issue, to vary the duty cycle of each digit based on how many segments were lit for that character. But what is the correlation (theoritical requirement) between # of segments and duty-cycle? Not only did I not know, but I also did not know where to get that information. So time to experiment with code.

Timing

For the PWM dimmer timing, I've decided that for the first pass, I'll leave the duty-cycle at (# of segments lit) / (total # of segments). With 8 segments (remember the decimal point), the "8" would have 100% duty-cycle, and the "1" would have 25% duty-cycle. However, to keep the granularity, the duty-cycle would be left with 8 counts per cycle. This may mean going to a higher frequency though.
Now comes the difficult part of how to "interleave" the PWM timing with the multiplexing routine. With some fancy math, I could figure out the lowest common duty-cycle factor from all 3 digits, and spread the on-time over the full 3-digit period, etc, etc. But I wasn't prepared to all of that for an unknown experiment, so I decided to make life easy on myself by having 8 steps for the first digit, 8 for the second, and 8 for the third. 24 total steps for a full cycle. I was using 3 digits since I had just completed another PIC project with had 3 7-segment digits, and I could easily use the circuit and code for a starting-point. That circuit was running flicker-free at 300Mhz, but again, I felt it would need to go up with this long cycle time. Here's what the timing would look like...

You'll see from this diagram the characters "1", "8". (with the decimal point) and "5" being displayed on digits 0, 1, and 2 respectively, with a total of 24 timing counts for a full cycle. The sad part about this, is that the digit 0, showing a "1" has a total of 2 time-counts in every 24 on which it's on. This translates to a net duty-cycle of 8.3% with 12.5mA going to each segment. Now I'm losing faith.

Implementing the code

For the code, I took some existing code and modified and butchered it until I questioned why I didn't just write it from scratch. I even kept the original chip, in case you're wondering why I didn't use a 16F628 or similar PIC. But it can be easily adapted in case you're wondering.
I think I've managed to clean up all the code, except that it doesn't match the circuit above exactly ... in the code, I was using Port C and A, but if you try it, it will be very straightforward to change.

Showtime!

Okay, it wasn't this straightforward, since I got odd results at first and found one stupid bug where I was using movf ...,F instead of movf ...,W. Arrrggghhh!!! But once it was sorted out, I found that I absolutely had to raise the oscillator speed. But 500khZ was all that was necessary. I was happily amazed!... the display was completely uniform, and also amazingly, all characters were quite bright still. I was expecting to run thru various math algorithms to see how to distribute the duty cycle based on segments lit, but this was perfect, as I could not tell if any character was brighter than the other. I setup the code initially with an A/D converter so I could manually change to any digit-combo I wanted, but later changed it to a counter. It seems that "181" on the display would be an excellent digit-combo to notice any difference, but I could not tell. And neither could anyone else. I modified the code again to eliminate the varying PWM duty-cycles, giving every character a full 8-step (of 8), 100% duty-cycle, and could immediately see the horrible varying-intensity characters.

Code and Circuit Notes

Ah yes, the code is provided here for anyone that is interested. If you decided to implement/experiment with it, remember to change the ports on the code or circuit to match. Also, apologies if I managed not to clean up any thing from the original application of this code. If you question why I did something in a certain way (like move something to a register and not use it), it's probably a left-over from the previous app. But I have compiled and tested it so I know it works.
The last experiment was using a 5.1K resistor and 22pf cap for the RC oscillator. If you want to really see what the code is doing, drop in a much larger cap (like .1uF) and count the display-update pulses at about 1 per second. Neat debugging tool.