íà ãëàâíóþ | âîéòè | ðåãèñòðàöèÿ | DMCA | êîíòàêòû | ñïðàâêà | donate |      

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ý Þ ß


ìîÿ ïîëêà | æàíðû | ðåêîìåíäóåì | ðåéòèíã êíèã | ðåéòèíã àâòîðîâ | âïå÷àòëåíèÿ | íîâîå | ôîðóì | ñáîðíèêè | ÷èòàëêè | àâòîðàì | äîáàâèòü



7.3 A Simple Timer Driver

The device driver example that we're about to discuss is designed to control one of the timer/counter units contained within the 80188EB processor. I have chosen to implement this driver — and all of the remaining examples in the book — in C++. Although C++ offers no additional assistance over C in accessing hardware registers, there are many good reasons to use it for this type of abstraction. Most notably, C++ classes allow us to hide the actual hardware interface more completely than any C features or programming techniques. For example, a constructor can be included to automatically configure the hardware each time a new timer object is declared. This eliminates the need for an explicit call from the application software to the driver initialization routine. In addition, it is possible to hide the data structure that corresponds to the device registers within the private part of the associated class. This helps to prevent the application programmer from accidentally reading or writing the device registers from some other part of the program.

The definition of the Timer class is as follows:

enum TimerState { Idle, Active, Done };

enum TimerType { OneShot, Periodic };


class Timer {

public:

 Timer();

 ~Timer();

 int start(unsigned int nMilliseconds, TimerType = OneShot);

 int waitfor();

 void cancel();

 TimerState state;

 TimerType type;

 unsigned int length;

 unsigned int count;

 Timer * pNext;

private:

 static void interrupt Interrupt();

};

Before discussing the implementation of this class, let's examine the previous declaration and consider the device driver's overall structure. The first thing we see are two enumerated types, TimerState and TimerType. The main purpose of these types is to make the rest of the code more readable. From them we learn that each software timer has a current state — Idle, Active, or Done — and a type — OneShot or Periodic. The timer's type tells the driver what to do with the timer when it expires; a Periodic timer is to be restarted then.

The constructor for the Timer class is also the device driver's initialization routine. It ensures that the timer/counter hardware is actively generating a clock tick every 1 millisecond. The other public methods of the class — start, waitfor, and cancel — provide an API for an easy-to-use software timer. These methods allow application programmers to start one-shot and periodic timers, wait for them to expire, and cancel running timers, respectively. This is a much simpler and more generic interface than that provided by the timer/counter hardware within the 80188EB chip. For one thing, the timer hardware does not know about human units of time, like milliseconds. But because the timer driver hides the specifics of this particular hardware, the application programmer need never even know about that.

The data members of the class should also help give you some insight into the device driver implementation. The first three items are variables that answer the following questions about this software timer:

• What is the timer's current state (idle, active, or done)?

• What type of a timer is it (one-shot or periodic)?

• What is the total length of the timer (in units called ticks)?

Following those are two more data members, both of which contain information that is specific to this implementation of the timer driver. The values of count and pNext have meaning only within the context of a linked list of active software timers. This linked list is ordered by the number of ticks remaining for each timer. So count contains information about the number of ticks remaining before this software timer is set to expire,[18] and pNext is a pointer to the software timer that will expire the soonest after this one.

Finally, there is a private method called Interrupt –our interrupt service routine. The Interrupt method is declared static because it is not allowed to manipulate the data members of the individual software timers. So, for example, the interrupt service routine is not allowed to modify the state of any timer. By using 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 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.[19] 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

Programming Embedded Systems in C and C++

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.

 //

 timerList.tick();

 //

 // Acknowledge the timer interrupt.

 //

 gProcessor.pPCB->intControl.eoi = EOI_NONSPECIFIC;

 //

 // Clear the Maximum Count bit (to start the next cycle).

 //

 gProcessor.pPCB->timer[2].control &= ~TIMER_MAXCOUNT;

} /* Interrupt() */

Of course, the tick method of the TimerList class does most of the work here. This method is mostly concerned with linked list manipulation and is not very exciting to look at. Briefly stated, the tick method starts by decrementing the tick count of the timer at the top of the list. If that timer's count has reached zero, it changes the state of the software timer to Done and removes it from the timer list. It also does the same for any timers that are set to expire on the very same tick. These are the ones at the new head of the list that also have a count of zero.

After creating and starting a software timer, the application programmer can do some other processing and then check to see if the timer has expired. The waitfor method is provided for that purpose. This routine will block until the software timer's state is changed to Done by timerList.tick. The implementation of this method is as follows:

/********************************************************************** 

*

* Method:      waitfor()

*

* Description: Wait for the software timer to finish.

*

* Notes:   

*

* 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.

 //

 while (state != Done);

 //

 // Restart or idle the timer, depending on its type.

 //

 if (type == Periodic) {

  state = Active;

  timerList.insert(this);

 } else {

  state = Idle;

 }

 return (0);

} /* waitfor() */

One important thing to notice about this code is that the test while (state != Done) is not an infinite loop. That's because, as we just learned a few paragraphs back, the timer's state is modified by timerList.tick, which is called from the interrupt service routine. In fact, if we were being careful embedded programmers, we would have declared state as volatile. Doing so would prevent the compiler from incorrectly assuming that the timer's state is either done or not done and optimizing away the while loop.[20]

The final method of the Timer class is used to cancel a running timer. This is easy to implement because we need only remove the timer from the timer list and change its state to Idle. The code that actually does this is shown here:

/**********************************************************************

*

* Method:      cancel()

*

* Description: Stop a running timer.

*

* Notes:

*

* Returns:     None defined.

*

**********************************************************************/

void Timer::cancel(void) {

 //

 // Remove the timer from the timer list.

 //

 if (state == Active) {

  timerList.remove(this);

 }

 //

 // Reset the timer's state.

 //

 state = Idle;

} /* cancel() */

Of course, there is also a destructor for the Timer class, though I won't show the code here. Suffice it to say that it just checks to see if the software timer is active and, if so, removes it from the timer list. This prevents a periodic timer that has gone out of scope from remaining in the timer list indefinitely and any pointers to the "dead" timer from remaining in the system.

For completeness, it might be nice to add a public method, perhaps called poll , that allows users of the Timer class to test the state of a software timer without blocking. In the interest of space, I have left this out of my implementation, but it would be easy to add such a routine. It need only return the current value of the comparison state == Done. However, in order to do this, some technique would need to be devised to restart periodic timers for which waitfor is never called.


5. One or more interrupt service routines | Programming Embedded Systems in C and C++ | Watchdog Timers