Timer functions
[Pre-emptive, soft real-time scheduler]

The kernel supports an unlimited number of software timers. More...

Data Structures

struct  BX_TIME
 Timer object. More...

Defines

#define BX_TIME_INIT()
 Static initialiser for a timer.

Functions

int BX_TimerAction (BX_TIME *timer, BX_DISR_FUNC func, void *par1, void *par2)
 Sets the action of a timer.
int BX_TimerCancel (BX_TIME *timer)
 Cancels a timer.
void BX_TimerInit (BX_TIME *timer)
 Initialises a timer object.
int BX_TimerReload (BX_TIME *timer, unsigned value)
 Reloads a timer.
int BX_TimerSleep (unsigned usec)
 Suspends the caller for a given time.
int BX_TimerStart (BX_TIME *timer, unsigned expire, unsigned period)
 Arms a timer.
int BX_TimerTick (int usec)
 Timer tick function, a function from the timer ISR.
unsigned long long BX_TimerUptime (void)
 Returns the system uptime.

Detailed Description

The kernel supports an unlimited number of software timers.

The timers, like any other kernel object, are statically allocated by the user.

Resolution and time units

The timing subsystem calculates everything in microseconds. However, it does not use a hardware timer - it can not know your microcontroller or your timer allocation strategy. Therefore, it is your responsibility to set up a timer interrupt and from that handler you must call the BX_TimerTick() function. This function has one parameter, which is the number of microseconds elapsed since the previous call to the function. In some cases you have a timer interrupt source of which the period is not an exact multiple of a microsecond. A very simple trick in your timer interrupt routine can solve that problem. Here is an example. Let's assume that you get your timer interrupt from your real-time clock, which generates a 2048Hz interrupt, divided from the 32.768kHz watch crystal. The period of 2048Hz is 488.28125µs. If you simply ignore the fractional bit and call BX_TimerTick() with 488 as parameter, you generate a roughly 0.06% error. It doesn't sound too much, but in a day it adds up to almost a minute. You want better than that. Basically, you are losing 0.28125µs or 281250ps at every call. So, if you start accumulating the error and add 1µs every time your accumulated error reaches one microsec (and then subtract it from the accumulated error), you can guarantee that you are always within 1µs of the real time. So, every time you receive the 2048Hz interrupt, you add 281250ps to an accumulator. If the accumulator is less than 1000000, that is, 1µs, you tell the kernel's timer subsystem that 488µs has elapsed. If the accumulator goes above 1000000, then you know that you have accumulated 1µs error. Thus, you tell the kernel that 489µs has elapsed, subtract 1000000 from the accumulator and keep going.
This method is well known and widely used. It is often implemented in hardware, under the names of modulus counter or fractional divider. As the name implies, what you are in effect doing is a division and of course you can always simplify a division by the GCD of the dividend and the divisior. In our case 1000000 and 281250 has a GCD of 31250, thus we can replace them with 32 and 9, respectively. Using constants less than 256 has code efficiency advantages on the ARM, so the simplification is actually useful. Here's a simple code snippet to show a possible implementation:

#define DENOMINATOR     32
#define NUMERATOR       9

static int accu = 0;    // Accumulator for the modulus counter

void TimerISR( void )
{
    // Do all sorts of things to keep the interrupt system happy

    ...

    // Process the modulus counter

    accu += NUMERATOR;

    if ( accu >= DENOMINATOR ) {

        // The accumulator (i.e. the numerator) is larger than or equal
        // to the denominator. That is, the fraction became >= 1. This
        // means that we have collected a full microsec. Take it from
        // the accumulator ...

        accu -= DENOMINATOR;

        // ... and call the timer subsystem with an extra microsec.

        BX_TimerTick( 488 + 1 );
    }
    else {

        // We haven't accumulated a full microsec error yet, just
        // tell the timer subsystem that 488 microsecs have elapsed.

        BX_TimerTick( 488 );
    }
}

As you can see, the extra code is very little and rather simple. As long as you can express your period as an integer plus a fraction of two integers, of which the sum is less than 2^32 - 1 (so that you can be sure that your addition never overflows), the timing subsystem will not deviate from your crystal.
The example also indicates that BX_TimerTick() does not expect you to call it with the same argument every time. You can call it at any time you wish, passing it the number of microsecs since the last call.

So, the timer system now keeps track of microseconds. However, there's a question: how can it handle a timer of, say, 30µs if you call the BX_TimerTick() function once in every 488µs?
Well, it can't. The timer subsystem uses microsecond units, but its actual resolution is no better than the resolution of calls to the BX_TimerTick() function. Timers expire only when you call that function. If you need a real 30µs timer, then you have to create one using a hardware timer. With a 2048Hz timer interrupt your timers will be pretty accurate in the tens of milliseconds range but useless in the tens of microseconds range.

