Timer driver/utility for all platforms
Posted: Wed May 20, 2009 5:04 pm
I created a driver that makes using hardware timers pretty trivial to use for all the current NetBurner products. This driver will use the DMA timer peripheral for all platforms except the MCF5272 which uses the timer peripheral. There are 3 main types of functionality available in this utility. To use this in a application just include StopWatch.cpp and StopWatch.h in your application. I also included a main.cpp as an example to demonstrate the various functionality described below.
The first is a simple stopwatch for basic timing. I use this when I capture code execution time when benchmarking performance. An initialize function for the timer needs to be called once:
void StopWatchInit( int Timer = DEFAULT_TIMER, double InterruptTime = 0, WORD Prescaler = 0x0 );
For the highest precision on a single timer no parameters are needed, just call StopWatchInit();
To start the timer:
void StopWatchStart( int Timer = DEFAULT_TIMER );
To read the elapsed time in seconds since start call:
double StopWatchReadTime( int Timer = DEFAULT_TIMER );
To stop and clear the timer:
void StopWatchStopClear( int Timer = DEFAULT_TIMER );
If you wish to keep all the timing results as non-floating points use the following functions.
To read the actual count register from the timer:
DWORD StopWatchReadLow( int Timer = DEFAULT_TIMER );
To read the software overflow register, incremented in interrupt every time the hardware timer overflows:
DWORD StopWatchReadHigh( int Timer = DEFAULT_TIMER );
Once the timer is running it is interrupt driven with an extra 32-bit overflow register. This means that the timer running at the highest precision (75MHz) should run for 7800 years before overflowing on the 150MHz platforms. The MCF5272 timer will overflow after about 50 days since it only has a 16-bit timer, the prescaler parameter can be used to decrease the precision and overflow/interrupt frequency.
The second functionality provided by this driver is called StopWatchDelay. This will function very similarly to an OSTimeDly call but will provide much higher precision. The only function required for this is:
void StopWatchDelay( double DelayTime, int Timer = DEFAULT_TIMER );
The DelayTime parameter is the amount of time to delay in seconds. So say you want to do a 5ms delay and let other tasks run like an OSTimeDly, just call StopWatchDelay( 0.005 );. The standard OSTimeDly must be called in increments of 50ms and has an accuracy precision of 50ms, StopWatchDelay should have an accuracy of a few uS depending on the task switch time performance. The timer parameter is like the other stopwatch function, it tells which timer to use. If you are using a timer for the StopWatch functions and also want to use delays, you will need to use two separate timers. Also if you are using delays in more then one task you will also need to use a different timer for each task. Calling a delay in a task on the same timer that another task is using can cause a deadlock.
void StopWatchPollingDelay( double DelayTime, int Timer = DEFAULT_TIMER );
This delay function is a new addition to the driver. It will do a polling delay instead of the interrupt based delay. Since there is no task or interrupt switching, this will be a much more accurate timer. The downside compared to the normal StopWatchDelay function is that this delay will not allow lower priority tasks to run during the delay. I suggest using this version of the timer delay if you want sub uS accuracy or are calling delays that are less then 2 or 3 ms.
The third functionality is a clock generator for the timer output pin of a timer. This one is pretty simple, just call:
void StopWatchClockGenerator( double Frequency, int Timer = DEFAULT_TIMER );
The Frequency parameter is in Hz. You must also make sure to configure the pin to be multiplexed to the timer peripheral for this to work.
There is also one extra callback function that will work with both the StopWatch and Delay functions. This is a callback function linked to the interrupt routine that captures the overflow of the hardware timer:
extern void ( *StopWatch_Interrupt_Function )( int Timer );
This will allows a simple way to have high precision interrupt timing events. For example, say you want to increment LEDs with high precision timing.
First you would make a function to do this with the format of the callback:
void IncrementLEDs( int Timer )
{
// The Timer parameter is not used here since we do not need to determine which timer interrupted
LEDValue++;
putleds( LEDValue );
}
Then point the callback to your function:
StopWatch_Interrupt_Function = IncrementLEDs; // Point timer interrupt call-back to LED function
Then start the timer with your precision...
To have the timer interrupt every 10ms, this will also call the IncrementLEDs function every ms:
StopWatchInit( 1, 0.01 );
StopWatchStart();
If you just want to have the delay occur once and want to task to block, use the delay call instead:
StopWatchDelay( 0.01 );
All comments are welcome, good or bad.
-Larry
The first is a simple stopwatch for basic timing. I use this when I capture code execution time when benchmarking performance. An initialize function for the timer needs to be called once:
void StopWatchInit( int Timer = DEFAULT_TIMER, double InterruptTime = 0, WORD Prescaler = 0x0 );
For the highest precision on a single timer no parameters are needed, just call StopWatchInit();
To start the timer:
void StopWatchStart( int Timer = DEFAULT_TIMER );
To read the elapsed time in seconds since start call:
double StopWatchReadTime( int Timer = DEFAULT_TIMER );
To stop and clear the timer:
void StopWatchStopClear( int Timer = DEFAULT_TIMER );
If you wish to keep all the timing results as non-floating points use the following functions.
To read the actual count register from the timer:
DWORD StopWatchReadLow( int Timer = DEFAULT_TIMER );
To read the software overflow register, incremented in interrupt every time the hardware timer overflows:
DWORD StopWatchReadHigh( int Timer = DEFAULT_TIMER );
Once the timer is running it is interrupt driven with an extra 32-bit overflow register. This means that the timer running at the highest precision (75MHz) should run for 7800 years before overflowing on the 150MHz platforms. The MCF5272 timer will overflow after about 50 days since it only has a 16-bit timer, the prescaler parameter can be used to decrease the precision and overflow/interrupt frequency.
The second functionality provided by this driver is called StopWatchDelay. This will function very similarly to an OSTimeDly call but will provide much higher precision. The only function required for this is:
void StopWatchDelay( double DelayTime, int Timer = DEFAULT_TIMER );
The DelayTime parameter is the amount of time to delay in seconds. So say you want to do a 5ms delay and let other tasks run like an OSTimeDly, just call StopWatchDelay( 0.005 );. The standard OSTimeDly must be called in increments of 50ms and has an accuracy precision of 50ms, StopWatchDelay should have an accuracy of a few uS depending on the task switch time performance. The timer parameter is like the other stopwatch function, it tells which timer to use. If you are using a timer for the StopWatch functions and also want to use delays, you will need to use two separate timers. Also if you are using delays in more then one task you will also need to use a different timer for each task. Calling a delay in a task on the same timer that another task is using can cause a deadlock.
void StopWatchPollingDelay( double DelayTime, int Timer = DEFAULT_TIMER );
This delay function is a new addition to the driver. It will do a polling delay instead of the interrupt based delay. Since there is no task or interrupt switching, this will be a much more accurate timer. The downside compared to the normal StopWatchDelay function is that this delay will not allow lower priority tasks to run during the delay. I suggest using this version of the timer delay if you want sub uS accuracy or are calling delays that are less then 2 or 3 ms.
The third functionality is a clock generator for the timer output pin of a timer. This one is pretty simple, just call:
void StopWatchClockGenerator( double Frequency, int Timer = DEFAULT_TIMER );
The Frequency parameter is in Hz. You must also make sure to configure the pin to be multiplexed to the timer peripheral for this to work.
There is also one extra callback function that will work with both the StopWatch and Delay functions. This is a callback function linked to the interrupt routine that captures the overflow of the hardware timer:
extern void ( *StopWatch_Interrupt_Function )( int Timer );
This will allows a simple way to have high precision interrupt timing events. For example, say you want to increment LEDs with high precision timing.
First you would make a function to do this with the format of the callback:
void IncrementLEDs( int Timer )
{
// The Timer parameter is not used here since we do not need to determine which timer interrupted
LEDValue++;
putleds( LEDValue );
}
Then point the callback to your function:
StopWatch_Interrupt_Function = IncrementLEDs; // Point timer interrupt call-back to LED function
Then start the timer with your precision...
To have the timer interrupt every 10ms, this will also call the IncrementLEDs function every ms:
StopWatchInit( 1, 0.01 );
StopWatchStart();
If you just want to have the delay occur once and want to task to block, use the delay call instead:
StopWatchDelay( 0.01 );
All comments are welcome, good or bad.
-Larry