Thursday, 25 October 2012

Embedded C Timing Functions

This post is about simple timing functions written in C for embedded processors or micro-controllers such as PICs. The timing functions here are a delay function and function that you poll to see if a set amount of time has elapsed.

The most basic timer feature I have seen is a global variable, or tick count, that is incremented every time a timer interrupt is fired. The problem here is if another piece of code interferes with this variable or if the timer interrupt fires during the read of the tick count value. The code below instead safely wraps the tick count and returns a structure. The code is written for the CCS  C compiler for PIC micros.

/*
  Timer.h
*/
#ifndef TIMING_H
#define TIMING_H

#include "defines.h"

typedef struct
{
 uint32 Duration;
 uint32 StartCount;
 uint32 EndCount;
} TimerInfo;

#inline
extern void OnTick(void);
extern uint32 GetTickCount(void);

extern void StartTimer(TimerInfo* timerInfo, uint16 duration);
extern bool HasTimerElapsed(TimerInfo* timerInfo);
extern void TimerWait(uint16 duration);


#ifdef _DEBUG
 void SetTickCount(unsigned int32 tickCount);
#endif

//Include guard
#endif



/*
        
 Timer.c

 Description:
 The aim of this code file is to implement a background timer in a safer manner by
 making the TickCount read-only.

 To perform a timed task:

 TimerInfo timerInfo; 
 StartTimer(&timerInfo, 42);
 while(!HasTimerElapsed(&timerInfo))
 {
   //perform some task
 }


*/
#include "Timing.h"

volatile static uint32 _TickCount = 0;

uint32 GetTickCount(void)
{
 uint32 nonVolatile;
 disable_interrupts(INT_TIMER0);
 nonVolatile = _TickCount;
 enable_interrupts(INT_TIMER0);
 return nonVolatile;
}


/*
 Call this method from the timer interrupt to increment the tick count.
*/
void OnTick(void)
{
 _TickCount++;
}


/*
 Call this method at the start of a timed task.
 Usage:
  TimerInfo timerInfo = StartTimer(42);
  while(!HasTimerElapsed(timerInfo)){//perform some task}

 Inputs:  duration - number of ticks to wait for
 Returns: TimerInfo - A structure holding the relevant timer information
*/
void StartTimer(TimerInfo* timerInfo, uint16 duration)
{
 timerInfo->Duration = duration;
 timerInfo->StartCount=GetTickCount();
 timerInfo->EndCount = timerInfo->Duration+timerInfo->StartCount;
}


/*
 Poll this method to determine if the timer has elapsed.
 Usage:
  TimerInfo timerInfo = StartTimer(42);
  while(!HasTimerElapsed(timerInfo)){//perform some task}

 Inputs:  TimerInfo - Timer information as returned by StartTimer()
 Returns: bool  - True timer has elapsed, false - timer has not elapsed

*/
bool HasTimerElapsed(TimerInfo* timerInfo)
{
 uint32 tickCount = GetTickCount();
 //Timer info uses uint32, but the logic still needs to be able to cope with overflow.
 if (timerInfo->EndCount >= timerInfo->StartCount)
 {
  return (tickCount>=timerInfo->StartCount && tickCountEndCount) ? false : true;
 }
 return (tickCount>=timerInfo->EndCount && tickCountStartCount) ? true : false;
}


/*
 Wait for the specified number of ticks
 Inputs: duration - number of ticks to wait
*/
void TimerWait(uint16 duration)
{
 TimerInfo timer;
 StartTimer(&timer, duration);
 while(!HasTimerElapsed(&timer)){}
}

#ifdef _DEBUG
 /*
  Allow unit tests to manipulate _TickCount
 */
 void SetTickCount(uint32 tickCount){ _TickCount=tickCount;}
#endif
The tick count is marked as volatile, this warns the compiler not to optimize the use of this variable and to always read from its memory location. You'll notice that I disable and then enable interrupts when reading the tick count. To increase the tick count you call OnTick() from your timer interrupt sub-routine, I've marked this function as inline to save doing the function call.

To use the code, you first need to get a TimerInfo which contains the start time, and then pass the TimerInfo to HasTimerElapsed() which returns true, the time has elasped, or false, the time has not elapsed. See my previous posts about C Type defines if you're wondering where the bool has come from.

You can easily port the code, but you will need to replace the #inline pre-processor and the disable/enable interrupt calls.

No comments:

Post a Comment