Calling timer functions from ISRs

The BX_TimerTick() function is a fast function. It does not actually do anythinhg with the timers. It knows if a timer will expire or not and if yes, it signals the kernel that it should switch to system mode and process the expiring timers. That's all BX_TimerTick() does, and it returns to your ISR immediately. Therefore, in your ISR, even if you know that the next call to BX_TimerTick() will expire a particular timer, you can not expect to see it being expired when the call returns.

This is true to all timer function calls. If you call them from an ISR, all they do is leave a message to the kernel, indicating what should be done, but they do not wait for it to be finished. The return value you receive about success or failure is still true. The routine called from the ISR knows up front if the requested action will succeed or not. For example, if you have a running timer and you cancel it from an ISR, you get a success return value, but the timer is still attached to the timer subsystem. If a subsequent ISR also cancels the timer, before the kernel had time to actually detach it, the second ISR will get a return value indicating that the timer was already cancelled. The consistency is maintained, even though the timer in fact is still a running timer. Before the kernel returns to a user process, however, the timer will be cancelled and truly detached from the timer subsystem.

Timer action

When a timer expires, it performs an action defined by you. More precisely, it generates a soft interrupt of your choice. That is, it will call a function with two parameters and that function will execute in the system context. The function as well as its argument can be defined by a timer by timer basis, and you can change it even on a running timer. The BX_TimerAction() call is used to set it up. Quite often timers are used as a timeout for some external condition. So, you would do something like this (error checks are omitted):

// We have a timer, quite intuitively called "timer".
// Let's assume that it's already initialised and is not
// doing anything at the moment.

static BX_TIME  timer;

