The whole truth about RTOS. Article # 28. Software timers

Original author: Colin Walls
  • Transfer
The idea of ​​software timers was introduced in a previous article . They are kernel objects that provide tasks with a simple way to trigger events on time, or, most often, a way to perform actions on a regular basis. All details of the time-related functionality (precision, interrupt handling, etc.) in Nucleus SE were discussed in a previous article .




Using timers


Program timers can be configured to trigger once, that is, they start, and then, after a specified period of time, simply end the cycle. Or the timer can be configured to restart: after the count is completed, the timer automatically restarts. The operating time after a restart may differ from the initial operating time. In addition, the timer can optionally be configured to perform a special termination function, which is executed when (or every time) the timer completes the work cycle.

Timer Settings


Number of timers


As with most aspects of Nucleus SE, the timer settings are controlled by the #define directives in nuse_config.h . The main parameter is NUSE_TIMER_NUMBER , which defines the timers configured in the application. By default, this value is zero (that is, timers are not used in the application), and can take values ​​up to 16. An incorrect value will lead to a compilation error, which will be generated by checking in the file nuse_config_check.h (this file is included in nuse_config.c and compiles along with it), which will trigger the #error directive .

Selecting a nonzero value is the main timer activator. This parameter is used when defining data structures, and their size depends on its value. In addition, a nonzero value activates the API settings.

Activation of the completion function


In Nucleus SE, I tried to find the opportunity to make the functionality optional, where it will save memory. A good example is the support for timer completion functions. Besides the fact that this feature is optional for each timer, the mechanism can be activated (or not) for the entire application using the NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT parameter in nuse_config.h . Setting this parameter to FALSE blocks the definition of two data structures in the ROM, which will be described in detail in this article.

API activation


Each API function (utility call) in Nucleus SE has the #define enable directive in nuse_config.h. For timers, these symbols include:
NUSE_TIMER_CONTROL
NUSE_TIMER_GET_REMAINING
NUSE_TIMER_RESET
NUSE_TIMER_INFORMATION
NUSE_TIMER_COUNT


By default, all activators are set to FALSE , so that all service calls are disabled, blocking their inclusion code. To set up timers in the application, you need to select the necessary API service calls and set them to TRUE .

The following is a snippet of code from the default nuse_config.h file .

#define NUSE_TIMER_NUMBER 0/* количество таймеров приложения в системе 0-16 */
                                 /* Активаторы служебного вызова */
   #define NUSE_TIMER_CONTROL          FALSE
   #define NUSE_TIMER_GET_REMAINING    FALSE
   #define NUSE_TIMER_RESET            FALSE
   #define NUSE_TIMER_INFORMATION      FALSE
   #define NUSE_TIMER_COUNT            FALSE

If the timer-related API function is activated and there are no configured timers in the application (except for the NUSE_Timer_Count () function , which is always enabled), a compilation error will occur. If your code uses an API call that has not been activated, a layout error will occur because the implementation code was not included in the application.

Timer Service Calls


Nucleus RTOS supports eight timer-related utility calls that provide the following functionality:

  • Management (start / stop) timer. Nucleus SE is implemented in the NUSE_Timer_Control () function .
  • Retrieving the remaining timer time. In Nucleus SE, implemented in NUSE_Timer_Get_Remaining () .
  • Restoring the timer to its original state (reset). Nucleus SE implemented in NUSE_Timer_Reset () .
  • Providing information about a specific timer. Nucleus SE is implemented in NUSE_Timer_Information () .
  • Returns the number of configured (currently) timers in the application. Nucleus SE is implemented in NUSE_Timer_Count () .
  • Adding a new timer to the application (creation). Nucleus SE is not implemented.
  • Removing a timer from the application. Nucleus SE is not implemented.
  • Returning pointers to all timers in the application. Nucleus SE is not implemented.

The implementation of each service call will be discussed in detail below.

Timer Services


The fundamental operations that can be performed with a timer are control (start and stop) and reading of the current value. Nucleus RTOS and Nucleus SE provide two basic API utility calls for these operations.

Timer control


A utility call to the Nucleus RTOS API to control the timer allows you to activate and deactivate the timer (start and stop). Nucleus SE provides similar functionality.

Call to control the timer in Nucleus RTOS
Service call prototype:

STATUS NU_Control_Timer (NU_TIMER * timer, OPTION enable);

Parameters:
timer - a pointer to a timer control unit provided by the user;
enable is the required function; it can take the values NU_ENABLE_TIMER or NU_DISABLE_TIMER .

