|
You Light Up My Micro
As published in MicroComputer Journal Jan/Feb 1998
I have an idea for a project, but it needs some way to get input to the microcontroller and some
way to display information from the micro. Let's start taking a look at I/O, with the
concentration this time on the O.
Sometimes you just need to be able to light up an indicator to give you an idea of what is
happening - power on. At other times it would be nice to display several numbers, or even
words. We'll start with a simple light, move up to numbers, and maybe next time, words.
The most common indicator these days is the light emitting diode, or LED. Just a few years ago,
they didn't exist. Now they are in everything, because of their low cost, long life, low current
requirements and overall colorful personality.
LEDs come in red, orange, yellow, green and even blue is becoming reasonably priced. In
general, they require around 2 volts to operate, though some have built-in regulators to run from
5 volts and some even have flasher circuits built-in. You can get a single LED for an indicator
light, or you can buy several all packaged up in a matrix of dots or numeric segments.
Since LEDs run on about 2 volts, you can't shove 5 volts through them and expect them to take it
like a man. You need a simple resistor in series to absorb some of the voltage and limit the
current the LED can draw. You should check the specifications for your particular LED to
calculate the exact resistor value. Of course, you can rarely find the specs on the generic LED
you just got from your parts house, so we'll just use generic specs: 2 volts at 10 milliamps (ma)
of current. You can choose more or less current depending on how bright you want the LED (up
to a point) and how efficient your LED is (some light up with just 1ma of current.)
So if we start with 5 volts and the LED drops 2 volts, our series resistor needs to drop the
remaining 3 volts. Using the old V = IR equation (no, it isn't just for infrared LEDs), V is 3
volts, I is 10ma (.010 amps), we can solve for R: R = V/I = 3/.010 = 300 ohms. That didn't hurt
too much did it?
Of course, finding a 300 ohm resistor could be a pain. The more common ones have odd,
non-decimal values. The closest ones for us are 270 ohms and 330 ohms. I'm going to choose 270
ohms which will increase our current a little, making the LED slightly brighter. Rearranging our
equation once more: I = V/R = 3/270 = .0111 amps or 11ma.
The last little gotcha is that LEDs are one-way, one side is plus, the anode, and the other is
minus, the cathode. If you hook it up backwards, you can damage the LED, but usually it just
won't light up. Most LEDs have one lead longer than the other, the longer one being the plus
side (anode.) If you have cut off the legs before checking, you may find that the plastic case is
flat towards the minus lead (cathode.)
After all of this, we can finally hook up our power LED as shown:

As you can see from Figure 1, it doesn't really matter which side of the LED the series resistor is
connected to. I know this may be way too basic for some readers, but judging from the phone
calls I get, this all needs to be said. Besides, we'll get in over everybody's head by the end of
this article.
So far we have an LED that lights up any time power is applied, but how do we control it? We
have a choice of connecting one end to ground and turning on and off 5 volts from an I/O pin, or
we can connect that end to 5 volts and turn on and off ground. Depending on the application,
one way or the other might be appropriate. In many cases, I/O pins can sink more current than
they can source. In other words, the drivers that put out ground are more powerful than the
drivers that put out 5 volts. So it might be better to connect the LED directly to 5 volts and turn
on and off ground to the other end. This confuses some people, so feel free to connect one end to
ground and turn on and off 5 volts from the I/O pin. One final consideration is to be sure that the
I/O pin of the microcontroller you are using can actually sink or source the necessary 11ma of
current. In the case of a PIC®, we can drive well beyond that puny amount.

Now let's hook up 2 LEDs. It should be about as simple as one and besides, we only need one
current limit resistor right? Well, not exactly. Take a look at Figure 3. In the drawing on the
left, you can see 2 LEDs connected to 2 I/O lines with 2 series resistors. This is usually what
you want to do. Consider the drawing on the right. It should do the same thing with only one
current limit resistor - you just saved .005 cents. And if you only turn on one LED at a time, it
actually will work fine.

But look what happens when you turn on both LEDs at the same time. The resistor will still
follow Ohm's law as described above, drop 3 volts and limit the current to 11ma. However, now
that current has to be shared by 2 LEDs. If you are lucky, each LED will get 5.5ma. But it is
more likely that the split will be uneven and one will draw more current than the other. In either
case, the LEDs will be quite a bit dimmer than if each had its own allotment of 11ma.
The normal end to this story would be: use 2 resistors. But we have a microcontroller on duty.
Perhaps the biggest benefit of a microcontroller is its ability to trade hardware for software.
Maybe you just don't have the extra .005 cents for the resistor, not to mention the assembly cost
and board space required for it. Remember we said earlier that as long as we don't turn on both
LEDs at the same time, things will work out fine with only one resistor. What we can do with a
microcontroller is time-multiplex the LEDs so that only one LED is on at a time. By rapidly
turning on and off each LED in sequence, it will appear as if both are on at the same time. It has
something to do with the way our eyeballs and brains work, persistence of vision. It is similar to
the way that TV works, we can see a steady picture even though there is nothing good on.
Next let's step up to 7 LEDs. And let's arrange them in the shape of a figure 8 like this:

Wow, with this kind of configuration we can build the numbers 0 - 9 and even some letters. I
should patent this idea. Anyway, a 7 segment display consists of 7 individual LEDs with one
end of each LED connected together. If all of the plus ends are connected together, it is called
common anode. If all of the minuses are connected together, it is called common cathode.
Whether you choose common anode or common cathode depends on how you want to hook
things up. I am going to choose common anode with all the pluses connected together for
reasons you will see shortly. Figure 5 shows one way to connect the display to a PIC. The 8
cathode lines are connected to the 8 PortB lines through series resistors. 8? What happened to 7
segments? I have also connected the decimal point for good measure. The common anode line
is connected to 5 volts.

We could use the technique described above and put only one current limit resistor in the
common line to 5 volts. However we would have to light up each segment one at a time over
and over again in order to display a complete character. We would need to do this at least 100
times per second (1000 times per second is better) to cut down flicker. If you only have one 7
segment display in your project, this could be workable. But we are going to add more.
To add another display, we could use 8 more I/O lines, connecting the next display to PortC, for
example. While it makes programming easier, it eats up I/O pins in a hurry. You could also add
separate latches or even decoders for each display, but, once again, we are going to use software
instead of hardware.

As you can see in Figure 6, the cathodes from a particular segment of each display are all
connected together and then connected to a PIC I/O line through a series resistor. We have also
removed each common anode from 5 volts and made it controllable by a PIC I/O pin. In this
manner, we can set up the segment value for a given display and then briefly turn on the anode
for that display. If we do each one fast enough, we will see the entire display, flicker free.
Remember earlier we chose common anode? Let's take a look at current considerations to see
why. The PIC is now driving up to 8 LEDs (segments) at a time off a single port. Just as each
I/O pin has a limit to the amount of current it will source or sink, each port also has a limit. To
do the least damage, we are sinking the current to ground rather than sourcing it to 5 volts.
You'll also note in the figure that I snuck in a transistor between the PIC and the common anode
to each display. Since the current for the entire display runs through this one pin, it could
overload a single I/O line. The transistor amplifies the current drive capability of the I/O line to
solve this dilemma. It is easy to operate, put ground on the I/O pin to turn on the transistor and
enable a given display, put 5 volts on the pin to turn off the transistor and the display.
Now all we need is software. The first task is to figure out how to get numbers (and even some
letters) on the display. All we really have right now are a bunch of individual LEDs connected to
I/O pins. To form characters, we need to come up with a way to turn on specific ones while
leaving others off.
Let's look at the case of 8. Eight is pretty simple. It is all the LEDs (except the decimal point)
on. To display 8 we need to put a binary 1000 0000 on PortB. This will turn on all the LEDs
except the decimal point. 0 = on? Remember, we are controlling the minus side of the LEDs.
We also need to put a value onto PortA to address a specific LED digit, 1110. Once again, 0 will
turn on one of the transistors and 1 will ensure the others are off.
The number 0 is simply an 8 with the middle segment off, 1100 0000. Now we could have a
subroutine to decode each digit, but that gets long and I hate to type (hard to tell?) The easiest
way to proceed is by using a lookup table. This method is particularly effective when you have a
limited number of combinations of desired states, in our case 10 (0-9.)
| Number |
Segments |
Hex |
| 0 |
1100 0000 |
$C0 |
| 1 |
1111 1001 |
$F9 |
| 2 |
1010 0100 |
$A4 |
| 3 |
1011 0000 |
$B0 |
| 4 |
1001 1001 |
$99 |
| 5 |
1001 0010 |
$92 |
| 6 |
1000 0010 |
$82 |
| 7 |
1111 1000 |
$F8 |
| 8 |
1000 0000 |
$80 |
| 9 |
1001 1000 |
$98 |
Now by simply using the number we wish to display, 0 - 9, as the index into the lookup table, we
can easily find the proper value to put onto the I/O port. You can even extend the lookup table to
include some letters, a - f perhaps.
In Microchip assembler, the code looks something like this:
movlw 1 ; Display a 1
call bin2seg ; Convert number in W to the segment value
movwf PORTB ; Send value returned in W to the segments
movlw H'1E' ; Turn on the first digit
movwf PORTA
loop goto loop ; Wait here
;
bin2seg addwf PCL, F ; Jump into the lookup table
retlw H'C0' ; Return segment code for 0
retlw H'F9' ; Return segment code for 1
retlw H'A4' ; Return segment code for 2
retlw H'B0' ; Return segment code for 3
retlw H'99' ; Return segment code for 4
retlw H'92' ; Return segment code for 5
retlw H'82' ; Return segment code for 6
retlw H'F8' ; Return segment code for 7
retlw H'80' ; Return segment code for 8
retlw H'98' ; Return segment code for 9
The jump table feature of PICs is pretty amusing. There is actually no other way to get constant
data into a program, other than moving literals, of course. The jump works like this: You load W
with the index to the particular item you want out of the table. You then call the jump routine.
There is one instruction at the top of your jump table. It says to add W to the current program
counter. This effects the jump right to your item. The item itself is a line that says return the
constant value you want in W, right back to the caller. It is pretty quick and simple, once you get
the hang of it. The one gotcha here is that the table must be all in one page. You also need to
preset PCLATH if it is not in the page you are in.
In PICBASIC, the display routine looks something like this:
loop: For B1 = 0 To 9 ' Count from 0 to 9
B0 = B1 ' Pass the count to the conversion subroutine
Gosub bin2seg ' Convert to segment value
Poke PortB, B0 ' Put segment values out to LED
Poke PortA, $1E ' Turn on the first digit
Pause 1000 ' Display it for 1 second
Next B1 ' Do next
Goto loop ' Do it forever
' Convert binary number in B0 to segments for LED
bin2seg: Lookup B0,($40,$79,$24,$30,$19,$12,$02,$78,$00,$18),B0
Return
This gets you the basic format to light up a single digit. All that remains is to write the code that
separates a binary number into the 4 decimal digits and display each digit. It's kind of a pain to
write in assembler, so here's some more PICBASIC code:
' PICBASIC subroutine to send the binary number (0 - 9999) in W1 to LEDs
display4: B0 = W1 / 1000 ' Find number of thousands
W1 = W1 // 1000 ' Remove thousands from W1
Gosub bin2seg ' Convert number to segments
Poke PortB, B0 ' Send segments to LED
Poke PortA, $17 ' Turn on fourth digit
Pause 1 ' Leave it on 1 ms
Poke PortA, $1F ' Turn off digit to prevent ghosting
B0 = W1 / 100 ' Find number of hundreds
W1 = W1 // 100 ' Remove hundreds from W1
Gosub bin2seg ' Convert number to segments
Poke PortB, B0 ' Send segments to LED
Poke PortA, $1B ' Turn on third digit
Pause 1 ' Leave it on 1 ms
Poke PortA, $1F ' Turn off digit to prevent ghosting
B0 = W1 / 10 ' Find number of tens
W1 = W1 // 10 ' Remove tens from W1
Gosub bin2seg ' Convert number to segments
Poke PortB, B0 ' Send segments to LED
Poke PortA, $1D ' Turn on second digit
Pause 1 ' Leave it on 1 ms
Poke PortA, $1F ' Turn off digit to prevent ghosting
B0 = W1 ' Get number of ones
Gosub bin2seg ' Convert number to segments
Poke PortB, B0 ' Send segments to LED
Poke PortA, $1E ' Turn on first digit
Pause 1 ' Leave it on 1 ms
Poke PortA, $1F ' Turn off digit to prevent ghosting
Return ' Go back to caller
Don't forget you want to call this routine continuously, or at least as often as possible, to reduce
the amount of flicker. Of course, this is the biggest drawback with using LEDs: You have to
continuously scan the data out to the displays. If the program has nothing better to do, it is no
big deal. Next time you'll see why I prefer to use LCDs.
Sample program for
PICBASIC PRO
|