[1] Specifically, it represents the number of clock ticks remaining after all of the timers ahead of it in the list have expired. [2] Astute readers might recall that in Chapter 5, I stated that the PCB was located in the I/O space of the 80188EB processor. However, because memory-mapped registers are more likely in a device driver situation, I've relocated the entire PCB to physical address 72000h, in the memory space. This new location will be assumed for the rest of the book. To see how this relocation was performed, take a look at the constructor for the i8018xEB class. [3] A word of caution about waitfor : this implementation spins its wheels waiting for the software timer to change to the done state. This technique is called busy- waiting, and it is neither elegant nor an efficient use of the processor. In Chapter 8, we'll see how the introduction of an operating system allows us to improve upon this implementation. Chapter 9. Putting It All Together 9.1 Application Overview 9.2 Flashing the LED 9.3 Printing "Hello, World!" 9.4 Working with Serial Ports 9.5 The Zilog 85230 Serial Controller A sufficiently high level of technology is indistinguishable from magic. —Arthur C. Clarke In this chapter, I'll attempt to bring all of the elements we've discussed so far together into a complete embedded application. I don't have much new material to add to the discussion at this point, so the body of the chapter is mainly a description of the code presented herein. My goal is to describe the structure of this application and its source code in such a way that there is no magic remaining for you. You should leave this chapter with a complete understanding of the example program and the ability to develop useful embedded applications of your own. 9.1 Application Overview The application we're going to discuss is not much more complicated than the "Hello, World!" example found in most other programming books. It is a testament to the complexity of embedded software development that this example comes near the end of this book, rather than at its beginning. We've had to gradually build our way up to the computing platform that most books, and even high-level language compilers, take for granted. Once you're able to write the "Hello, World!" program, your embedded platform starts to look a lot like any other programming environment. The hardest parts of the embedded software development process—familiarizing yourself with the hardware, establishing a software development process for it, and interfacing to the individual hardware devices—are behind you. You are finally able to focus your efforts on the algorithms and user interfaces that are specific to the product you're developing. In many cases, these higher-level aspects of the program can be developed on another computer platform, in parallel with the lower-level embedded software development we've been discussing, and merely ported to the embedded system once both are complete. Figure 9-1 contains a high-level representation of the "Hello, World!" application. This application includes three device drivers, the ADEOS operating system, and two ADEOS tasks. The first task toggles the Arcom board's red LED at a rate of 10 Hz. The second prints the string "Hello, World!" at 10 second intervals to a host computer or dumb terminal connected to one of the board's serial ports. Figure 9-1. The "Hello, World!" application In addition to the two tasks, there are three device drivers shown in the figure. These control the Arcom board's LEDs, timers, and serial ports, respectively. Although it is customary to draw device drivers below the operating system, I chose to place these three on the same level as the operating system to emphasize that they actually depend more on ADEOS than it does on them. In fact, the embedded operating system doesn't even know (or care) that these drivers are present in the system. This is a common feature of the device drivers and other hardware-specific software in an embedded system. The implementation of main is shown below. This code simply creates the two tasks and starts the operating system's scheduler. At such a high level the code should speak for itself. In fact, we've already discussed a similar code listing in the previous chapter. #include "adeos.h" void flashRed(void); void helloWorld(void); /* * Create the two tasks. */ Task taskA(flashRed, 150, 512); Task taskB(helloWorld, 200, 512); /****************************************************************** *** * * Function: main() * * Description: This function is responsible for starting the ADEOS * scheduler only. * * Notes: * * Returns: This function will never return! * ****************************************************************** ***/ void main(void) { os.start(); // This point will never be reached. } /* main() */ 9.2 Flashing the LED As I said earlier, one of two things this application does is blink the red LED. This is done by the code shown below. Here the function flashRed is executed as a task. However, ignoring that and the new function name, this is almost exactly the same Blinking LED function we studied in Chapter 7. The only differences at this level are the new frequency (10 Hz) and LED color (red). #include "led.h" #include "timer.h" /****************************************************************** **** * * Function: flashRed() * * Description: Blink the red LED ten times a second. * * Notes: This outer loop is hardware-independent. However, it * calls the hardware-dependent function toggleLed(). * * Returns: This routine contains an infinite loop. * ****************************************************************** ****/ void flashRed(void) { Timer timer; timer.start(50, Periodic); // Start a periodic 50ms timer. while (1) { toggleLed(LED_RED); // Toggle the red LED. timer.waitfor(); // Wait for the timer to expire. } } /* flashRed() */ The most significant changes to the Blinking LED program are not visible in this code. These are changes made to the toggleLed function and the Timer class to make them compatible with a multitasking environment. The toggleLed function is what I am now calling the LED driver. Once you start thinking about it this way, it might make sense to consider rewriting the driver as a C++ class and add new methods to set and clear an LED explicitly. However, it is sufficient to leave our implementation as it was in Chapter 7 and simply use a mutex to protect the P2LTCH register from simultaneous access by more than one task. [1] Here is the modified code: #include "i8018xEB.h" #include "adeos.h" static Mutex gLedMutex; /****************************************************************** **** * * Function: toggleLed() * * Description: Toggle the state of one or both LEDs. * * Notes: This version is ready for multitasking. * * Returns: None defined. * ****************************************************************** ****/ void toggleLed(unsigned char ledMask) { gLedMutex.take(); // Read P2LTCH, modify its value, and write the result. // gProcessor.pPCB->ioPort[1].latch ^= ledMask; gLedMutex.release(); } /* toggleLed() */ A similar change must be made to the timer driver from Chapter 7 before it can be used in a multitasking environment. However, in this case there is no race condition. [2] Rather, we need to use a mutex to eliminate the polling in the waitfor method. By associating a mutex with each software timer, we can put any task that is waiting for a timer to sleep and, thereby, free up the processor for the execution of lower-priority ready tasks. When the awaited timer expires, the sleeping task will be reawakened by the operating system. Toward this end, a pointer to a mutex object, called pMutex, will be added to the class definition: class Timer . your embedded platform starts to look a lot like any other programming environment. The hardest parts of the embedded software development process—familiarizing yourself with the hardware, establishing