Clock Management
Time is a very important concept. Going out with friends requires an appointment, and completing tasks also requires time. Life is inseparable from time. The same is true for operating systems, which need to use time to regulate the execution of their tasks. The smallest unit of time in an operating system is the clock tick (OS Tick). This chapter mainly introduces the clock tick and the timer based on the clock tick. After reading this chapter, we will understand how the clock tick is generated and how to use the timer of RT-Thread.
Any operating system needs to provide a clock tick for the system to handle all time-related events, such as thread delays, thread time slice round-robin scheduling, and timer timeouts. The clock tick is a specific periodic interrupt, which can be regarded as the heartbeat of the system. The time interval between interrupts depends on different applications, usually 1ms-100ms. The faster the clock tick rate, the faster the real-time response of the system, but the greater the system overhead. The number of clock ticks counted from the start of the system is called the system time.
In RT-Thread, the length of the clock tick can be adjusted according to the definition of RT_TICK_PER_SECOND, which is equal to 1/RT_TICK_PER_SECOND seconds.
The clock beat is generated by a hardware timer configured in interrupt trigger mode. When an interrupt arrives, it will call once: void rt_tick_increase(void) to notify the operating system that a system clock has passed. Different hardware timer interrupt implementations are different. The following interrupt function takes the STM32 timer as an example.
Call rt_tick_increase() in the interrupt function to increment the global variable rt_tick. The code is as follows:
It can be seen that the value of the global variable rt_tick increases by 1 every time a clock tick passes. The value of rt_tick represents the total number of clock ticks that have passed since the system was started, that is, the system time. In addition, every time a clock tick passes, it checks whether the time slice of the current thread has been used up and whether any timer has timed out.
Note
Note: rt_timer_check() in the interrupt is used to check the system hardware timer list. If a timer times out, the corresponding timeout function will be called. All timers will be removed from the timer list after the timer times out, and the periodic timer will be added to the timer list when it is started again.
Since the value of the global variable rt_tick increases by 1 every time a clock tick passes, calling rt_tick_get will return the current value of rt_tick, that is, the current clock tick value can be obtained. This interface can be used to record the running time of the system or measure the running time of a task. The interface function is as follows:
The following table describes the return values of the rt_tick_get() function:
return
describe
rt_tick
Current clock tick value
A timer is a device that triggers an event after a specified period of time, such as setting a time to remind you to get up on time the next day. There are two types of timers: hardware timers and software timers:
1) The hardware timer is a timing function provided by the chip itself. Generally, an external crystal oscillator provides the chip with input clock. The chip provides a set of configuration registers to the software module, accepts control input, and the chip interrupt controller generates a clock interrupt after reaching the set time value. The accuracy of the hardware timer is generally very high, reaching the nanosecond level, and it is an interrupt trigger mode.
2) Software timer is a type of system interface provided by the operating system. It is built on the basis of hardware timer, enabling the system to provide unlimited number of timer services.
The RT-Thread operating system provides a software-implemented timer that uses the length of the clock tick (OS Tick) as the unit, that is, the timing value must be an integer multiple of the OS Tick. For example, if an OS Tick is 10ms, then the upper-level software timer can only be 10ms, 20ms, 100ms, etc., and cannot be timed to 15ms. The RT-Thread timer is also based on the system tick, providing a timing capability based on integer multiples of the tick.
RT-Thread's timer provides two types of timer mechanisms: the first type is a single-trigger timer, which will only trigger a timer event once after it is started, and then the timer will stop automatically. The second type is a periodic trigger timer, which will periodically trigger a timer event until the user manually stops it, otherwise it will continue to execute forever.
In addition, according to the context in which the timeout function is executed, the RT-Thread timer can be divided into HARD_TIMER mode and SOFT_TIMER mode, as shown in the following figure.
The timer timeout function in HARD_TIMER mode is executed in the interrupt context and can be specified using the parameter RT_TIMER_FLAG_HARD_TIMER when initializing/creating the timer.
When executed in the interrupt context, the requirements for the timeout function are the same as those for the interrupt service routine: the execution time should be as short as possible, and the execution should not cause the current context to suspend or wait. For example, a timeout function executed in the interrupt context should not attempt to apply for dynamic memory or release dynamic memory.
The default mode of RT-Thread timer is HARD_TIMER mode, that is, after the timer times out, the timeout function is executed in the context of the system clock interrupt. The execution mode in the interrupt context determines that the timer timeout function should not call any system function that will suspend the current context; it cannot be executed for a very long time, otherwise it will cause the response time of other interrupts to be prolonged or preempt the execution time of other threads.
The SOFT_TIMER mode is configurable. The macro definition RT_USING_TIMER_SOFT determines whether to enable this mode. When this mode is enabled, the system will create a timer thread during initialization, and then the timer timeout function of the SOFT_TIMER mode will be executed in the context of the timer thread. The parameter RT_TIMER_FLAG_SOFT_TIMER can be used to specify the setting of SOFT_TIMER mode when initializing/creating the timer.
The following example illustrates the working mechanism of the RT-Thread timer. Two important global variables are maintained in the RT-Thread timer module:
(1) The current system tick time rt_tick (when the hardware timer interrupt comes, it will be increased by 1);
(2) Timer linked list rt_timer_list. Newly created and activated timers in the system are inserted into the rt_timer_list linked list in the order of timeout time.
As shown in the figure below, the current tick value of the system is 20. Three timers have been created and started in the current system, namely Timer1 with a timing time of 50 ticks, Timer2 with a timing time of 100 ticks, and Timer3 with a timing time of 500 ticks. These three timers are added with the current system time rt_tick=20, and are linked in the rt_timer_list linked list in ascending order, forming the timer linked list structure shown in the figure.
As the hardware timer is triggered, rt_tick keeps increasing (each time the hardware timer interrupt comes, the rt_tick variable will increase by 1). After 50 ticks, rt_tick increases from 20 to 70, which is equal to the timeout value of Timer1. At this time, the timeout function associated with Timer1 timer will be triggered, and Timer1 will be deleted from the rt_timer_list linked list. Similarly, after 100 ticks and 500 ticks have passed, the timeout function associated with Timer2 and Timer3 timers will be triggered, and then Timer2 and Timer3 timers will be deleted from the rt_timer_list linked list.
If the current timer state of the system is after 10 ticks (rt_tick=30), a task creates a new Timer4 timer with a tick value of 300. Since the timeout of Timer4 timer=rt_tick+300=330, it will be inserted between Timer2 and Timer3 timers, forming a linked list structure as shown in the following figure:
In the RT-Thread operating system, the timer control block is defined by the structure struct rt_timer and forms a timer kernel object, which is then linked to the kernel object container for management. It is a data structure used by the operating system to manage timers, which stores some information about the timer, such as the initial beat number, the beat number when the timeout occurs, and also includes the linked list structure used to connect timers, timeout callback functions, etc.
The timer control block is defined by the struct rt_timer structure and forms a timer kernel object, which is then linked to the kernel object container for management. The list member is used to link an activated (already started) timer to the rt_timer_list linked list.
When introducing the working method of timers, we mentioned that newly created and activated timers in the system will be inserted into the rt_timer_list linked list in the order of timeout time. That is to say, the t_timer_list linked list is an ordered linked list. The skip list algorithm is used in RT-Thread to speed up the search of linked list elements.
A skip list is a data structure based on a parallel linked list. It is simple to implement, and the time complexity of insertion, deletion, and search is O(log n). A skip list is a type of linked list, but it adds a "jump" function on the basis of the linked list. It is this function that enables the skip list to provide a time complexity of O(log n) when searching for elements. For example:
An ordered linked list, as shown in the figure below, searches for the element {13, 39} from the ordered linked list. The number of comparisons required is {3, 5}, and the total number of comparisons is 3 + 5 = 8.
After using the skip list algorithm, you can use a method similar to the binary search tree to extract some nodes as indexes, and get the structure shown in the following figure:
In this structure, {3, 18, 77} is extracted as the primary index, so that the number of comparisons can be reduced when searching. For example, when searching for 39, only 3 comparisons are made (by comparing 3, 18, 39). Of course, we can also extract some elements from the primary index as the secondary index, which can speed up the element search.
Therefore, the timer skip list can reduce the number of comparisons during the search through the upper-level index, thereby improving the efficiency of the search. This is an algorithm that "trades space for time". In RT-Thread, the number of levels of the skip list is configured through the macro definition RT_TIMER_SKIP_LIST_LEVEL. The default value is 1, which means that an ordered linked list algorithm with a first-level ordered linked list graph is used. Each increase by one means that a level of index is added on the basis of the original linked list.
The previous section introduced the RT-Thread timer and gave a conceptual explanation of the working mechanism of the timer. This section will go into the various interfaces of the timer to help readers understand the RT-Thread timer at the code level.
The timer management system needs to be initialized when the system starts. This can be done through the following function interface:
If you need to use SOFT_TIMER, you should call the following function interface when the system is initialized:
The timer control block contains important parameters related to the timer, and acts as a link between the various states of the timer. The relevant operations of the timer are shown in the figure below. The operations on the timer include: creating/initializing the timer, starting the timer, running the timer, and deleting/leaving the timer. All timers will be removed from the timer list after the timer times out, and the periodic timer will be added to the timer list when it is started again, which is related to the timer parameter setting. Every time the operating system clock interrupt occurs, the state parameters of the timer that has timed out will be changed.
When dynamically creating a timer, you can use the following function interface:
After calling this function interface, the kernel first allocates a timer control block from the dynamic memory heap, and then performs basic initialization on the control block. The parameters and return values are described in the following table:
Input parameters and return values of rt_timer_create()
parameter
describe
name
Timer name
void (timeout) (void parameter)
Timer timeout function pointer (when the timer times out, the system will call this function)
parameter
The entry parameter of the timer timeout function (when the timer times out, the timeout callback function is called and this parameter is passed to the timeout function as the entry parameter)
time
The timer timeout period, in clock ticks
flag
Parameters when creating a timer. Supported values include single timer, periodic timer, hardware timer, software timer, etc. (You can use the "or" relationship to take multiple values)
return
——
RT_NULL
Creation failed (usually returns RT_NULL due to insufficient system memory)
The timer handle
Timer created successfully
Some timer-related macros are defined in include/rtdef.h, as follows:
The above two sets of values can be assigned to flags in an "OR" logic manner. When the specified flag is RT_TIMER_FLAG_HARD_TIMER, if the timer times out, the timer callback function will be called in the context of the clock interrupt service routine; when the specified flag is RT_TIMER_FLAG_SOFT_TIMER, if the timer times out, the timer callback function will be called in the context of the system clock timer thread.
When the system no longer uses dynamic timers, the following function interface can be used:
After calling this function interface, the system will delete this timer from the rt_timer_list linked list, and then release the memory occupied by the corresponding timer control block. The parameters and return values are described in the following table:
Input parameters and return value of rt_timer_delete()
parameter
describe
timer
Timer handle, pointing to the timer to be deleted
return
——
RT_EOK
Deletion is successful (if the parameter timer handle is a RT_NULL, it will cause an ASSERT assertion)
When you choose to create a timer statically, you can use the rt_timer_init interface to initialize the timer. The function interface is as follows:
When using this function interface, the corresponding timer control block will be initialized, the corresponding timer name, timer timeout function, etc. The parameters and return values are described in the following table:
Input parameters and return values of rt_timer_init()
parameter
describe
timer
Timer handle, pointing to the timer control block to be initialized
name
Timer name
void (timeout) (void parameter)
Timer timeout function pointer (when the timer times out, the system will call this function)
parameter
The entry parameter of the timer timeout function (when the timer times out, the timeout callback function is called and this parameter is passed to the timeout function as the entry parameter)
time
The timer timeout period, in clock ticks
flag
Parameters for creating a timer. Supported values include single timer, periodic timer, hardware timer, and software timer (you can use the "or" relationship to take multiple values). For details, see the section Creating a Timer.
When a static timer is no longer needed, the following function interface can be used:
When detaching from a timer, the system will detach the timer object from the kernel object container, but the memory occupied by the timer object will not be released. For details on the parameters and return values, see the table below:
rt_timer_detach() Input Parameters and Return Values
parameter
describe
timer
Timer handle, pointing to the timer control block to be detached
return
——
RT_EOK
Breakaway success
Starting and stopping the timer
After a timer is created or initialized, it will not be started immediately. It must start working after calling the start timer function interface. The start timer function interface is as follows:
After calling the timer start function interface, the timer status will be changed to the activated status (RT_TIMER_FLAG_ACTIVATED) and inserted into the rt_timer_list queue list in the order of timeout. The parameters and return values are described in the following table:
Input parameters and return value of rt_timer_start()
parameter
describe
timer
Timer handle, pointing to the timer control block to be started
return
——
RT_EOK
Startup Success
For an example of starting a timer, please refer to the sample code below.
After starting the timer, if you want to stop it, you can use the following function interface:
After calling the timer stop function interface, the timer state will change to the stop state, and it will be separated from the rt_timer_list list and will not participate in the timer timeout check. When a (periodic) timer times out, this function interface can also be called to stop the (periodic) timer itself. The parameters and return values are described in the following table:
Input parameters and return value of rt_timer_stop()
parameter
describe
timer
Timer handle, pointing to the timer control block to be stopped
return
——
RT_EOK
Successfully stopped the timer
- RT_ERROR
The timer is already in the stopped state
In addition to the programming interfaces provided above, RT-Thread also provides additional timer control function interfaces to obtain or set more timer information. The control timer function interface is as follows:
The control timer function interface can view or change the timer settings according to the command type parameters. The parameters and return values are described in the following table:
rt_timer_control() Input Parameters and Return Values
parameter
describe
timer
Timer handle, pointing to the timer control block to be controlled
cmd
Commands used to control the timer. Currently, four commands are supported: set timing time, view timing time, set single trigger, and set periodic trigger.
arg
The control command parameters corresponding to cmd. For example, when cmd is used to set the timeout, the timeout parameter can be set through arg.
return
——
RT_EOK
success
Function parameter cmd supports the following commands:
For code using the timer control interface, see the dynamic timer example.
This is an example of creating a timer. This routine will create two dynamic timers, one is a one-time timer and the other is a periodic timer and let the periodic timer run for a period of time and then stop running, as shown below:
The simulation results are as follows:
The timeout function of periodic timer 1 runs once every 10 OS ticks, for a total of 10 times (call rt_timer_stop after 10 times to stop timer 1); the timeout function of one-shot timer 2 runs once at the 30th OS tick.
The example of initializing a timer is similar to the example of creating a timer. This program will initialize two static timers, one for a single timer and one for a periodic timer, as shown in the following code:
Initialize static timer routine
The simulation results are as follows:
The timeout function of periodic timer 1 runs once every 10 OS ticks, for a total of 10 times (call rt_timer_stop after 10 times to stop timer 1); the timeout function of one-shot timer 2 runs once at the 30th OS tick.
The minimum precision of the RT-Thread timer is determined by the system clock tick (1 OS Tick = 1/RT_TICK_PER_SECOND seconds, RT_TICK_PER_SECOND value is defined in the rtconfig.h file), and the time set by the timer must be an integer multiple of the OS Tick. When a shorter system timing is required, for example, the OS Tick is 10ms, and the program needs to implement 1ms timing or delay, the operating system timer will not be able to meet the requirements, and can only be read by reading the counter of a hardware timer in the system or directly using the hardware timer.
In the Cortex-M series, SysTick has been used by RT-Thread as OS Tick. It is configured to trigger an interrupt after 1/RT_TICK_PER_SECOND seconds. The interrupt handler uses the default SysTick_Handler name of Cortex-M3. The CMSIS (Cortex Microcontroller Software Interface Standard) specification of Cortex-M3 stipulates that SystemCoreClock represents the main frequency of the chip, so based on SysTick and SystemCoreClock, we can use SysTick to obtain an accurate delay function, as shown in the following example, the precise delay based on SysTick on Cortex-M3 (needs to be used after the system enables SysTick):
The high-precision delay routine is as follows:
The entry parameter us indicates the number of microseconds required for delay. This function can only support delays less than 1 OS Tick.
Note: This interface should be implemented by the BSP maker according to the specific chip characteristics. The above code is for reference only!
Last updated