Hooking in the Timer

Jump to: navigation, search
<< Creating a Kernel Interface
Before Running the Code >>



Now that you have compiled the library and created an RTOS interface, you are ready to consider how you must hook in the timer. Notice that the timer interface is not part of the RTOS interface. This is because the timer interface can be hooked up in different ways. These different methods do not depend on the RTOS interface. There are several different methods to hook up the timer. Two of the most common are:


  1. Use a Timer Task to Update and Execute the Timers.
  2. Use a Timer ISR to Update the Timers, and execute from either a main line loop or a task.
Note Note: You must not call any timer functions prior to calling tfStartTreck().


In version 4.7 of the Treck stack, more options were added to update and execute the timers. These options were primarily added for customers interested in power saving modes of operation. To avoid unnecessary calls to update and execute the timers three new features were added.

  1. Manual Updates of tvTime and tvTimeRollover
  2. Notification of required calls to tfTimerExecute()
  3. Polling the stack for required calls to tfTimerExecute()

The two variables that maintain Treck's internal clock, tvTime and tvTimeRollover, are global and shared by multiple instances of the Treck stack in the case where multiple contexts are used. While the internal clock variables are global, the internal timers are maintained in a list on a per-context basis. So if you run multiple instances (contexts) of the stack, you need to make sure to register a timer callback function for each instance of the stack. If you want to query the stack for the next expiration interval, you need to do so for each context. Also tfTimerExecute() needs to be called for each context.


Use a Timer Task to Update and Execute the Timers

In this model, you will have a simple loop in a separate timer task. In this loop you will update the timers and execute the timers that have expired. You will then pause the task for some fixed interval. This interval MUST match the tick count that you have setup for the system (see <trsystem.h> or tfInitTreckOptions()). This is the most popular method to use when you have a real time operating system. An example of this method is as follows:

#include <trsocket.h>
void timerTask(void)
{
    while(1)
    {
/* Update the Timers */
        tfTimerUpdate ();
/* Execute the Timers */
        tfTimerExecute ();
/* Call the RTOS to delay for 1 clock tick */
        RTOSTimeDelay(1);
    }
}

If your RTOS does not allow you to start a task at run-time, then you should use a simple flag or semaphore to prevent the calls to the timer prior to calling tfStartTreck().

An example of this is as follows:

#include <trsocket.h>
static int treckStarted=0;
 
void mainTask(void)
{
    int errCode;
 
    tfInitTreck();
    errCode=tfStartTreck();
    if (errCode=TM_ENOERROR)
    {
        treckStarted=1;
/* Other initialization code follows ... */
    }
}
 
void timerTask(void)
{
    while(1)
    {
/* Check to make sure that Treck has started */
        if (treckStarted)
        {
/* Update the Timers */
            tfTimerUpdate ();
/* Execute the Timers */
            tfTimerExecute ();
        }
/* Call the RTOS to delay for 1 clock tick */
        RTOSTimeDelay(1);
    }
}


Note Note: A good timer interval is between 10ms and 100ms. It is not necessary that the timer be VERY accurate between calls to tfTimerUpdate(), but it should be accurate on average over time. It is usually best to set the timer task to the highest priority of tasks operating with the protocol stack, but it is not necessary.

Use a Timer ISR to Update the Timers

In this method, you simply update the timers from an ISR routine using the call tfTimerUpdateIsr(). Note that this is a different call than from the call made at task level (tfTimerUpdate()). This call is designed to be able to be called from an interrupt handler. It is not recommended to use this method if you have an RTOS. You must NOT call the function tfTimerUpdateIsr() before you call the function tfStartTreck(). In this regard, it has the same rules as tfTimerUpdate(). An example of using this method for a main line loop is as follows:

#include <trsocket.h>
static int treckStarted=0;
 
void timerIsrHandler(void)
{
/* Check to make sure that Treck has started */
    if (treckStarted)
    {
/* Update the Timers */
        tfTimerUpdateIsr ();
    }
}
 
void main(void)
{
    int errCode;
    errCode=tfStartTreck ();
    treckStarted=1;
/* Other initialization code follows ... */
    if (errCode == TM_ENOERROR)
    {
        while(1)
        {
            tfTimerExecute ();
/* Application code follows ... */
        }
    }
}


Note Note: A good timer interval is between 10ms and 100ms. It is not necessary that the timer be VERY accurate between calls to tfTimerUpdateIsr(), but it should be accurate on average over time.


<< Creating a Kernel Interface
Before Running the Code >>


Manual Updates of tvTime and tvTimeRollover

