keyword static, this restriction is automatically enforced for us by the C++ compiler. The most important thing to learn from the class declaration is that, although all of the software timers are driven by the same hardware timer/counter unit, each has its own private data store. This allows the application programmer to create multiple simultaneous software timers and the device driver to manage them behind the scenes. Once you grasp that idea, you're ready to look at the implementation of the driver's initialization routine, API, and interrupt service routine. The constructor for the Timer class is responsible for initializing both the software timer and the underlying hardware. With respect to the latter, it is responsible for configuring the timer/counter unit, inserting the address of the interrupt service routine into the interrupt vector table, and enabling timer interrupts. However, because this method is a constructor that may be called several times (once for each of the Timer objects declared), our implementation of the constructor must be smart enough to perform these hardware initializations only during the very first call to it. Otherwise, the timer/counter unit might be reset at an inopportune time or become out of sync with the device driver. That is the reason for the static variable bInitialized in the following code. This variable is declared with an initial value of zero and set to one after the hardware initialization sequence has been performed. Subsequent calls to the Timer constructor will see that bInitialized is no longer zero and skip that part of the initialization sequence. #include "i8018xEB.h" #include "timer.h" #define CYCLES_PER_TICK (25000/4) // Number of clock cycles per tick. /****************************************************************** **** * * Method: Timer() * * Description: Constructor for the Timer class. * * Notes: * * Returns: None defined. * ****************************************************************** ****/ Timer::Timer(void) { static int bInitialized = 0; // // Initialize the new software timer. // state = Idle; type = OneShot; length = 0; count = 0; pNext = NULL; // // Initialize the timer hardware, if not previously done. // if (!bInitialized) { // // Install the interrupt handler and enable timer interrupts. // gProcessor.installHandler(TIMER2_INT, Timer::Interrupt); gProcessor.pPCB->intControl.timerControl &= ~(TIMER_MASK | TIMER_PRIORITY); // // Initialize the hardware device (use Timer #2). // gProcessor.pPCB->timer[2].count = 0; gProcessor.pPCB->timer[2].maxCountA = CYCLES_PER_TICK; gProcessor.pPCB->timer[2].control = TIMER_ENABLE | TIMER_INTERRUPT | TIMER_PERIODIC; // // Mark the timer hardware initialized. // bInitialized = 1; } } /* Timer() */ The global object gProcessor is declared in a header file called i8018xEB.h. It represents the Intel 80188EB processor. The i8018xEB class is something that I wrote, and it includes methods to make interaction with the processor and its on- chip peripherals easier. One of these methods is called installHandler, and its job is to insert an interrupt service routine into the interrupt vector table. This class also includes a global data structure called PCB that can be overlaid upon the memory- mapped registers of the peripheral control block. [2] The three registers associated with timer/counter unit 2 make up just one small part of this 256-byte structure. (For purely aesthetic reasons, I've implemented the PCB data structure as a set of nested structures. Hence, the control register of timer/counter unit 2 is accessible as pPCB->timer[2].control.) The initialization of the timer/counter unit consists of resetting its count register to 0, loading the maxCountA register with the countdown length, and setting several bits within the control register. What we are doing above is starting a 1 ms periodic timer that generates an interrupt at the end of each cycle. (This periodic timer will act as the clock tick we need to create software timers of arbitrary lengths.) The value that is loaded into maxCountA can be determined mathematically because it represents the number of clock cycles input to the timer/counter unit in a 1 ms period. According to the 80188EB databook, this will be one fourth of the number of processor cycles in a 1 ms period. So, for a 25 MHz processor like the one we're using (that's 25,000,000 cycles per second, or, if you prefer, 25,000 cycles per millisecond), maxCountA should be set to 25,000/4—as it is in the constant CYCLES_PER_TICK earlier. Once the hardware has been initialized and the clock tick established, it is possible to start a software timer of any length, so long as that length can be expressed as an integral number of ticks. Because our clock tick is 1 ms long, the application programmer can create timers of any length from 1 to 65,535 ms (65.536 seconds). He would do this by calling the start method: /****************************************************************** **** * * Method: start() * * Description: Start a software timer, based on the tick from the * underlying hardware timer. * * Notes: * * 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); } // // 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() */ When a software timer is started, the data members state, type, and length are initialized and the timer is inserted into a linked list of active timers called the timerList. The timers in the timer list are ordered so that the first timer to expire is at the top of the list. In addition, each timer has a count associated with it. This value represents the number of ticks that will be remaining in the software timer once all previous timers in the list have expired. Taken together, these design choices favor quick updates to the timer list at the price of slower insertions and deletions. Speed is important during updates because the timer list will be updated every time the hardware generates a clock tick interrupt—that's every one millisecond. Figure 7-1 shows the timer list in action. Remember that each software timer has its own unique length and starting time, but once it has been inserted into the list, only the count field matters for ordering. In the example shown, the first and second timers were both started (the second might actually have been restarted, because it is periodic) at the same time. Since the second is 5 ms longer, it will expire 5 clock ticks after the first. The second and third timers in the list both happen to expire at the same time, though the third timer will have been running for 10 times longer. Figure 7-1. The timer list in action The code for the interrupt service routine is shown below. This routine is declared to be of type void interrupt. The keyword interrupt is an extension of the C/C++ language that is understood only by compilers for 80x86 processors. By declaring the routine in this way, we ask the compiler to save and restore all of the processor's registers at the entry and exit, rather than only those that are saved during an ordinary function call. /****************************************************************** **** * * Method: Interrupt() * * Description: An interrupt handler for the timer hardware. * * Notes: This method is declared static, so that we cannot * inadvertently modify any of the software timers. * * Returns: None defined. * ****************************************************************** ****/ void interrupt Timer::Interrupt() { // // Decrement the active timer's count. . routine. The constructor for the Timer class is responsible for initializing both the software timer and the underlying hardware. With respect to the latter, it is responsible for configuring the. keyword static, this restriction is automatically enforced for us by the C++ compiler. The most important thing to learn from the class declaration is. constructor that may be called several times (once for each of the Timer objects declared), our implementation of the constructor must be smart enough to perform these hardware initializations only