Return value:
NU_SUCCESS - the call was completed successfully;
NU_INAVLID_TIMER - invalid timer pointer;
NU_INAVLID_ENABLE - invalid function.

Nucleus SE Timer Control Call
This API call supports the full functionality of the Nucleus RTOS API.

Service call prototype:
STATUS NUSE_Timer_Control (NUSE_TIMER timer, OPTION enable);

Parameters:
timer - index (ID) of the used timer;
enable is the required function; it can take the values NUSE_ENABLE_TIMER or NUSE_DISABLE_TIMER .

Return value:
NUSE_SUCCESS - the call was completed successfully;
NUSE_INCALID_TIMER - invalid timer index;
NUSE_INVALID_ENABLE is an invalid function.

Implementation of timer control in Nucleus SE
The API function code NUSE_Timer_Control () (after checking the parameters) is quite simple:

NUSE_CS_Enter();
if (enable == NUSE_ENABLE_TIMER)
{
   NUSE_Timer_Status[timer] = TRUE;
   if (NUSE_Timer_Expirations_Counter[timer] == 0)
   {
      NUSE_Timer_Value[timer] = NUSE_Timer_Initial_Time[timer];
   }
   else
   {
      NUSE_Timer_Value[timer] = NUSE_Timer_Reschedule_Time[timer];
   }
}
else  /* enable == NUSE_DISABLE_TIMER */
{
   NUSE_Timer_Status[timer] = FALSE;
}
NUSE_CS_Exit();

If the NUSE_DISABLE_TIMER function was specified , the timer status ( NUSE_Timer_Status [] parameter ) is set to FALSE , which ignores the timer by the interrupt handler.

When selecting the NUSE_ENABLE_TIMER function , the timer counter ( NUSE_Timer_Value [] ) is set to NUSE_Timer_initial_Time [] , provided that the timer has never stopped since the last reset. Otherwise, it is assigned the value NUSE_Timer_Reschedule_Time [] . Then the timer status (parameter NUSE_Timer_Status [] ) is set to TRUE, which leads to the fact that the timer is processed by the interrupt handler.

Timer reading


To get the remaining timer time, the Nucleus RTOS API service call returns the number of measures until it expires. Nucleus SE provides similar functionality.

Call to get the remaining time in Nucleus RTOS

Service call prototype:
STATUS NU_Get_Remaining_Time (NU_TIMER * timer, UNSIGNED * remaining_time);

Parameters:
timer - pointer to the timer control block provided by the user;
remaining_time - a pointer to the storage of the remaining time value, which is a variable of type UNSIGNED .

Return value
NU_SUCCESS - the call was completed successfully;
NU_INVALID_TIMER- invalid timer pointer.

Call to get the remaining time in Nucleus SE
This API call supports the full functionality of the Nucleus RTOS API.

Service call prototype:
STATUS NUSE_Timer_Get_Remaining (NUSE_TIMER timer, U16 * remaining_time);

Parameters:
timer - index (ID) of the used timer;
remaining_time - a pointer to the storage of the remaining time value, which is a variable of type U16 .

Return value:
NUSE_SUCCESS - the call was completed successfully;
NUSE_INVALID_TIMER - invalid timer index;
NUSE_INVALID_POINTER - null pointer to the remaining time ( NULL)

Implementing a Timer Read in Nucleus SE
The version of the NUSE_Timer_Get_Remaining () API function code () (after checking the parameters) is trivially simple. The value NUSE_Timer_Value [] is obtained and then returned in the critical section.

Auxiliary Timer Services


Nucleus RTOS has four API calls that provide auxiliary functions related to timers: resetting a timer, getting timer information, getting the number of timers in an application, and getting pointers to all timers in an application. The first three functions are implemented in Nucleus SE.

Timer reset


This API call resets the timer to its original, unused state. The timer can be activated or deactivated after the end of this call. It can only be used after the timer has been disabled (using NUSE_Timer_Control () ). The next time the timer is activated, it will be initialized with the NUSE_Timer_Initial_Time [] parameter . Nucleus RTOS allows you to provide a new initial state and reschedule time, as well as specify the completion function when the timer is reset. In Nucleus SE, these values ​​are set during setup and cannot be changed since they are stored in ROM.

Call to reset the timer in Nucleus RTOS

Service call prototype:
STATUS NU_Reset_Timer (NU_TIMER * timer, VOID (* expiration_routine) (UNSIGNED), UNSIGNED initial_time, UNSIGNED reschedule_time, OPTION enable);

Parameters:
timer - a pointer to a resettable timer;
expiration_routine - indicates the function that will be executed when the loop ends;
initial_time - the initial number of timer ticks until the loop ends;
reschedule_time - the number of timer ticks until the second and subsequent cycles are completed;
enable - the required state of the timer after a reset, can take the values NU_ENABLE_TIMER or NU_DISABLE_TIMER .