The user will be able to manually update tvTime and tvTimeRollover which will eliminate the need to call tfTimerUpdate() every x milliseconds. This can be done through direct assignment however it is possible to update other variables if they are setup such that they point to tvTime and tvTimeRollver in <trsystem.h>.

Value Meaning
tvTime This is an unsigned 32-bit variable that contains the number of elapsed milliseconds since startup.
tvTimeRollover This is an unsigned 32-bit variable that contains the number of times tvTime wraps around to 0x00.


Notification of required calls to tfTimerExecute()

tfTimerExecute() only needs to be called when the next timer is about to expire. The user will be able to register a timer callback function which will indicate the amount of time in milliseconds that can elapse before a call to tfTimerExecute() is required. This callback is invoked by the Treck Stack each time a new timer is added/modified which changes the previous callback's indicated time interval to a smaller value and also at the completion of each tfTimerExecute() call (thus updating the user with the new timeframe). So the user only needs to keep track of the latest time interval given by the stack.

Compile Time Macros

Define the macro TM_USE_TIMER_CB in <trsystem.h> to enable the new timer CB code.

Registering a Timer Callback Function

To register a timer callback function use tfRegisterTimerCB().


Polling the stack for required calls to tfTimerExecute()

Compile Time Macros

Define the macro TM_USE_TIMER_INTERVAL_QUERY in <trsystem.h> to enable the timer interval query code.

Querying the Stack

To query the stack for the timer interval (in milliseconds) for the next call to tfTimerExecute() use tfTimerIntervalQuery().

Configuring the Timer Cache

By default, the Timer code uses a timer wheel (for timers set to expire within the maximum timer wheel interval), and one timer active queue (for timers set to expire within an interval bigger than the maximum timer wheel interval, called the minimum active queue interval). By default this timer cache (including a timer wheel, and one active queue) is activated, as using a timer wheel reduces the Timer Execute CPU load.

Timer Wheel

Timers are inserted in the wheel when they are to expire in an amount of time below the maximum timer wheel interval. The timer wheel contains several slots arranged in a circular fashion. The slot passed the last slot on the wheel is slot zero. Each slot contains a list of zero or more timers, all set to expire within the same tick. The timer code keeps track of a current slot for a given current time. Timers set to expire within x ticks from the given current time at the current slot, are inserted in the xth slot from the current slot. When the timer execute function is called, the timer only needs to run the consecutive slots that have expired between the previous call, and the current call to tfTimerExecute(), and to update the current slot and current time.

Timer Active Queue

When a timer is set to expire within an interval bigger than the maximum timer wheel interval, it is inserted in the Timer Active Queue. If the Timer Active Queue is empty, the queue is marked to be scanned in the maximum timer wheel interval. This guarantees that the Timer Execute does not need to scan the Time Active Queue any sooner than the maximum timer wheel interval.

Multiple Timer Active queues

By default a single Timer Active Queue is used in addition to the timer wheel. When many timers are used, the user can elect to make the timer wheel bigger (see "changing the maximum timer interval on the timer wheel" below), or/and can elect to have multiple timer active queues with increasing minimum timer interval, beyond the wheel maximum timer interval. When many timers are used, it is preferable to make the timer wheel bigger, but if memory is tight, a good compromise would be to use a medium size wheel, with multiple timer active queues.

When a timer is set to expire within an interval bigger than the maximum timer wheel interval, it is inserted in a timer active queue. Each timer active queue has a minimum timer interval. A timer active queue is selected based on the interval to fire. If that timer active queue is empty, the queue is marked to be run in the minimum timer interval for that queue (its stop time). The timer execute only needs to scan the active queues that are non empty and whose cached stop time has expired. When the timer execute scans an active queue, it moves all the timers whose time left to fire is less than the minimum interval of fire for that queue, to a different active queue, or to the timer wheel selected based on the amount of time left to fire. This guarantees that each active list does not need to be scanned sooner than its minimum interval. Note that a timer inserted in the fourth list could theoretically move through all the other active lists before reaching the wheel. So there is some overhead associated with using multiple timer active queues.

To use four active queues (instead of one), define TM_USE_TIMER_MULT_ACTIVE_LIST in <trsystem.h>.


Note Note: In power save mode (i.e. when TM_USE_TIMER_CB is defined), it is best to define the TM_USE_TIMER_MULT_ACTIVE_LIST macro, if the timer wheel is relatively small.


Disabling the Timer Active queues

By default a single Timer Active Queue is used in addition to the timer wheel. When many timers are used, the user can elect to make the timer wheel bigger (see "changing the maximum timer interval on the timer wheel" below), and can elect to not have any active queue beyond the wheel maximum timer interval.

