Overview
Well, this was supposed to be an easy one to write, but ended up taking over 50 hours to work out all the kinks! Since the multiplexing uses interrupts, there were a number of practical considerations to account for, and then trying to make it as versatile as possible (to handle any type of device) required much revision.
But what came out of all that labor is a feature-rich library unit which should save you much time and bother.
This library unit can be used either with a single 7-segment display, or via multiplexing with two to four displays. In the first case, a simple procedure with parameter passing is used to write to the single display, either with or without a decimal point.
But in the case of multiplexed displays, a different method must be used, since parameter passing to an interrupt routine is not permitted, and besides, calling a procedure from within an interrupt routine is problematic. So, we simply use a global two-, three- or four-byte buffer to hold the character string to be printed.
Compilation Constants
There are quite a few compilation constants which can be used to customize the action of the library unit. Let's dig in.
If using segment pins that are scattered around more than one port, your program will require compilation constants like these. I've shown them here with sample values.
{$DEFINE SEG_A_PORT 'PORTB'}
{$DEFINE SEG_B_PORT 'PORTB'}
{$DEFINE SEG_C_PORT 'PORTB'}
{$DEFINE SEG_D_PORT 'PORTB'}
{$DEFINE SEG_E_PORT 'PORTB'}
{$DEFINE SEG_F_PORT 'PORTB'}
{$DEFINE SEG_G_PORT 'PORTB'}
{$DEFINE SEG_DP_PORT 'PORTB'} // optional decimal
{$DEFINE SEG_A_PIN 0}
{$DEFINE SEG_B_PIN 1}
{$DEFINE SEG_C_PIN 2}
{$DEFINE SEG_D_PIN 3}
{$DEFINE SEG_E_PIN 4}
{$DEFINE SEG_F_PIN 5}
{$DEFINE SEG_G_PIN 6}
{$DEFINE SEG_DP_PIN 7} // optional decimal
Otherwise, if the segment pins are in order (0..7) within a single port (as they were in the example above), you'll simply need:
{$DEFINE SEG7_PORT 'PortB'}
With either the full-port or scattered-pins approaches, if you intend to use a decimal point, then you'll also need to define the following:
{$DEFINE USE_DP}
If using only a single device which is permanently selected via hardware (common cathode grounded, or common anode tied high) then there's no need to define any selection pins.
Otherwise, if multiplexing with more than one device, you'll need to set up at least Device 1 and Device 2 with something like the following. For three or four devices, you'll also need to specify Device 3 and Device 4, respectively. For example,
{$DEFINE DEVICE1_PORT 'PORTA'}
{$DEFINE DEVICE2_PORT 'PORTA'}
{$DEFINE DEVICE3_PORT 'PORTA'}
{$DEFINE DEVICE4_PORT 'PORTA'}
{$DEFINE DEVICE1_PIN 0}
{$DEFINE DEVICE2_PIN 1}
{$DEFINE DEVICE3_PIN 2}
{$DEFINE DEVICE4_PIN 3}
Be sure to define these in order, as required.
When using more than one display, they are assumed to be multiplexed. This automatically engages Timer0 and interrupts. But it's up to your program to specify the multiplex speed by specifying an OPTION_REG prescaler value. For example,
OPTION_REG := 0b11010011; // Timer0 prescaler: 16
Also be sure your main program enables the interrupts:
INTCON := [GIE, PEIE, TMR0IE]; // turn on interrupts
Obviously the multiplexing speed depends not only on the prescaler value, but also the PIC clock speed and number of devices used.
By default, the displays are assumed to be common cathode devices. If common anode, then you'll want the following compilation constant to be defined:
{$DEFINE COMMON_ANODE}
Furthermore, depending on how you're sourcing or sinking the devices (PNP or NPN transistors, say), you may need to invert the selection signals, in which case use:
{$DEFINE SELECT_INVERT}
Global Types, Constants and Variables
In multiplexed mode, there is one global data type, tDevice, which can take on the values DEV1, DEV2, DEV3 or DEV4. Thus the displays are given names.
The printing is character based, with chr(48)..chr(57) being the decimal digits, chr(65)..chr(90) being the alphabetic characters, chr(48) being the blank or space character, and chr(58)..chr(64) being the individual segments. For your convenience, the following global constants are defined.
SEG7_BLANK = chr(47); // the blank character
SEG7_A = chr(58); // segment a
SEG7_B = chr(59); // segment b
SEG7_C = chr(60); // segment c
SEG7_D = chr(61); // segment d
SEG7_E = chr(62); // segment e
SEG7_F = chr(63); // segment f
SEG7_G = chr(64); // segment g
Again in multiplexed mode, there is one global variable, Seg7Buffer[], the array used to hold the character string to be printed. Its size is automatically configured by however many devices you're using (from 2 to 4). Simply load the buffer with the desired characters and the interrupt routine takes care of multiplexing them.
Command
As mentioned above, when writing to a single device, the action is carried out directly through the single command:
procedure WriteSeg7(character : char; dp : boolean);
Obviously, character is in ASCII, while the parameter dp determines whether the decimal point should be lit.
Add It to Your Library Collection
You can get the 7-segment display library unit "SevenSegment.pas" by clicking the following link:
Be sure to read over the source code for additional details on how to use it.
Try It Out
Since this is an extremely versatile library unit with many options, you'll no doubt want to play with some exercises first. You're in luck, for I've written sixteen programs using it; I employed these when testing the unit. Click here to get the entire package of all sixteen demos.
No comments:
Post a Comment