void    SomeFunction( void )
{
int     event;

    // We start some hardware operation now. It will send us a signal
    // at completion; we specify the signal in the argument.

    StartSomeHardware( MY_HW_DONE_SIGNAL );

    // Now we specify the timer action for the timer. We want to get a
    // signal when the timer expires. The timer calls a function with
    // two (32-bit) parameters. The BX_SignalSend() function is like that,
    // although its prototype is different, but the number (and size) of
    // the arguments match. So we just cast it to the proper type and we
    // also cast its parameters to the proper type:

    BX_TimerAction( &timer, (BX_DISR_FUNC) BX_SignalSend,
                    (void *) BX_ProcSelf(), (void *) MY_TIMEOUT_SIGNAL );

    // Let's start the timer

    BX_TimerStart( &timer, TIMEOUT_TIME_IN_MICROSEC, 0 );

    // We can now wait for either the timeout or the HW to finish

    event = BX_EventWait( MY_HW_DONE_SIGNAL | MY_TIMEOUT_SIGNAL );

    if ( event & MY_TIMEOUT_SIGNAL ) {

        // We had a timeout, stop the hardware and do whatever we have to.
    }
    else {

        // The hardware finished in time, cancel the timer

        BX_TimerCancel( &timer );
    }

    ...

Initialisation

Before use, a timer has to be initialised. The initialisation only prepares the data structure to act as a timer, but otherwise it doesn't do anything. You can either call BX_TimerInit() or you can initialise the timer structure statically using the TIMER_INIT() macro. Note that using a timer that has not been initialised will cause either the functions returning a run-time error code (if they realise that the timer was not initialised), or, if you are unlucky and the memory image of the structure contains data that looks like a timer, a system crash. Initialising a running timer will also cause a system crash. Statically allocated timers need to be initialised once, at startup, and never again.
You can also allocate timers on the stack, if you make sure about two things:

If you forget either of these, you will end up with very hard to debug crashes.

Starting a timer

Once you initialised your timer and set up its action, you can start it. The BX_TimerStart() function gets a timer and two time values. The first time value is the time to the first expiry of the timer. If the second value is 0, then that's all the timer does. If it is not 0, however, then the timer, when it expires, will restart itself with that value. The difference between you restarting the timer in the timer expiry soft interrupt and the timer restarting itself is that the timer takes the latencies into account. If you set up a periodic timer with 10ms period, there might be some variation between subsequent expiry times, but on average the associated soft interrupt will be activated at 100Hz. When you do it on your own, the delay between the timer expiring and your soft interrupt restarting the timer is not compensated anywhere and your average frequency will be less than 100Hz. However, keep in mind that if one soft interrupt from a periodic timer is late, it will compensate for it and thus your next soft interrupt will arrive in less time than the period of timer is. You can not rely on two consecutive soft interrupts to be at least a period apart. A periodic timer is most useful if you want to guarantee that some activity happens with a certain frequency, at least on average.

Reloading a timer

If you have a timer running, sometimes you just want to restart it. The BX_TimerReload() function does that for you. A practical scenario is a serial comms handler that expects that characters are coming more or less constantly from the other end. Small delays are accepted, but if the other end stops for an extended period, you have to signal error.
You can set up a timer with the expiry time of the too long silence and whenever you receive a character, you just restart it. Note that once the timer expires, you can not restart it any more. Also note that this technique is more suitable for slow comms. If you have a 115.2kbaud line, then calling BX_TimerReload() after each character, i.e. with 10kHz will work but it will consume a serious amount of CPU time. For a 9600baud line the technique is fine.

Cancelling a timer

If you do not need a timer any more, you can cancel it. After the cancellation the timer will not be attached to the kernel's timer subsystem in any way and you can, if you want, release the timer structure. The BX_TimerCancel() call leaves the structure initialised and it's action settings intack, so you can call BX_TimerStart() after a cancel without any other timer calls. You do not have to cancel a timer that has expired and did not restart itself; from the user's point of view the only difference between a non-periodic timer expiring and cancelling a timer is that cancellation does not call the timer's associated action function.


Function Documentation

int BX_TimerAction ( BX_TIME timer,
BX_DISR_FUNC  func,
void *  par1,
void *  par2 
)

Sets the action of a timer.

Parameters:
timer Pointer to the timer object to be created.
func The soft interrupt function to call at expiry
par1 The first argument of the soft interrupt function
par2 The second argument of the soft interrupt function
Return values:
>0 Success
BX_ERR_OBJECT The object is not a timer or wasn't initialised
int BX_TimerCancel ( BX_TIME timer  ) 

Cancels a timer.

Parameters:
timer Pointer to the timer to be cancelled
Return values:
>0 The timer was successfully cancelled
0 The timer was not running
BX_ERR_OBJECT The timer parameter was not a valid timer object
void BX_TimerInit ( BX_TIME timer  ) 

Initialises a timer object.

This function simply sets the pointed object to become an unused timer. This function should only be called once on any particular timer. Calling this function on a timer that is already running will probably cause a system crash.

Parameters:
timer Pointer to the timer object to be initialised.
int BX_TimerReload ( BX_TIME timer,
unsigned  value 
)

Reloads a timer.

Parameters:
timer Pointer to the timer to be reloaded
value The time value to load into the timer. If this value is 0, then the period of the timer will be used. If the period is als0 0, then the timer will not be touched and 0 is returned.
Return values:
>0 The timer was successfully reloaded
0 The timer was not running or should have been reloaded with 0.
BX_ERR_OBJECT The timer parameter was not a valid timer object
BX_ERR_INACTIVE The timer was not running
int BX_TimerStart ( BX_TIME timer,
unsigned  expire,
unsigned  period 
)

Arms a timer.

The function arms a timer. When the timer expires, it will invoke a delayed ISR function. If a reload value is specified, then upon expiry the timer will not be deactivated, but restarted with the specified reload value.

Parameters:
timer Pointer to an initialised timer object
expire The time to wait before the timer expires, in microseconds. Note that the longest period the timer can wait is 1h 11m 34.9s. Also note that although the time is expressed in microsecs, the resolution of the timers depends on the frequency with which the BX_TimerTick() function is called.
period If this value is not 0, then the timer will be restarted with this value when it expires. If it is 0, then upon expiry the timer becomes inactive.
Return values:
>0 The timer was activated
BX_ERR_OBJECT The timer object is not initialised
BX_ERR_INUSE The timer object is already running
BX_ERR_PARAM The expire argument was 0
int BX_TimerTick ( int  usec  ) 

Timer tick function, a function from the timer ISR.

The function receives the number of microsecs that elapsed since it was called the last time. It then handles all the software timers and also maintains the uptime and absolute time in the system. If your timer interrupt period is not an exact multiple of a microsec, see the example code for a solution to keep your timing precise.

Parameters:
usec The number of microseconds elapsed since the last call. The number must be at least 1us. Note that this is a signed number, so the max. value is 0x7fffffff microsecs.
Return values:
>0 Success
BX_ERR_CONTEXT The function was not called from an ISR
BX_ERR_PARAM The argument was not inside the limits
Note:
This function must be called from a real IRQ ISR context.
unsigned long long BX_TimerUptime ( void   ) 

Returns the system uptime.

This function returns the uptime. It returns an unsigned long long value. The upper 32 bits contain the number of seconds and the lower 32 bits is the microseconds. This method was chosen over returning a structure because structures are always returned on the stack while a long long is returned in 2 32-bit registers. The compiler is smart enough to know that if you shift a long long to the right by 32 bits and assign it to a 32-bit variable it does not have to actually shift, but use the register representing the upper half of the long long.

Return values:
((uptime_microsecs/1E6)<<32)+(uptime_microsecs%1E6) 
Generated on Mon Aug 2 18:49:51 2010 by  doxygen 1.6.3