The superstructure of the Blinking LED program is shown below. This part of the program is hardware-independent. However, it relies on the hardware-dependent functions toggleLed and delay to change the state of the LED and handle the timing, respectively.
/**********************************************************************
*
* Function: main()
*
* Description: Blink the green LED once a second.
*
* Notes: This outer loop is hardware-independent. However,
* it depends on two hardware-dependent functions.
*
* Returns: This routine contains an infinite loop.
*
**********************************************************************/
void main(void) {
while (1) {
toggleLed(LED_GREEN); /* Change the state of the LED. */
delay(500); /* Pause for 500 milliseconds. */
}
} /* main() */
In the case of the Arcom board, there are actually two LEDs: one red and one green. The state of each LED is controlled by a bit in a register called the Port 2 I/O Latch Register ( p2ltch, for short). this register is located within the very same chip as the CPU and takes its name from the fact that it contains the latched state of eight I/O pins found on the exterior of that chip. Collectively, these pins are known as I/O Port 2. And each of the eight bits in the P2LTCH register is associated with the voltage on one of the I/O pins. For example, bit 6 controls the voltage going to the green LED:
#define LED_GREEN 0x40 /* The green LED is controlled by bit 6. */
By modifying this bit, it is possible to change the voltage on the external pin and, thus, the state of the green LED. As shown in Figure 2-1 , when bit 6 of the P2LTCH register is 1 the LED is off; when it is the LED is on.
Figure 2-1. LED wiring on the Arcom board
The P2LTCH register is located in a special region of memory called the I/O space, at offset 0xFF5E. Unfortunately, registers within the I/O space of an 80x86 processor can be accessed only by using the assembly language instructions in and out . The C language has no built-in support for these operations. Its closest replacements are the library routines inport and outport , which are declared in the PC-specific header file dos.h . Ideally, we would just include that header file and call those library routines from our embedded program. However, because they are part of the DOS programmer's library, we'll have to assume the worst: that they won't work on our system. At the very least, we shouldn't rely on them in our very first program.
An implementation of the toggleLed routine that is specific to the Arcom board and does not rely on any library routines is shown below. The actual algorithm is straightforward: read the contents of the P2LTCH register, toggle the bit that controls the LED of interest, and write the new value back into the register. You will notice that although this routine is written in C, the functional part is actually implemented in assembly language. This is a handy technique, known as inline assembly, that separates the programmer from the intricacies of C's function calling and parameter passing conventions but still gives her the full expressive power of assembly language. [4] Unfortunately, the exact syntax of inline assembly varies from compiler to compiler. In the example, I'm using the format preferred by the Borland C++ compiler. Borland's inline assembly format is one of the best because it supports references to variables and constants that are defined within the C code.
#define P2LTCH 0xFF5E /* The offset of the P2LTCH register. */
/**********************************************************************
*
* Function: toggleLed()
*
* Description: Toggle the state of one or both LEDs.
*
* Notes: This function is specific to Arcom's Target188EB board.
*
* Returns: None defined.
*
**********************************************************************/
void toggleLed(unsigned char ledMask) {
asm {
mov dx, P2LTCH /* Load the address of the register. */
in al, dx /* Read the contents of the register. */
mov ah, ledMask /* Move the ledMask into a register. */
xor al, ah /* Toggle the requested bits. */
out dx, al /* Write the new register contents. */
};
} /* toggleLed() */
We also need to implement a half-second (500 ms) delay between LED toggles. This is done by busy-waiting within the delay routine shown below. This routine accepts the length of the requested delay, in milliseconds, as its only parameter. It then multiplies that number by the constant CYCLES_PER_MS to obtain the total number of while-loop iterations required to delay for the requested time period.
/**********************************************************************
*
* Function: delay()
*
* Description: Busy-wait for the requested number of milliseconds.
*
* Notes: The number of decrement-and-test cycles per millisecond
* was determined through trial and error. This value is
* dependent upon the processor type and speed.
*
* Returns: None defined.
*
**********************************************************************/
void delay(unsigned int nMilliseconds) {
#define CYCLES_PER_MS 260 /* Number of decrement-and-test cycles. */
unsigned long nCycles = nMilliseconds * CYCLES_PER_MS;
while (nCycles--);
} /* delay() */
The hardware-specific constant CYCLES_PER_MS represents the number of decrement-and-test cycles ( nCycles-- != 0 ) that the processor can perform in a single millisecond. To determine this number I used trial and error. I made an approximate calculation (I think it came out to around 200), then wrote the remainder of the program, compiled it, and ran it. The LED was indeed blinking but at a rate faster than 1 Hz. So I used my trusty stopwatch to make a series of small changes to CYCLES_PER_MS until the rate of blink was as close to 1 Hz as I cared to test.
That's it! That's all there is to the Blinking LED program. The three functions main , toggleLed , and delay do the whole job. If you want to port this program to some other embedded system, you should read the documentation that came with your hardware, rewrite toggleLed as necessary, and change the value of CYCLES_PER_MS. Of course, we do still need to talk about how to build and execute this program. We'll examine those topics in the next two chapters. But first, I have a little something to say about infinite loops and their role in embedded systems.
2.3 The Role of the Infinite Loop
One of the most fundamental differences between programs developed for embedded systems and those written for other computer platforms is that the embedded programs almost always end with an infinite loop. Typically, this loop surrounds a significant part of the program's functionality — as it does in the Blinking LED program. The infinite loop is necessary because the embedded software's job is never done. It is intended to be run until either the world comes to an end or the board is reset, whichever happens first.
Читать дальше