Return value:
NU_SUCCESS - the call was completed successfully;
NU_INVALID_TIMER - invalid pointer to the timer control unit;
NU_INVALID_FUNCTION - null pointer to the completion function ( NULL );
NU_INVALID_ENABLE - the specified state is incorrect;
NU_NOT_DISABLED - the timer is already running (it should be stopped before calling this function).

Nucleus SE timer reset
call This API service call supports a simplified version of the core functionality of the Nucleus RTOS API.

Service call prototype:
STATUS NUSE_Timer_Reset (NUSE_TIMER timer, OPTION enable);

Parameters:
timer - index (ID) of the reset timer;
enable- the desired state after a reset, can take the values NUSE_ENABLE_TIMER or NUSE_DISABLE_TIMER .

Return value:
NUSE_SUCCESS - the call was completed successfully;
NUSE_INVALID_TIMER - invalid timer index;
NUSE_INVALID_ENABLE - the specified state is incorrect;
NUSE_NOT_DISABLED - the timer is already running (it should be stopped before calling this function).

Implementing a timer reset in Nucleus SE
The variant of the NUSE_Timer_Reset () API function code (after checking the parameters and current state) is quite simple:

NUSE_CS_Enter();
NUSE_Init_Timer(timer);
if (enable == NUSE_ENABLE_TIMER)
{
   NUSE_Timer_Status[timer] = TRUE;
}
/* иначе enable == NUSE_DISABLE_TIMER а статус остается FALSE */
NUSE_CS_Exit();

A call to NUSE_Init_Timer () initializes the time value and clears the completion counter. After that, if necessary, the value of the required state is checked and whether the timer is on.

Timer Information


This service call allows you to get a set of timer information. The implementation of Nucleus SE differs from Nucleus RTOS in that it returns less information since object naming is not supported.

Call for timer information in Nucleus RTOS

Service call prototype:
STATUS NU_Timer_Information (NU_TIMER * timer, CHAR * name, OPTION * enable, UNSIGNED * expirations, UNSIGNED * id, UNSIGNED * initial_time, UNSIGNED * reschedule_time);

Parameters:
timer - a pointer to a timer about which information is requested;
name - pointer to the 8-character region for the timer name;
enable - pointer to a variable that takes the current state of the timer activator: NU_ENABLE_TIMER orNU_DISABLE_TIMER ;
expirations - a pointer to a variable that takes a counter of the number of completions of the timer cycle since its last reset;
id - pointer to a variable that takes the value of the parameter passed to the timer cycle end function;
initial_time - a pointer to a variable that takes a value into which the timer will be initialized after a reset;
reschedule_time - a pointer to a variable that takes a value into which the timer will be initialized after completion.

Return value:
NU_SUCCESS - the call was completed successfully;
NU_INVALID_TIMER - invalid timer pointer.

Call for timer information in Nucleus SE
This API call supports the core functionality of the Nucleus RTOS API.

Service call prototype:
STATUS NUSE_Timer_Information (NUSE_TIMER timer, OPTION * enable, U8 * expirations, U8 * id, U16 * initial_time, U16 * reschedule_time);

Parameters:
timer - index of the timer about which information is requested;
enable - a pointer to a variable that takes the value TRUE or FALSE , depending on whether the timer is activated or not;
expirations - a pointer to a variable of type U8 that takes the value of the number of timer completions since its last reset;
id- a pointer to a variable of type U8 that takes the value of the parameter passed to the timer completion function (will return an empty value if the completion functions are disabled);
initial_time - a pointer to a variable of type U16 that takes a value by which the timer will be initialized after a reset;
reschedule_time - a pointer to a variable of type U16 , which takes the value by which the timer will be initialized after completion.

Return value:
NUSE_SUCCESS - the call was completed successfully;
NUSE_INVALID_TIMER - invalid timer index;
NUSE_INVALID_POINTER - one or more pointer parameters are incorrect.

Implementing Timer Information on Nucleus SE
Implementing this API call is pretty simple:

NUSE_CS_Enter();
if (NUSE_Timer_Status[timer])
{
   *enable = NUSE_ENABLE_TIMER;
}
else
{
   *enable = NUSE_DISABLE_TIMER;
}
*expirations = NUSE_Timer_Expirations_Counter[timer];
#if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT
   *id = NUSE_Timer_Expiration_Routine_Parameter[timer];
