Timer Management Internals

Timer States

The figures below show the state diagrams for One-Shot and Periodic timers.

Tasks can call OSTmrStateGet() to find out the state of a timer. Also, at any time during the countdown process, the application code can call OSTmrRemainGet() to find out how much time remains before the timer reaches zero (0). The value returned is expressed in “timer ticks.” If timers are decremented at a rate of 10 Hz then a count of 50 corresponds to 5 seconds. If the timer is in the stop state, the time remaining will correspond to either the initial delay (one shot or periodic with initial delay), or the period if the timer is configured for periodic without initial delay.

One-Shot Timers

Figure - One-Shot Timer State Diagram

(1) The “Unused” state is a timer that has not been created or has been “deleted.” In other words, µC/OS-III does not know about this timer.

(2) When creating a timer or calling OSTmrStop(), the timer is placed in the “stopped” state.

(3) A timer is placed in running state when calling OSTmrStart(). The timer stays in that state unless it’s stopped, deleted, or it expires.

(4) The "Timeout" state is where we process timers that have expired. In particular, timer callbacks are executed in this state.

(5) The “Completed” state is reached once a one-shot timer has been processed. However, if the timer's own callback stops, restarts or deletes it, this state will be bypassed.

Periodic Timers

Figure - Periodic Timer State Diagram

(1) The “Unused” state is a timer that has not been created or has been “deleted.” In other words, µC/OS-III does not know about this timer.

(2) When creating a timer or calling OSTmrStop(), the timer is placed in the “stopped” state.

(3) A timer is placed in running state when calling OSTmrStart(). The timer stays in that state unless it’s stopped, deleted, or it expires.

(4) The "Timeout" state is where we process timers that have expired. In particular, timer callbacks are executed in this state. A periodic timer is returned to the running state once it is processed, unless the timer's own callback stops, restarts, or deletes it.


OS_TMR

A timer is a kernel object as defined by the OS_TMR data type (see os.h) as shown in the listing below:

The services provided by µC/OS-III to manage timers are implemented in the file os_tmr.c. Timer services are enabled at compile time by setting the configuration constant OS_CFG_TMR_EN to DEF_ENABLED in os_cfg.h.

Listing - OS_TMR data type
typedef  struct  os_tmr  OS_TMR;               (1)
 
 
struct  os_tmr {
    OS_OBJ_TYPE          Type;                 (2) 
    CPU_CHAR            *NamePtr;              (3) 
    OS_TMR_CALLBACK_PTR  CallbackPtr;          (4) 
    void                *CallbackPtrArg;       (5) 
    OS_TMR              *NextPtr;              (6) 
    OS_TMR              *PrevPtr;                  
    OS_TICK              Remain;               (7) 
    OS_TICK              Dly;                  (8) 
    OS_TICK              Period;               (9) 
    OS_OPT               Opt;                 (10) 
    OS_STATE             State;               (11) 
};

(1) In µC/OS-III, all structures are given a data type. In fact, all data types start with “OS_” and are all uppercase. When a timer is declared, you simply use OS_TMR as the data type of the variable used to declare the timer.

(2) The structure starts with a “Type” field, which allows it to be recognized by µC/OS-III as a timer. Other kernel objects will also have a “Type” as the first member of the structure. If a function is passed a kernel object, µC/OS-III is able to confirm that it is passed the proper data type (assuming OS_CFG_OBJ_TYPE_CHK_EN is set to  DEF_ENABLED  in os_cfg.h). For example, if passing a message queue (OS_Q) to a timer service (for example OSTmrStart()) then µC/OS-III will be able to recognize that an invalid object was passed, and return an error code accordingly.

(3) Each kernel object can be given a name for easier recognition by debuggers or µC/Probe. This member is simply a pointer to an ASCII string which is assumed to be NUL terminated.

(4) The .CallbackPtr member is a pointer to a function that is called when the timer expires. If a timer is created and passed a NULL pointer, a callback would not be called when the timer expires.

(5) If there is a non-NULL .CallbackPtr then the application code could have also specified that the callback be called with an argument when the timer expires. This is the argument that would be passed in this call.

(6)  .NextPtr and .PrevPtr are pointers used to link a timer in a doubly linked list. These are described later.

(7) The .Remain field contains the amount of time remaining for the timer to expire. This value is updated each time the timer task executes (described later). The value is expressed in multiples of 1/OS_CFG_TMR_TASK_RATE_HZ of a second (see os_cfg_app.h).

(8) The .Dly field contains the one-shot time when the timer is configured (i.e., created) as a one-shot timer and the initial delay when the timer is created as a periodic timer. The value is expressed in multiples of 1/OS_CFG_TMR_TASK_RATE_HZ of a second (see os_cfg_app.h).

(9) The .Period field is the timer period when the timer is created to operate in periodic mode. The value is expressed in multiples of 1/OS_CFG_TMR_TASK_RATE_HZ of a second (see os_cfg_app.h).

(10) The .Opt field contains options that are passed to OSTmrCreate().

(11) The .State field represents the current state of the timer (see the figure in Timer States, above).


Even if the internals of the OS_TMR data type are understood, the application code should never access any of the fields in this data structure directly. Instead, you should always use the Application Programming Interfaces (APIs) provided with µC/OS-III.

Timer Task

OS_TmrTask() is a task created by µC/OS-III (assumes setting OS_CFG_TMR_EN to DEF_ENABLED in os_cfg.h) and its priority is configurable by the user through µC/OS-III’s configuration file os_cfg_app.h (see OS_CFG_TMR_TASK_PRIO). OS_TmrTask() is typically set to a medium priority.

As of V3.07, OS_TmrTask() no longer runs periodically. It now supports dynamic timer management, which is to say it only runs when it needs to process a change to the timer list. During normal operation, the task determines which timer will timeout next and performs a delay for that length of time. This approach helps reduce power consumption and is better suited to low-power applications, where a periodic wake-up is often undesirable.

Figure - Timer Task

(1) The timer task begins its core loop by checking if any timers are available in the timer list.

(2) If no timers are available, the timer task waits indefinitely for a timer to be added. This helps to conserve power while the task has nothing to do. Once a timer is added, a signal will wake it up and we can check the timer list again.

(3) If a timer is available in the list, we want to delay the timer task until it expires. Since timers are stored in a delta list, we only need to check the first timer in the list to know how long we should delay. However, if a timer API call requires us to load a new delay value, we must wake up early to do so.

(4) Regardless of which condition caused us to wake-up in Step 3, we must account for the time which has passed while we were asleep.

(5) If any timers have expired, we must process them. Callbacks are executed, timer states are advanced, and completed one-shot timers are removed from the timer list.


The figure below shows timing diagram associated with the timer management task.

Figure - Timing Diagram

(1) The tick ISR occurs and assumes interrupts are enabled and executes.

(2) The tick ISR signals the tick task that it is time for it to update timers.

(3) The tick ISR terminates, however there might be higher priority tasks that need to execute (assuming the timer task has a lower priority). Therefore, µC/OS-III runs the higher priority task(s).

(4) When all higher priority tasks have executed, µC/OS-III switches to the timer task and determines that there are three timers that expired.

(5) The callback for the first timer is executed.

(6) The callback for the second expired timer is executed.

(7) The callback for the third expired timer is executed.


There are a few interesting things to notice:

  • Execution of the callback functions is performed within the context of the timer task. This means that the application code will need to make sure there is sufficient stack space for the timer task to handle these callbacks.
  • The callback functions are executed one after the other based on the order they are found in the timer list.
  • The execution time of the timer task greatly depends on how many timers expire and how long each of the callback functions takes to execute. Since the callbacks are provided by the application code they have a large influence on the execution time of the timer task.
  • The timer callback functions must never wait on events because this would delay the timer task for excessive amounts of time, if not forever.
  • Callbacks should execute as quickly as possible.

Timer List

µC/OS-III applications may require many timers. The timer manager implements a delta list where each timer is linked in a doubly linked list as shown in the figure below.

Figure - Empty Timer List

(1) OSTmrListEntries contains the current number of entries in the list. This variable is updated whenever timers are added or removed from the list.

(2) OSTmrListPtr contains a pointer to a doubly linked list of timers that the timer manager will need to update.

(3) OSTmrTickCtr is incremented by OS_TmrTask() every time the tick ISR signals the task. This counter basically keeps track of the number of times the timer task has been signaled.


Timers are inserted in the timer list by calling OSTmrStart() and, a timer must be created before it can be used. Timer locations in the list are determined by the order in which they expire, as shown below.

Listing - Creating and Starting a timer
          OS_TMR  MyTmr1;
          OS_TMR  MyTmr2;
           
           
          void MyTmrCallbackFnct1 (void *p_arg)
          {
              /* Do something when timer #1 expires */
          }
           
           
          void MyTmrCallbackFnct2 (void *p_arg)
          {
              /* Do something when timer #2 expires */
          }
           
           
          void MyTask (void *p_arg)
          {
              OS_ERR  err;
           
           
              while (DEF_ON) {
                  :
                  OSTmrCreate((OS_TMR            *)&MyTmr1,
                             (OS_CHAR           *)"My Timer #1",
                             (OS_TICK            )1,
                             (OS_TICK            )0,
                             (OS_OPT             )OS_OPT_TMR_ONE_SHOT,
                             (OS_TMR_CALLBACK_PTR)MyTmrCallbackFnct1,
                             (void              *)0,
                             (OS_ERR            *)&err);
                  /* Check 'err" */
                  OSTmrStart ((OS_TMR *)&MyTmr1,
                             (OS_ERR *)&err);
                  /* Check "err" */
                  // Continues in the next code listing! 


Since this is the first timer inserted in the timer list, the .NextPtr and .PrevPtr both point to NULL.

Figure - Inserting a timer in the timer list


The code below shows creating and starting another timer. This is performed “before” the timer task is signaled.

Listing - Creating and Starting a timer - continued
                  // Continuation of code from previous code listing.
                  :
                  :
                  OSTmrCreate((OS_TMR            *)&MyTmr2,
                             (OS_CHAR           *)"My Timer #2",
                             (OS_TICK            )10,
                             (OS_TICK            )0,
                             (OS_OPT             )OS_OPT_TMR_ONE_SHOT,
                             (OS_TMR_CALLBACK_PTR)MyTmrCallbackFnct2,
                             (void              *)0,
                             (OS_ERR            *)&err);
                  /* Check 'err" */
                  OSTmrStart ((OS_TMR *)&MyTmr,
                             (OS_ERR *)&err);
                  /* Check 'err" */
              }
          }


The “second timer” is inserted at the tail of the list as shown in the figure below, because it expires after the first. The delta property is maintained with respect to .Remain: the remaining time of the a timer is the sum of its .Remain value and that of all of the previous timers.

Figure - Inserting a second timer in the tick list