microEngineering Labs, Inc.
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.)
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.
microEngineering Labs, Inc.
2845 Ore Mill Road, STE 4
Colorado Springs CO 80904
(719) 520-1867 fax