With no active queues, when a timer is set to expire within an interval bigger than the maximum timer wheel interval, it is inserted at the end of the wheel. The timer execute code will double check that the timers in a given slot are set to expire. The timers that are not set to expire yet will be moved to their correct location on the wheel.

To disable the Timer Active Queues, define TM_DISABLE_TIMER_ACTIVE_LISTS in <trsystem.h>.


Note Note: In power save mode (i.e. when TM_USE_TIMER_CB is defined), it is best to not disable the timer active queues.


Note Note: If you elect to disable the timer active queues, you should make the timer wheel much bigger than 500ms.


TM_USE_65BIT_TIME, Using a 64-bit time

When many timers are used, and the user elects to make the timer wheel bigger (see "changing the maximum timer interval on the timer wheel" below), it is best to use a global 64-bit time variable. This is only possible if you are running on a 64-bit CPU. To enable a 64-bit time variable, define TM_USE_64BIT_TIME in <trsystem.h>. In addition you will need to update the global variable tvTime64, the same way you update the global variable tvTime.


Note Note: If you enable the tvTime64 global variable (i.e. when TM_USE_64BIT_TIME is defined), you will need to update the tvTime64 variable the same way you update the global variable tvTime.


Note Note: Enabling the tvTime64 global variable (i.e. when TM_USE_64BIT_TIME is defined), is only possible on a 64-bit CPU.


Default Values

Default Case

Timers inserted in one of the following (wheel, or active queue) based on time left to fire:

Minimum interval of firing Maximum interval of firing
Timer Wheel 1 tick 500 ms
Active Queue > 500 ms (max wheel intv) unlimited


Multiple Active Queues

Timers inserted in one of the following (wheel, or active queues) based on time left to fire:

Minimum interval of firing Maximum interval of firing
Timer Wheel 1 tick 500
First Active queue > 500 (max wheel intv) 1000
Second Active Queue > 1000 (2 * max wheel intv) 8000
Third Active Queue > 8000 (8 * max wheel intv) 32000
Fourth Active Queue > 32000 (64 * max wheel intv) unlimited

Memory requirements

Timer Wheel

The overhead is 12 bytes per slot on a 32-bit CPU. So the memory requirements for a timer wheel are 12 times the number of slots. The timer wheel memory overhead depends on the tick length as we allocate one slot per tick. The table below gives examples of memory overhead for different maximum intervals of firing on the wheel, and for 10 ms and 100 ms tick lengths.


Maximum interval of firing on the wheel in ms Tick length in ms Number of slots Wheel overhead in bytes
500 10 51 612
500 100 6 72
4000 10 401 4812
4000 100 41 492
7500 10 7501 90012
7500 100 76 912

Active queues

The overhead per active queue is 20 bytes on a 32-bit CPU if TM_USE_TIMER_CB is not defined, 24 bytes otherwise.


Changing the Maximum Timer Interval

When many timers are used, the user can elect to make the timer wheel bigger, as doing so will reduce the timer execute CPU load. This can be done at compile time, or at run time prior to calling tfStartTreck().

Compile Time

The maximum timer interval on the timer wheel can be changed at compile time by adding the TM_TIM_MAX_WHEEL_INTV macro definition to <trsystem.h>.

For example, to change the maximum timer interval on the timer wheel to 4000 ms, add:

#define TM_TIM_MAX_WHEEL_INTV 4000

Run Time

The maximum timer interval on the timer wheel can be changed at run time prior to starting the Treck stack, using the TM_OPTION_TIMER_MAX_WHEEL_INTV option with tfInitTreckOptions(). For example, to change the maximum timer interval on the timer wheel to 4000 ms:

errorCode = tfInitTreckOptions(TM_OPTION_TIMER_MAX_WHEEL_INTV, 4000);

Disabling the Timer Cache

If you wish to disable the timer cache to save data and code space, the timer code will use a single active timer queue instead, and will not use a timer wheel, and will not cache the next timer to expire.

To disable the timer cache, add the TM_DISABLE_TIMER_CACHE macro definition to <trsystem.h>.


Note Note: Disabling the timer cache, will also disable the user Timer Callback feature.


Restricting the Number of Timers Executed

The user can restrict the number of timers executed per tfTimerExecute() call (with the TM_OPTION_TIMER_MAX_EXECUTE option). Even with the timer cache on, the maximum number of timers executed per call to tfTimerExecute() is the number given by that option. For example, to change the number of timers to be executed by tfTimerExecute() to 2:

errorCode = tfSetTreckOptions(TM_OPTION_TIMER_MAX_EXECUTE, 2);


Note Note: Default value is 0, meaning no restriction.