Home arrow Support arrow Forums

Luminary Micro Forums

<< Start < Prev 1 2 Next > End >>

dereksoftstuff

Expert Boarder
Click here to see the profile of this user

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_TIMERINTERRUPT}; #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 eventIdstruct 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_BASETIMER_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_BASETIMER_CFG_32_BIT_PER);        TimerLoadSet(TIMER0_BASETIMER_AHW_TIMER_WRAP); //in startup.c    TimerIntRegister(TIMER0_BASE, TIMER_TIMA_TIMEOUT, Timer0IntHandler);        // Setup the interrupts for the timer timeouts     IntEnable(INT_TIMER0A);     TimerIntEnable(TIMER0_BASETIMER_TIMA_TIMEOUT);     // Enable the timer     TimerEnable(TIMER0_BASETIMER_A);                    // some magic ...     selfCalibration();      #if defined(USE_EVENTS)         resetPriorityMaps(); #endif // USE_EVENTS     } // local modules //*********************************************************************** UINT timeNow(void) {     return TimerValueGet(TIMER0_BASETIMER_A); } UINT hwTimerWrap(void) {     return TimerLoadGet(TIMER0_BASETIMER_A); } UINT clocksElapsedSinceTime(UINT startTimeUINT currentTime) {     UINT numClocksElapsed;     if (currentTime startTime) {         // wrap         numClocksElapsed startTime + (hwTimerWrap() - currentTime);         } else {         numClocksElapsed startTime currentTime;     }   return numClocksElapsed; } UINT clocksElapsedSince(UINT startTime) {   return clocksElapsedSinceTime(startTimetimeNow()); } //*********************************************************************** // *************************  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 0sCount sPeriodsCount++) {         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_EVENTeventId nextEventIdeventId++) {         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 eventIdstruct 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 eventIdUINT currentTime) {     UINT clocks clocksElapsedSinceTime(eventInfo[eventId].startTimecurrentTime);     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 eventIdUINT currentTime) {          // need to process cyclics for timeout transitions      if (eventInfo[eventId].inUse && eventTimeout(eventIdcurrentTime)) {         // 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 eventIdUINT currentTime) {     UINT numClocksElapsed =          clocksElapsedSinceTime(eventInfo[eventId].startTimecurrentTime);     UINT timeoutPeriod eventInfo[eventId].clocksTimeoutPeriod_OR_hwInterruptId;          return numClocksElapsed >= timeoutPeriod 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 (0numCyclicTimersi++) {                         eventId cyclicTimerHighestPriorityToEventIdMap[i];                  if (cyclicTimerTimeout(eventIdpollTime)) {             // 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(eventIdpollTime);             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;              0;         // find highest priority interrupt event that is ready                  #if defined(EVENTS_WITH_STATS)                      while (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 ((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 && eventTimeout(SCHEDULER_EVENTpollTime))) {                 // one of cyclics is due for timeout - check all                 processCyclicTimers();             }             } // cyclics                  // forever } void resetPriorityMaps(void) {          UINT i;     for (0MAX_NUM_INTERRUPT_EVENTSi++) {         interruptHighestPriorityToEventIdMap[i] = NULL_EVENT;     }          for (0MAX_NUM_CYCLIC_TIMER_EVENTSi++) {         cyclicTimerHighestPriorityToEventIdMap[i] = NULL_EVENT;     } } void createPriorityMap(UINT eventIdUINT priorityUINT numEntriesUINTmap) {          UINT priorityEventId;     UINT i,j;          for (0numEntriesi++) {         priorityEventId map[i];                  if (priorityEventId != NULL_EVENT) {                          if (priority eventInfo[priorityEventId].priority) {                 // higher priority event - insert                                  // need to shuffle others along                 for (