Driving LEDs
7.6 Using Pointers
7.6.1 Number arrays for the LEDs
Firstly we will develop a program which ‘walks a LED’ along the bank of eight LEDs. This program uses a fixed pattern.
Walking LEDs – Fixed Array Defined Within the Class
We will be using an array and scanning it cyclically to light up the LEDs using the port at address BASE. The effect of cyclic scanning will be the appearance of a “walking LED” across the bank of 8 LEDs. The array will contain eight elements, each element used to light just one LED of the group. When we move from one
element to the next element in the array, the LED that is currently lit will turn off and the adjacent LED in the direction of the ‘walk’ will light up.
Table 7-3 shows each hexadecimal number and corresponding binary bit pattern for each array element that in-turn must be output to the port.
Table 7-3 LED pattern values stored in Pattern array. Binary Number Array Element D7 D6 D5 D4 D3 D2 D1 D0 Hex value Pattern[0] 0 0 0 0 0 0 0 1 0x01 Pattern[1] 0 0 0 0 0 0 1 0 0x02 Pattern[2] 0 0 0 0 0 1 0 0 0x04 Pattern[3] 0 0 0 0 1 0 0 0 0x08 Pattern[4] 0 0 0 1 0 0 0 0 0x10 Pattern[5] 0 0 1 0 0 0 0 0 0x20 Pattern[6] 0 1 0 0 0 0 0 0 0x40 Pattern[7] 1 0 0 0 0 0 0 0 0x80
A new object class named LEDs will be created which will have Pattern as a data member and also have the functionality to initialise the Pattern array to the desired values. The contents of the array Pattern will be fixedfor the class and cannot be changed by the user within the main() function. The class must also have a function to sequentially output the appropriate values in Pattern to the port at address BASE. The LEDs class can be derived from the ParallelPort class to inherit the required interface functionality. Listing 7-3 shows the class definition.
Listing 7-3 LEDs class definition.
class LEDs : public ParallelPort {
private:
unsigned char Pattern[8]; int PatternIndex; public: LEDs(); LEDs(int baseaddress); void LightLEDs(); };
The private data member PatternIndex is required to store the number of the LED that was previously lit so cycling can be controlled as we move through the array to produce the ‘LED walk’. Note that Pattern is an array of eight unsigned char elements. The elements need to be unsigned char to provide just 8 bits in each element, and to avoid the added complications that would be involved if signed numbers were used instead.
The member functions for the class can now be defined as shown in Listing 7-4.
Listing 7-4 Member functions for the LEDs class.
LEDs::LEDs() {
// Fill in the Pattern array for(int i = 0; i < 8; i++) *(Pattern + i) = 1 << i;
PatternIndex = 0; // initialise to 0 }
LEDs::LEDs(int baseaddress) : ParallelPort(baseaddress) {
// Fill in the Pattern array for(int i = 0; i < 8; i++) *(Pattern +i) = 1 << i; PatternIndex = 0; // initialise to 0 } void LEDs::LightLEDs() {
while(!kbhit()) // key press terminates function {
WritePort0(*(Pattern + PatternIndex++)); // Reset PatternIndex when it gets to 8 if(PatternIndex == 8) PatternIndex = 0; delay(500);
} }
The array name Pattern (without the subscripts) is a pointer and points to the first element of the array. Therefore:
Pattern + i
points to the ith element of the array. To refer to the value pointed to by Pattern
+ i, we must de-reference it as follows:
*(Pattern + i)
The constructors of the LEDs class initialise the array by left-shifting the number 1 byi bit places using:
1 << i
The constructors will initialise the Pattern array with the values shown in Table 7-3 as their respective for loops complete each iteration of the statement:
*(Pattern + i) = 1 << i;
The while loop in the LightLEDs() function is conditioned on !kbhit() and will continue to execute provided a key is not hit. Inside the while loop the inherited WritePort0() function is used to write the array Pattern to the port at address BASE – one element at a time. Each element is accessed using PatternIndex as an offset with respect to the starting memory address of the array Pattern. The constant integer pointer Pattern is added the value of PatternIndex each time. This allows the Pattern array to be scanned from beginning to end. When the end is reached, PatternIndex is reset to 0 to allow a new cycle of scanning the Pattern array to repeat. Note that PatternIndex is post-incremented within the expression:
*(Pattern + PatternIndex++)
In this expression, the current value of PatternIndex is used to evaluate the current address of the element to access. Following this activity, the value of PatternIndex is incremented. A delay of 500 ms is included to provide sufficient time to see the LED walk. Connect the BASE address signals on the interface board (U13) to the LED Driver IC (U3) to test the complete program shown below.
Listing 7-5 Complete program to 'Walk a LED'.
// Complete Program to 'walk' a LED #include <iostream.h> #include <conio.h> #include <dos.h> class ParallelPort { private:
unsigned char InDataPort1; public:
ParallelPort();
ParallelPort(int baseaddress);
void WritePort0(unsigned char data); void WritePort2(unsigned char data); unsigned char ReadPort1();
}; ParallelPort::ParallelPort() { BaseAddress = 0x378; InDataPort1 = 0; } ParallelPort::ParallelPort(int baseaddress) { BaseAddress = baseaddress; InDataPort1 = 0; }
void ParallelPort::WritePort0(unsigned char data) {
outportb(BaseAddress,data); }
void ParallelPort::WritePort2(unsigned char data) {
outportb(BaseAddress+2,data ^ 0x0B); }
unsigned char ParallelPort::ReadPort1() {
InDataPort1 = inportb(BaseAddress+1); // Invert most significant bit to compensate
// for internal inversion by printer port hardware. InDataPort1 ^= 0x80;
// Filter to clear unused data bits D0, D1 and D2 to zero. InDataPort1 &= 0xF8;
return InDataPort1;
}
class LEDs : public ParallelPort {
private:
unsigned char Pattern[8]; int PatternIndex; public: LEDs(); LEDs(int baseaddress); void LightLEDs(); }; LEDs::LEDs() {
// Fill in the Pattern array for(int i = 0; i < 8; i++) *(Pattern + i) = 1 << i;
PatternIndex = 0; // initialise to 0 }
LEDs::LEDs(int baseaddress) : ParallelPort(baseaddress) {
// Fill in the Pattern array for(int i = 0; i < 8; i++)
*(Pattern + i) = 1 << i; // Shift '1' left 'i' places // and fill Pattern array. PatternIndex = 0; // initialise to 0
}
void LEDs::LightLEDs() {
while(!kbhit()) // keypress terminates function {
WritePort0(*(Pattern + PatternIndex++)); // Reset PatternIndex when it reaches 8 if(PatternIndex == 8) PatternIndex = 0; delay(500); } } void main() { LEDs Leds;
Leds.LightLEDs(); // Displays a 'walking' LED. getch();
cout << endl << "Halted !” << endl;
cout << “Press a key to continue" << endl; getch();
Leds.LightLEDs(); // 'Walking' restarts with the LED // alight in the next position. }
Walking LEDs – User Definable Contents with Fixed Array Size
The program shown in Listing 7-5 is rather inflexible. The contents of the array Pattern are fixed within the class. It is more appropriate to give the user the ability to define the contents of the array, therefore, the LEDs class must be modified.
The user will define the contents of the array used to light the LEDs. Therefore, the constructors of the LEDs class are not needed to initialise this array. Instead, the class can maintain a pointer that points to the array the user will define. The user- defined array can be scanned by the LightLEDs() function if the starting address of the array and its size are known. Therefore, the LEDs class only needs to have a data member to store the address of the array and another data member to store its size.
To facilitate these changes we will replace the member data Pattern (unsigned char array) with PatternPtr, a pointer type (pointer to unsigned char). A member function must also be included to extract the address of the array and to assign it to the pointer maintained within the class. Since the size of the array can now be arbritrarily set, a new data member must be included to store the maximum number of elements in the array. This new member is used to determine when the final element of the array has been scanned, whereupon PatternIndex can then be reset to 0. The modifications to the LEDs class are shown in Listing 7-6.
Listing 7-6 Modified LEDs class.
class LEDs : public ParallelPort {
private:
unsigned char* PatternPtr;
int PatternIndex;
int MaxIndex;
LEDs();
LEDs(int baseaddress);
void SetPatternAddress(unsigned char* pattern, int maxidx);
void LightLEDs();
};
The definitions of the member functions of this class are given in Listing 7-7.
Listing 7-7 Member function definitions for the modified class.
LEDs::LEDs() {
MaxIndex = 0; PatternIndex = 0; }
LEDs::LEDs(int baseaddress) : ParallelPort(baseaddress) {
MaxIndex = 0; PatternIndex = 0; }
void LEDs::SetPatternAddress(unsigned char* pattern, int maxidx) {
PatternPtr = pattern; // Pointer PatternPtr assigned
// address of pattern. MaxIndex = maxidx; } void LEDs::LightLEDs() { if(MaxIndex <= 0) {
cout << "No Patterns to display " << endl; return;
}
while(!kbhit()) {
WritePort0(*(PatternPtr + PatternIndex++)); // Reset PatternIndex when it gets to MaxIndex. if(PatternIndex == MaxIndex) PatternIndex = 0;
delay(500); }
}
Listing 7-8 showns a main() function which asks a user to enter patterns into the LED pattern array during program execution.
Listing 7-8 Main function – user fills in the array (LED pattern).
void main() {
unsigned char LightPattern[8];
int UserPattern;
LEDs Leds;
int i;
cout << "Enter 8 user patterns in the range 0x00-0xFF "; cout << endl;
for(i = 0; i < 8; i++) // fill 8 element Array { cin >> UserPattern; *(LightPattern + i) = UserPattern; } Leds.SetPatternAddress(LightPattern, 8); Leds.LightLEDs(); getch(); Leds.LightLEDs(); }
Note that in the main()function shown in Listing 7-8, the programmer is required to code the array name (LightPattern[8]) with the number of elements of the array (each element will store one of the patterns sent out to the LEDs). The user enters the actual values for each sequential pattern at run-time. The local variable UserPattern, of type int, is used to read integer data. The data is then assigned to the array LightPattern of type unsigned char through the use of the array name LightPattern as a pointer.
The statement in Listing 7-8:
Leds.SetPatternAddress(LightPattern,8);
Leds.SetPatternAddress(LightPattern,
sizeof(LightPattern));
Programming with functions can often be made more efficient by defining macros and then calling the functions through the macro. As can be seen in the above statement, the word LightPattern occurs twice in the function call. In order to minimise the possibility of coding an error, a macro can be written that requires the parameter LightPattern be specified only once. The following section describes the process of defining a macro.
7.7 Macros
Macros can be viewed as placeholders that the preprocessor replaces with an expression. For example, consider when the compiler encounters the following statement:
y = x*x*x;
We can define a macro to facilitate programming as follows:
#define CUBE(x) ((x)*(x)*(x))
The preprocessor will replace all occurrences of CUBE(x) with ((x)*(x)*(x)). Thus, CUBE(x) can be used freely in the program. The extra pair of parenthesis is necessary to adhere strictly with the intended precedence of operations. For example, if CUBE(x) was defined as:
#define CUBE(x) x*x*x; then,
y = CUBE(3); will be replaced by, y = 3*3*3 evaluating to 27. On the other hand, the line:
y = CUBE(2+1); will be expanded to, y = 2+1*2+1*2+1 incorrectly evaluating to 7.
If we now use the definition with every x placed in a pair of parentheses; (x)*(x)*(x), the expression will be expanded to:
y = (2+1)*(2+1)*(2+1) which correctly evaluates to 27.
If the preceding definition for CUBE was used for the following expression, an incorrect result will be generated.
y = 81/CUBE(3);
This will expand to:
y = 81/(3)*(3)*(3);
which evaluates to 243 instead of the intended result 3. Using an additional outer pair of parenthesis ensures a correct result.
To improve the program given in Listing 7-8, we can include a macro as follows:
#define SetArray(x) SetPatternAddress((x), sizeof(x))
Themain() function is shown in Listing 7-9.
Listing 7-9 The main() function – user fills in the LED pattern.
#define SetArray(x) SetPatternAddress((x), sizeof((x))) void main()
{
unsigned char UserPattern, LightPattern[4];
LEDs Leds;
int i;
cout << "Enter “ << sizeof(LightPattern);
cout << “ user patterns in the range 0x00-0xFF "; cout << endl;
for(i = 0; i < sizeof(LightPattern); i++) { cin >> UserPattern; *(LightPattern + i) = UserPattern; } Leds.SetArray(LightPattern); Leds.LightLEDs(); getch(); Leds.LightLEDs(); }