{ public: Timer(); ~Timer(); int start(unsigned int nMilliseconds, TimerType = OneShot); int waitfor(); void cancel(); TimerState state; TimerType type; unsigned int length; Mutex * pMutex; unsigned int count; Timer * pNext; private: static void interrupt Interrupt(); }; This pointer is initialized each time a software timer is created by the constructor. And, thereafter, whenever a timer object is started, its mutex is taken as follows: /****************************************************************** **** * * Method: start() * * Description: Start a software timer, based on the tick from the * underlying hardware timer. * * Notes: This version is ready for multitasking. * * Returns: 0 on success, -1 if the timer is already in use. * ****************************************************************** ****/ int Timer::start(unsigned int nMilliseconds, TimerType timerType) { if (state != Idle) { return (-1); } // // Take the mutex. It will be released when the timer expires. // pMutex->take(); // // Initialize the software timer. // state = Active; type = timerType; length = nMilliseconds / MS_PER_TICK; // // Add this timer to the active timer list. // timerList.insert(this); return (0); } /* start() */ By taking the mutex when the timer is started, we guarantee that no task (not even the one that started this timer) will be able to take it again until the same mutex is released. And that won't happen until either the timer expires naturally (via the interrupt service routine) or the timer is canceled manually (via the cancel method). So the polling loop inside waitfor can be replaced with pMutex- >take(), as follows: /****************************************************************** **** * * Method: waitfor() * * Description: Wait for the software timer to finish. * * Notes: This version is ready for multitasking. * * Returns: 0 on success, -1 if the timer is not running. * ****************************************************************** ****/ int Timer::waitfor() { if (state != Active) { return (-1); } // // Wait for the timer to expire. // pMutex->take(); // // Restart or idle the timer, depending on its type. // if (type == Periodic) { state = Active; timerList.insert(this); } else { pMutex->release(); state = Idle; } return (0); } /* waitfor() */ When the timer does eventually expire, the interrupt service routine will release the mutex and the calling task will awake inside waitfor. In the process of waking, the mutex will already be taken for the next run of the timer. The mutex need only be released if the timer is of type OneShot and, because of that, not automatically restarted. 9.3 Printing "Hello, World!" The other part of our example application is a task that prints the text string "Hello, World!" to one of the serial ports at a regular interval. Again, the timer driver is used to create the periodicity. However, this task also depends on a serial port driver that we haven't seen before. The guts of the serial driver will be described in the final two sections of this chapter, but the task that uses it is shown here. The only thing you need to know about serial ports to understand this task is that a SerialPort is a C++ class and that the puts method is used to print a string of characters from that port. #include "timer.h" #include "serial.h" /****************************************************************** **** * * Function: helloWorld() * * Description: Send a text message to the serial port periodically. * * Notes: This outer loop is hardware-independent. * * Returns: This routine contains an infinite loop. * ****************************************************************** ****/ void helloWorld(void) { Timer timer; SerialPort serial(PORTA, 19200L); timer.start(10000, Periodic); // Start a periodic 10 s timer. while (1) { serial.puts("Hello, World!"); // Output a simple text message. timer.waitfor(); // Wait for the timer to expire. } } /* helloWorld() */ Though the periodicity has a different length, the general structure of this task is the same as that of the flashRed function. So, the only thing left for us to discuss is the makeup of the serial port driver. We'll start with a description of a generalized serial ports interface and then finish with the specifics of the serial controller found on the Arcom board. 9.4 Working with Serial Ports At the application level, a serial port is simply a bidirectional data channel. This channel is usually terminated on each end with a hardware device called a serial communications controller (SCC). Each serial port within the SCC—there are usually at least two serial ports per controller—is connected to the embedded processor on one side and to a cable (or the connector for one) on the other side. At the other end of that cable there is usually a host computer (or some other embedded system) that has an internal serial communications controller of its own. Of course, the actual purpose of the serial port is application-dependent. But the general idea is this: to communicate streams of data between two intelligent systems or between one such device (the target) and a human operator. Typically, the smallest unit of data that can be sent or received over a serial port is an 8-bit character. So streams of binary data need to be reorganized into bytes before transmission. This restriction is similar to that of C's stdio library, so it makes sense to borrow some programming conventions from that interface. In order to support serial communications and emulate a stdio-style interface, I've defined the SerialPort class as it is shown below. This class abstracts the application's use of the serial port as bidirectional data channel and makes the interface as similar as possible to what we've all seen before. In addition to the constructor and destructor, the class includes four methods—putchar, [3] puts, getchar, and gets —for sending characters and strings of characters and receiving the same. These routines are defined exactly as they would be in any ANSI C- compliant version of the header file stdio.h. Here's the actual class definition: #include "circbuf.h" #define PORTA 0 #define PORTB 1 class SerialPort { public: SerialPort(int port, unsigned long baudRate = 19200L, unsigned int txQueueSize = 64, // Transmit Buffer Size unsigned int rxQueueSize = 64); // Receive Buffer Size ~SerialPort(); int putchar(int c); int puts(const char *s); . because of that, not automatically restarted. 9.3 Printing "Hello, World!" The other part of our example application is a task that prints the text string "Hello, World!"