#endif
*initial_time = NUSE_Timer_Initial_Time[timer];
*reschedule_time = NUSE_Timer_Reschedule_Time[timer];
NUSE_CS_Exit();

The function returns the status of the timer. The parameter value of the termination function is returned only if their support has been activated in the application.

Getting the number of timers


This utility call returns the number of timers configured in the application. In Nucleus RTOS, this value may change over time, and the return value will display the current number of timers. In Nucleus SE, the return value is set during the assembly phase and cannot be changed.

Call for a timer counter in Nucleus RTOS

Service call prototype:
UNSIGNED NU_Established_Timers (VOID);

Parameters: none

Returned value: the number of timers created in the system.

Nucleus SE Timer Counter Call
This API call supports the core functionality of the Nucleus RTOS API.

Service call prototype:
U8 NUSE_Timer_Count (void);

Parameters: none

Returned value:
the number of configured timers in the application

Timer Counter Implementation


The implementation of this API call is quite simple: the value of the #define NUSE_TIMER_NUMBER symbol is returned .

Data structures


Timers use five or seven data structures (located in RAM or ROM) which (like other Nucleus SE objects) are a set of tables whose size and number corresponds to the number of configured timers and selected parameters.

I strongly recommend that the application code does not use direct access to these data structures, but refers to them through the provided API functions. This will avoid incompatibility with future versions of Nucleus SE and unwanted side effects, as well as simplify porting applications to Nucleus RTOS. The following is a detailed overview of structures to simplify the understanding of service call and debugging code.

RAM data


This data has the following structure:
NUSE_Timer_Status [] - an array of type U8 that has one record for each configured timer and stores the status of the timer (running or stopped: TRUE or FALSE ).
NUSE_Timer_Value [] is an array of type U16 that has one entry for each configured timer and stores the current value of the timer counter.
NUSE_Timer_Expirations_Counter [] - an array of type U8 , containing a counter of the number of cases when the timers reached the end of the cycle since their last reset.

All these data structures are initialized by the NUSE_Init_Timer () function .when starting Nucleus SE. One of the following articles will contain a complete description of the startup procedures for Nucleus SE.

The following are the definitions of these data structures in the nuse_init.c file :
RAM U8 Timer_Status [NUSE_TIMER_NUMBER];
RAM U16 NUSE_Timer_Value [NUSE_TIMER_NUMBER];
RAM U8 NUSE_Timer_Expirations_Counter [NUSE_TIMER_NUMBER];


ROM data


The structure of this data:
NUSE_Timer_Initial_Time [] - an array of type U16 that has one entry for each configured timer and stores the value of each timer.
NUSE_Timer_Reschedule_Time [] is an array of type U16 that has one entry for each configured timer and stores the value to which the timer will be set after completion. A value of zero indicates that the timer is “one-time” and should not restart automatically.
NUSE_Timer_Expiration_Routine_Address [] - an array of type ADDR containing the address of timer expiration procedures. This array exists only if support for the timer expiration procedure has been activated.
NUSE_Timer_Expiration_Routine_Parameter [] - an array of type U8 containing the values ​​of the parameter that is passed to the timer completion function. This array exists only if support for completion functions has been activated.

These data structures are declared and initialized (statically) in the nuse_config.c file , thus:

ROM U16 NUSE_Timer_Initial_Time[NUSE_TIMER_NUMBER] =
{
   /* исходное время таймера------ */
};
ROM U16 NUSE_Timer_Reschedule_Time[NUSE_TIMER_NUMBER] =
{
   /* время таймеров после первого завершения ------ */
};
#if NUSE_TIMER_EXPIRATION_ROUTINE_SUPPORT || NUSE_INCLUDE_EVERYTHING
   /* прототипы функций завершения */
   ROM ADDR
      NUSE_Timer_Expiration_Routine_Address[NUSE_TIMER_NUMBER] =
   {
      /* адреса функций завершения таймеров ------ */
      /* может быть NULL */
   };
   ROM U8
      NUSE_Timer_Expiration_Routine_Parameter[NUSE_TIMER_NUMBER] =
   {
      /* параметры функции завершения таймера ------ */
   };
#endif

The amount of memory for the timer


Like all other Nucleus SE objects, the amount of data needed for timers is predictable.

The amount of data in RAM (in bytes) for all timers in the application can be calculated as follows:
NUSE_TIMER_NUMBER * 4

The amount of data in ROM (in bytes) for all timers in the application, if support for termination functions is disabled, can be calculated as follows:
NUSE_TIMER_NUMBER * 4

Otherwise, it is:
NUSE_TIMER_NUMBER * (sizeof (ADDR) + 5)

Unrealized API Calls


