|
|
dereksoftstuff
Expert Boarder |
|
2008/07/02 05:50 |
|
HowTo : Unlimited Software Timers, Events & Delays
Here's my take on Software Timers, Delays and Events.
Software Timers - good for performance testing your code - plug-in anywhere (accuracy 1 clock = 20ns @50MHz) Delays - good for initialisations and general testing Software Events - system design driven by events (cyclic timeouts and interrupts)
Attached code files :
timerUtils.h timerUtils.c
The functionality for timers, delays and events are completely independent, so you can pick and mix. You can have as many timers and events as you want (UNLIMITED). All of the utils run off one hardware timer (I used Timer0) but you can use whatever you want.
I've tested them @ 8MHz, 20MHz, 25MHz and 50MHz system clock (just change the define in systemDefs.h).
Accuarcy of timers and delays is good across the range - run some timer tests and see for yourself.
The Timers and delays are self-explanetary.
The Event system in timerUtils is a compact way to implement your whole system. It's very easy to create timeouts, and integrate rx interrupts into your code (just one line of code to configure an event), and then startEvent/stopEvent.
You can setup cyclic timer events (choose : frequency, priority) and interrupt events using eventReady() in ISR - no more g_ulFlags.
processEvents() finds the highest priority event (cyclic timer timeout or interrupt rx'd) and executes your code function for that event. Then repeats ...
The event statistics are logged, total number of events, delayed events (by priority), missed events and event latency. The latency is the time between when the event occurred (rx interrupt or timeout) to the time your code was run to action that event. So in just a few seconds you can see if your system is performing correctly, missing interrupts or hitting bottlenecks under different load conditions etc, More importantly it allows easy modification and quick re-test. There is also a threshold mechanism to quickly diagnose latency issues.
Have a play.
Associated code :
systemDefs.h - stuff you probably already have - but need.
I can post some test stuff as well if anyone wants a quick start on events ...
| Code: | #ifndef TIMER_UTILS_H_
#define TIMER_UTILS_H_
#include <systemDefs.h>
// call once only - system initialisation
void initTimerUtils(void);
// ********************** TIMERS *******************************
// **** set this value for your system ****
#define MAX_NUM_TIMERS 5
// returned from getTimerId if they are all in use
#define TIMER_IDS_EXHAUSTED 0
// call once only for each s/w timer required
UINT getTimerId(void);
void startTimer(UINT timerId);
UINT msTimeElapsed(UINT timerId);
UINT usTimeElapsed(UINT timerId);
UINT clocksElapsed(UINT timerId);
// ********************** DELAYS *******************************
void usDelay(UINT usPeriod);
void msDelay(UINT msPeriod);
void sDelay(UINT sPeriod);
// ********************** EVENTS *******************************
// **** comment/uncomment these 2 as required
#define USE_EVENTS
#define EVENTS_WITH_STATS
#if defined(USE_EVENTS)
// **** set for your system ****
#define MAX_NUM_INTERRUPT_EVENTS 5
#define MAX_NUM_CYCLIC_TIMER_EVENTS 10
// this is the event processor - forever loop
#define NO_EVENTS 0 // error
UINT processEvents(void);
enum eventClassType {CYCLIC_TIMER, INTERRUPT};
#define CONFIG_ERROR 0
// call once only for each s/w event required
// returns eventId from 1 .. n sequentially, error is 0
// priority highest = 1 (do first), no duplicates allowed, can mix cyclics & interrupts
UINT getEventId(enum eventClassType eventClass,
UINT msTimeoutPeriod_OR_hwInterruptId,
UINT priority,
void(*eventHandler)());
// start & stop the events (for interrupts - will enable / disable )
void startEvent(UINT eventId);
void stopEvent(UINT eventId);
// use in ISR
void eventReady(UINT eventId);
#if defined(EVENTS_WITH_STATS)
// ******************************* STATS ******************************
// whats happening in the system
typedef struct eventStatsResultsType {
ULONG numEvents;
ULONG numEventsMissed;
ULONG numEventsDelayed;
ULONG numEventsLatencyLow;
ULONG numEventsLatencyHigh;
};
struct eventStatsResultsType getEventStats(UINT eventId);
void resetEventStatsResults(UINT eventId);
// allows user to configure stats collected
// Event Latency - Band 1 : (0 to Low) Band 2 : (Low to High)
typedef struct eventStatsConfigType {
UINT usLatencyThresholdLow;
UINT usLatencyThresholdHigh;
};
void configureEventStats(UINT eventId, struct eventStatsConfigType config);
#endif // EVENTS_WITH_STATS
#endif // USE_EVENTS
#endif // TIMER_UTILS_H_
|
| Code: | #include <timerUtils.h>
#include <hw_ints.h>
#include <hw_nvic.h>
#include <sysctl.h>
#include <interrupt.h>
#include <lmi_timer.h> // driverlib timer - maybe called <timer.h> for others
#define HW_TIMER_WRAP 0xFFFFFFFF // max 32 bit
#define SELF_CALIBRATION_TIMER 0
//************************** LOCAL PROTOTYPES ****************************************
void selfCalibration(void);
UINT clocksElapsedSince(UINT startTime);
void clockDelay(ULONG value);
UINT timeNow(void);
UINT hwTimerWrap(void);
void resetPriorityMaps(void);
//*****************************************************************************
// The interrupt handler for timer0 interrupt.
// continuous timer - never stops - just wraps ...
//*****************************************************************************
void Timer0IntHandler(void) {
// ignore ...
// Clear the timer interrupt.
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
}
// configure this for the type of timer you want
// timer wraps at approx 85 secs - the max timeout allowed with this config
void initTimerUtils() {
// Enable the peripherals
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
// Configure the two 32-bit periodic timer
TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
TimerLoadSet(TIMER0_BASE, TIMER_A, HW_TIMER_WRAP);
//in startup.c TimerIntRegister(TIMER0_BASE, TIMER_TIMA_TIMEOUT, Timer0IntHandler);
// Setup the interrupts for the timer timeouts
IntEnable(INT_TIMER0A);
TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
// Enable the timer
TimerEnable(TIMER0_BASE, TIMER_A);
// some magic ...
selfCalibration();
#if defined(USE_EVENTS)
resetPriorityMaps();
#endif // USE_EVENTS
}
// local modules
//***********************************************************************
UINT timeNow(void) {
return TimerValueGet(TIMER0_BASE, TIMER_A);
}
UINT hwTimerWrap(void) {
return TimerLoadGet(TIMER0_BASE, TIMER_A);
}
UINT clocksElapsedSinceTime(UINT startTime, UINT currentTime) {
UINT numClocksElapsed;
if (currentTime > startTime) {
// wrap
numClocksElapsed = startTime + (hwTimerWrap() - currentTime);
} else {
numClocksElapsed = startTime - currentTime;
}
return numClocksElapsed;
}
UINT clocksElapsedSince(UINT startTime) {
return clocksElapsedSinceTime(startTime, timeNow());
}
//***********************************************************************
// ************************* TIMERS ***********************************
//***********************************************************************
#define FIRST_TIMER 1
//************************** DATA ****************************************
static UINT clocksTimerCalibrationOffsetClocks = 0;
static UINT delayCalibrationOffsetClocks = 0;
static UINT startTimes[MAX_NUM_TIMERS + 1];
static UINT nextTimerId = FIRST_TIMER; // 0 is for error
//************************** MODULES ****************************************
UINT getTimerId(void) {
return nextTimerId <= MAX_NUM_TIMERS ? nextTimerId++ : TIMER_IDS_EXHAUSTED;
}
void startTimer(UINT timerId) {
if (timerId <= MAX_NUM_TIMERS) {
startTimes[timerId] = timeNow();
}
}
UINT usTimeElapsed(UINT timerId) {
return clocksElapsed(timerId) / NUM_CLOCKS_IN_MICROSEC;
}
UINT msTimeElapsed(UINT timerId) {
return usTimeElapsed(timerId) / 1000;
}
UINT clocksElapsed(UINT timerId) {
UINT clocks = clocksElapsedSince(startTimes[timerId]);
return clocks > clocksTimerCalibrationOffsetClocks
? clocks - clocksTimerCalibrationOffsetClocks : 0;
}
void selfCalibration(void) {
// calibrate timer - startTimer and clocksElapsed combined
startTimer(SELF_CALIBRATION_TIMER);
clocksTimerCalibrationOffsetClocks = clocksElapsed(SELF_CALIBRATION_TIMER);
// using new calibration above - calibrate delay
startTimer(SELF_CALIBRATION_TIMER);
usDelay(0);
delayCalibrationOffsetClocks = clocksElapsed(SELF_CALIBRATION_TIMER);
startTimer(SELF_CALIBRATION_TIMER);
clockDelay(1);
delayCalibrationOffsetClocks += clocksElapsed(SELF_CALIBRATION_TIMER);
}
//***********************************************************************
// ************************* DELAYS ***********************************
//***********************************************************************
// for slower CPU speed (e.g. 8MHz) accuracy degrades below approx (5us)
// otherwise accuracy < 1us for smaller values, but still accurate across whole range
#define CLOCK_SCALER 5
// scaled value
void clockDelay(ULONG value) {
// assembler takes CLOCK_SCALER clocks to execute a loop iteration
__asm( " mov r3, r0 n" // load scaled value
"tloop: sub r3, #1 n" // decrement loop counter
" cmp r3, #0 n" // check if reached zero
" bne tloop "); // jump to loop label if not done
}
void usDelay(UINT usPeriod) {
ULONG periodClocks = usPeriod * NUM_CLOCKS_IN_MICROSEC;
ULONG scaledClocks;
if (periodClocks > delayCalibrationOffsetClocks) {
scaledClocks = (periodClocks - delayCalibrationOffsetClocks) / CLOCK_SCALER;
if (scaledClocks == 0) {
scaledClocks++; // run one iteration for fraction
}
clockDelay(scaledClocks); // don't pass in zero !
}
}
// (not optimised)
void msDelay(UINT msPeriod) {
usDelay(msPeriod * 1000);
}
// just for testing (not optimised)
void sDelay(UINT sPeriod) {
UINT sCount;
for (sCount = 0; sCount < sPeriod; sCount++) {
msDelay(1000);
}
}
#if defined(USE_EVENTS)
//***********************************************************************
// ************************* EVENTS ***********************************
//***********************************************************************
typedef struct eventInfoType{
UINT startTime;
UINT clocksTimeoutPeriod_OR_hwInterruptId;
UINT priority;
boolean inUse;
boolean ready;
enum eventClassType eventClass;
void (*eventHandler)();
#if defined(EVENTS_WITH_STATS)
struct eventStatsResultsType results;
struct eventStatsConfigType config;
#endif // EVENTS_WITH_STATS
};
#define MAX_NUM_EVENTS ( MAX_NUM_INTERRUPT_EVENTS + MAX_NUM_CYCLIC_TIMER_EVENTS )
#define FIRST_EVENT 1
#define NULL_EVENT 0
#define SCHEDULER_EVENT NULL_EVENT
#define DEFAULT_PRIORITY 9999
#define CONFIG_OK 1
#define NULL_HANDLER 0
#define DEFAULT_STATS_LATENCY_LOW 10 // 10us
#define DEFAULT_STATS_LATENCY_HIGH 1000 // 1ms
//************************** DATA ****************************************
static struct eventInfoType eventInfo[MAX_NUM_EVENTS + 1];
static UINT nextEventId = FIRST_EVENT; // 0 is for error
static configStatus = CONFIG_OK; // default
static UINT numInterrupts = 0;
static UINT numCyclicTimers = 0;
static UINT interruptHighestPriorityToEventIdMap[MAX_NUM_INTERRUPT_EVENTS];
static UINT cyclicTimerHighestPriorityToEventIdMap[MAX_NUM_CYCLIC_TIMER_EVENTS];
static boolean recalcCyclicTimeoutsNow = FALSE;
static UINT pollTime = 0;
//************************** MODULES ****************************************
boolean isPriorityUnique(UINT priority) {
// check for duplicate priority - not allowed
UINT eventId;
for (eventId = FIRST_EVENT; eventId < nextEventId; eventId++) {
if (eventInfo[eventId].priority == priority) {
return FALSE;
}
} // loop
return TRUE;
}
void startEvent(UINT eventId) {
if (eventId != NULL_EVENT) {
eventInfo[eventId].startTime = timeNow();
eventInfo[eventId].ready = FALSE;
eventInfo[eventId].inUse = TRUE;
if (eventInfo[eventId].eventClass == INTERRUPT) {
IntEnable(eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId);
} else {
// CYCLIC_TIMER
recalcCyclicTimeoutsNow = TRUE;
}
}
}
void stopEvent(UINT eventId) {
if (eventId != NULL_EVENT) {
if (eventInfo[eventId].eventClass == INTERRUPT) {
IntDisable(eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId);
}
eventInfo[eventId].startTime = timeNow(); // may be useful later
eventInfo[eventId].ready = FALSE;
eventInfo[eventId].inUse = FALSE;
}
}
#if defined(EVENTS_WITH_STATS)
void collectInterruptEventStats(UINT eventId) {
eventInfo[eventId].results.numEvents++;
if (eventInfo[eventId].ready) {
// another event received before processed the last one
eventInfo[eventId].results.numEventsMissed++;
} // ready
eventInfo[eventId].startTime = timeNow(); // for latency check
}
UINT usLatency(UINT eventId) {
return clocksElapsedSince(eventInfo[eventId].startTime) / NUM_CLOCKS_IN_MICROSEC;
}
void collectLatencyEventStats(UINT eventId) {
UINT latency = usLatency(eventId);
if (latency <= eventInfo[eventId].config.usLatencyThresholdLow) {
eventInfo[eventId].results.numEventsLatencyLow++;
} else {
if (latency <= eventInfo[eventId].config.usLatencyThresholdHigh) {
eventInfo[eventId].results.numEventsLatencyHigh++;
}
}
}
void resetEventStatsResults(UINT eventId) {
eventInfo[eventId].results.numEvents = 0;
eventInfo[eventId].results.numEventsDelayed = 0;
eventInfo[eventId].results.numEventsMissed = 0;
eventInfo[eventId].results.numEventsLatencyLow = 0;
eventInfo[eventId].results.numEventsLatencyHigh = 0;
}
void resetEventStatsConfig(UINT eventId) {
eventInfo[eventId].config.usLatencyThresholdLow = DEFAULT_STATS_LATENCY_LOW;
eventInfo[eventId].config.usLatencyThresholdHigh = DEFAULT_STATS_LATENCY_HIGH;
}
struct eventStatsResultsType getEventStats(UINT eventId) {
return eventInfo[eventId].results;
}
void configureEventStats(UINT eventId, struct eventStatsConfigType config) {
if (eventId != NULL_EVENT) {
eventInfo[eventId].config = config;
}
}
#endif // EVENTS_WITH_STATS
// use in ISR
void eventReady(UINT eventId) {
#if defined(EVENTS_WITH_STATS)
collectInterruptEventStats(eventId);
#endif // EVENTS_WITH_STATS
eventInfo[eventId].ready = TRUE; // go
}
boolean eventTimeout(UINT eventId, UINT currentTime) {
UINT clocks = clocksElapsedSinceTime(eventInfo[eventId].startTime, currentTime);
return clocks >= eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId ? TRUE : FALSE;
}
void recalcCyclicTimer(UINT eventId) {
// for accurate cyclicity (no drift) - calc next timeout based on last one
// not the time it was eventually processed (i.e. timeNow)
// h/w timer decrements from max to zero, then wraps
UINT nextStartTime = eventInfo[eventId].startTime - eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId;
if (nextStartTime > eventInfo[eventId].startTime) {
// wrap
nextStartTime = (hwTimerWrap() - eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId) +
eventInfo[eventId].startTime;
}
eventInfo[eventId].startTime = nextStartTime;
}
boolean cyclicTimerTimeout(UINT eventId, UINT currentTime) {
// need to process cyclics for timeout transitions
if (eventInfo[eventId].inUse && eventTimeout(eventId, currentTime)) {
// transition - timeout occurred
#if defined(EVENTS_WITH_STATS)
if (eventInfo[eventId].ready) {
// whole cycle completed without processing last timeout
eventInfo[eventId].results.numEventsMissed++; //stats
} else {
eventInfo[eventId].ready = TRUE; // go
eventInfo[eventId].results.numEvents++; //stats
}
#else
eventInfo[eventId].ready = TRUE; // go
#endif // EVENTS_WITH_STATS
// need to update for next period
recalcCyclicTimer(eventId);
}
return eventInfo[eventId].ready;
}
UINT clocksUntilEventTimeoutDue(UINT eventId, UINT currentTime) {
UINT numClocksElapsed =
clocksElapsedSinceTime(eventInfo[eventId].startTime, currentTime);
UINT timeoutPeriod = eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId;
return numClocksElapsed >= timeoutPeriod ? 0 : timeoutPeriod - numClocksElapsed;
}
void runEventHandler(UINT eventId) {
#if defined(EVENTS_WITH_STATS)
collectLatencyEventStats(eventId);
#endif // EVENTS_WITH_STATS
eventInfo[eventId].ready = FALSE;
(*eventInfo[eventId].eventHandler)();
}
void processCyclicTimers(void) {
UINT i;
UINT eventId;
UINT highestPriorityEventId = NO_EVENTS; // default
boolean cyclicTimeoutReady = FALSE;
UINT minClocksUntilTimeoutDue = HW_TIMER_WRAP; // default high
UINT clocksUntilTimeoutDue;
recalcCyclicTimeoutsNow = FALSE; // reset
// find highest priority cyclic timer event that is ready
// process all cyclics
for (i = 0; i < numCyclicTimers; i++) {
eventId = cyclicTimerHighestPriorityToEventIdMap[i];
if (cyclicTimerTimeout(eventId, pollTime)) {
// cyclic ready
if (highestPriorityEventId == NO_EVENTS) {
cyclicTimeoutReady = TRUE;
highestPriorityEventId = eventId;
} else {
#if defined(EVENTS_WITH_STATS)
// already got timeout - so this lower priority one is delayed
eventInfo[eventId].results.numEventsDelayed++;
#endif // EVENTS_WITH_STATS
recalcCyclicTimeoutsNow = TRUE; // try next poll
}
}
// calc time for next cyclic processing based on predicted cycle times
if (!recalcCyclicTimeoutsNow) {
clocksUntilTimeoutDue = clocksUntilEventTimeoutDue(eventId, pollTime);
if (clocksUntilTimeoutDue < minClocksUntilTimeoutDue) {
minClocksUntilTimeoutDue = clocksUntilTimeoutDue;
}
}
} // event loop
// update timeout for next poll
eventInfo[SCHEDULER_EVENT].startTime = pollTime;
if (!recalcCyclicTimeoutsNow) {
eventInfo[SCHEDULER_EVENT].clocksTimeoutPeriod_OR_hwInterruptId = minClocksUntilTimeoutDue;
} else {
eventInfo[SCHEDULER_EVENT].clocksTimeoutPeriod_OR_hwInterruptId = 0;
}
if (cyclicTimeoutReady) {
runEventHandler(highestPriorityEventId);
}
}
UINT processEvents(void) {
UINT eventId;
UINT i;
boolean interruptReady;
UINT highestPriorityEventId;
if (configStatus == CONFIG_ERROR) {
return NO_EVENTS; // do nothing - problem in configuration (Attention grabber)
}
while(TRUE) {
interruptReady = FALSE;
highestPriorityEventId = NO_EVENTS;
i = 0;
// find highest priority interrupt event that is ready
#if defined(EVENTS_WITH_STATS)
while (i < numInterrupts) {
eventId = interruptHighestPriorityToEventIdMap[i];
if (eventInfo[eventId].ready) {
if (!interruptReady) {
interruptReady = TRUE;
highestPriorityEventId = eventId;
} else {
eventInfo[eventId].results.numEventsDelayed++;
}
}
i++;
} // event loop
#else
// fast loop
while ((i < numInterrupts) && !interruptReady) {
eventId = interruptHighestPriorityToEventIdMap[i];
interruptReady = eventInfo[eventId].ready;
i++;
} // event loop
highestPriorityEventId = eventId;
#endif // EVENTS_WITH_STATS
// interrupts have priority over cyclics here
if (interruptReady) {
runEventHandler(highestPriorityEventId);
} else {
pollTime = timeNow();
if (recalcCyclicTimeoutsNow ||
(numCyclicTimers > 0 && eventTimeout(SCHEDULER_EVENT, pollTime))) {
// one of cyclics is due for timeout - check all
processCyclicTimers();
}
} // cyclics
} // forever
}
void resetPriorityMaps(void) {
UINT i;
for (i = 0; i < MAX_NUM_INTERRUPT_EVENTS; i++) {
interruptHighestPriorityToEventIdMap[i] = NULL_EVENT;
}
for (i = 0; i < MAX_NUM_CYCLIC_TIMER_EVENTS; i++) {
cyclicTimerHighestPriorityToEventIdMap[i] = NULL_EVENT;
}
}
void createPriorityMap(UINT eventId, UINT priority, UINT numEntries, UINT* map) {
UINT priorityEventId;
UINT i,j;
for (i = 0; i < numEntries; i++) {
priorityEventId = map[i];
if (priorityEventId != NULL_EVENT) {
if (priority < eventInfo[priorityEventId].priority) {
// higher priority event - insert
// need to shuffle others along
for (j =
|
|