Nucleus SE does not implement the three API calls that can be found in RTOS.

Timer creation


This API call creates a timer. Nucleus SE does not need it, since timers are created statically.

Service call prototype:
STATUS NU_Create_Timer (NU_TIMER * timer, CHAR * name, VOID (* expiration_routine) (UNSIGNED), UNSIGNED id, UNSIGNED initial_time, UNSIGNED reschedule_time, OPTION enable);

Parameters:
timer - a pointer to a timer control unit provided by the user; it will be used to control timers in other API calls;
name - pointer to the 7-character name of the timer with a terminating zero;
expiration_routine - indicates the function that should be executed after the timer ends;
id - data element of type UNSIGNEDtransmitted to the termination function: this parameter can be used to identify timers with the same termination function;
initial_time - indicates the initial number of timer ticks before the timer ends;
reschedule_time - indicates the number of timer ticks until the second and subsequent cycles are completed; if this parameter is equal to zero, the timer stops only once;
enable - this parameter can take the values NU_ENABLE_TIMER and NU_DISABLE_TIMER ; NU_ENABLE_TIMER activates a timer after it is created; NU_DISABLE_TIMER leaves the timer disabled; timers created with the NU_DISABLE_TIMER parametermust be activated by calling NU_Control_Timer .

Return value:
NU_SUCCESS - the call was completed successfully;
NU_INVALID_TIMER - a null pointer to a timer control unit ( NULL ), or the control unit is already in use;
NU_INVALID_FUNCTION - null pointer to the completion program ( NULL );
NU_INVALID_ENABLE - invalid enable parameter ;
NU_INVALID_OPERATION - the initial_time parameter was zero.

Delete timer


This API call deletes a previously created timer. Nucleus SE does not need it, because timers are created statically and cannot be deleted.

Service call prototype:
STATUS NU_Delete_Timer (NU_TIMER * timer);

Parameters:
timer - pointer to the timer control block.

Return value:
NU_SUCCESS - the call was completed successfully;
NU_INVALID_TIMER - invalid timer pointer;
NU_NOT_DISABLED - The specified timer is not disabled.

Timer Pointers


This API call forms a sequential list of pointers to all timers in the system. Nucleus SE does not need it, since the timers are determined by a simple index, not a pointer.

Service call prototype:
UNSIGNED NU_Timer_Pointers (NU_TIMER ** pointer_list, UNSIGNED maximum_pointers);

Parameters:
pointer_list - pointer to an array of pointers NU_TIMER ; it will be filled with pointers to timers configured in the system;
maximum_pointers - the maximum number of pointers in the array.

Return value: The
number of NU_TIMER pointers placed in the array.

Nucleus RTOS Compatible


As with all other Nucleus SE objects, my goal was to maximize application code compatibility with Nucleus RTOS. Timers are no exception and, from the user's point of view, they are implemented in the same way as in Nucleus RTOS. There is also a certain incompatibility, which I considered acceptable, given that as a result, the code will become more understandable and more efficient in terms of the amount of memory required. Otherwise, Nucleus RTOS API calls can be ported almost directly to Nucleus SE.

Object Identifiers


In Nucleus RTOS, all objects are described by a data structure — a control block that has a specific data type. A pointer to this control unit is a timer identifier. I decided that in Nucleus SE, a different approach is needed for efficient memory usage: all kernel objects are described by a set of tables in RAM and / or ROM. The size of these tables is determined by the number of configured objects of each type. The identifier of a particular object is the index in this table. So I defined NUSE_TIMER as the equivalent of U8, a variable (not a pointer) of this type serves as the identifier of the timer. This slight incompatibility is easy to handle if the code is ported from Nucleus SE to Nucleus RTOS and vice versa. Typically, no operations are performed on object identifiers other than moving and storing.
Nucleus RTOS also supports naming timers. These names are used only for debugging. I excluded them from Nucleus SE to save memory.

Timer size


In Nucleus RTOS, timers are implemented using 32-bit counters. I decided to reduce this value to 16 bits in Nucleus SE. This has led to significant improvements in memory efficiency and runtime. Nucleus SE can be modified if the application requires a longer run time.

Completion functions


Nucleus SE implements termination functions in a manner similar to Nucleus RTOS, only they can be turned off completely (which saves memory), and they are also statically determined. The end function cannot be changed when the timer is reset.

Unrealized API Calls


Nucleus RTOS supports eight timer service calls. Of these, three are not implemented in Nucleus SE. A detailed description of these calls, as well as the reasons for this decision, can be found earlier in this article, in the "Unrealized API Calls" section.

The following article will examine interruptions.

Also popular now: