Source: https://blog.csdn.net/qq_26904271/article/
1. What is a Software Timer
A software timer is a timer simulated by a program, which can be simulated by a hardware timer to create thousands of software timers. This allows the program to use a large number of timers without being limited by insufficient hardware resources, which is one of the advantages of software timers: their quantity is not limited.
However, since software timers are implemented through programs, their operation and maintenance require a certain amount of CPU resources, and their precision is relatively inferior to that of hardware timers.
2. Principle of Software Timer Implementation
Operating systems such as Linux, uC/OS, and FreeRTOS all come with software timers, and the principles are similar. A typical implementation method is to generate a fixed clock tick using a hardware timer. Each time the hardware timer interrupts, a global time marker is incremented, and each software timer keeps track of its expiration time.
The program needs to periodically scan all running software timers, comparing their expiration times with the global clock marker to determine if the corresponding software timer has expired. If it has expired, the corresponding callback function is executed, and the timer is closed.
The above describes the implementation of a one-shot timer. To implement a periodic timer, which resets after expiration, simply retrieve the current time marker value after executing the callback function, add the delay time as the next expiration time, and continue running the software timer.
3. Software Timer Based on STM32
3.1 Clock Tick
A software timer requires a hardware clock source as a reference. This clock source has a fixed tick (which can be understood as the tick of a second hand), and a 32-bit global variable tickCnt
is used to record the changes in this tick:
static volatile uint32_t tickCnt = 0; // Software timer clock tick
Each tick increments tickCnt
(recording how many ticks have occurred):
/* Must be executed in the timer interrupt */
void tickCnt_Update(void)
{
tickCnt++;
}
Once it starts running, tickCnt
will keep incrementing, and each software timer records an expiration time. As long as tickCnt
is greater than the expiration time, it indicates that the timer has expired.
3.2 Data Structure
The data structure of the software timer determines its execution performance and functionality. It can generally be divided into two types: array structure and linked list structure. What does this mean? It refers to how multiple software timers are stored in memory; they can be stored in an array or in a linked list.
The advantages and disadvantages of both are based on the characteristics of these two data structures: the array method allows for faster timer lookups, but the number is fixed and cannot change dynamically. A large array can waste memory, while a small array may not be sufficient, making it suitable for systems with clearly defined and fixed timing events; the linked list method allows for dynamic addition and removal of timers, but can lead to memory fragmentation (if there is no memory management), and the lookup time overhead is relatively larger than that of arrays, making it suitable for more general-purpose systems. Operating systems like Linux, uC/OS, and FreeRTOS use linked list-based software timers.
This article uses an array structure:
static softTimer timer[TIMER_NUM]; // Software timer array
The array and linked list are the overall data structures of the software timer. When it comes to a single timer, it involves the definition of the software timer structure, which is closely related to the functionalities of the software timer. Below is the structure definition of the software timer used in this article:
typedef struct softTimer {
uint8_t state; // State
uint8_t mode; // Mode
uint32_t match; // Expiration time
uint32_t period; // Timing period
callback *cb; // Callback function pointer
void *argv; // Argument pointer
uint16_t argc; // Number of arguments
}softTimer;
The timer has three states: stopped by default, running after starting, and timed out after expiration.
typedef enum tmrState {
SOFT_TIMER_STOPPED = 0, // Stopped
SOFT_TIMER_RUNNING, // Running
SOFT_TIMER_TIMEOUT // Timed out
}tmrState;
There are two modes: one-shot mode, which stops after expiration, and periodic mode, which resets after expiration.
typedef enum tmrMode {
MODE_ONE_SHOT = 0, // One-shot mode
MODE_PERIODIC, // Periodic mode
}tmrMode;
Regardless of the mode, after the timer expires, the callback function will be executed. Below is the definition of this function, where the argument pointer argv
is of type void
pointer to facilitate passing different types of parameters.
typedef void callback(void *argv, uint16_t argc);
The state mode and callback function pointer cb
in the above structure are optional features. If the system does not require a periodically executing timer or does not need to automatically execute a function after expiration, these two definitions can be removed.
3.3 Timer Operations
3.3.1 Initialization
First, the software timer must be initialized by assigning initial values to each member of the timer structure. Although static variables are initialized to 0, I believe it is still necessary to maintain the habit of initializing variables to avoid strange bugs.
void softTimer_Init(void)
{
uint16_t i;
for(i=0; i<timer_num; code="" i++)="" timer[i].argc="0;" timer[i].argv="NULL;" timer[i].cb="NULL;" timer[i].match="0;" timer[i].mode="MODE_ONE_SHOT;" timer[i].period="0;" timer[i].state="SOFT_TIMER_STOPPED;" {="" }="" }<=""></timer_num;>
3.3.2 Start
To start a software timer, not only must its state be changed to running, but it must also specify when the timer will expire (the current tickCnt
value plus the delay time equals the expiration time), whether it is a one-shot or periodic timer, and which function to execute upon expiration, along with its parameters. Once these are specified, the timer can start running.
void softTimer_Start(uint16_t id, tmrMode mode, uint32_t delay, callback *cb, void *argv, uint16_t argc)
{
assert_param(id < TIMER_NUM);
assert_param(mode == MODE_ONE_SHOT || mode == MODE_PERIODIC);
timer[id].match = tickCnt_Get() + delay;
timer[id].period = delay;
timer[id].state = SOFT_TIMER_RUNNING;
timer[id].mode = mode;
timer[id].cb = cb;
timer[id].argv = argv;
timer[id].argc = argc;
}
The assert_param()
function in the above code is used for parameter checking, similar to the library function assert()
.
3.3.3 Update
This software timer has three states: stopped, running, and timed out, and different states perform different actions. The stopped state is the simplest, doing nothing; the running state needs to continuously check if it has expired, executing the callback function and entering the timed out state if it has; the timed out state checks the timer’s mode. If it is periodic mode, it updates the expiration time and continues running; if it is one-shot mode, it stops the timer. These operations are implemented by an update function:
void softTimer_Update(void)
{
uint16_t i;
for(i=0; i<timer_num; (timer[i].state)="" +="" <="tickCnt_Get())" break;="" callback="" case="" code="" default:="" else="" error!\r\n",="" execute="" function="" i);="" i++)="" if(timer[i].match="" if(timer[i].mode="MODE_ONE_SHOT)" printf("timer[%d]="" soft_timer_running:="" soft_timer_stopped:="" soft_timer_timeout:="" state="" switch="" timer[i].argc);="" timer[i].cb(timer[i].argv,="" timer[i].match="tickCnt_Get()" timer[i].period;="" timer[i].state="SOFT_TIMER_RUNNING;" {="" }="" }<=""></timer_num;>
3.3.4 Stop
If a timer is running and you want to stop it, a stop function is needed. The operation is simple: just change the target timer’s state to stopped:
void softTimer_Stop(uint16_t id)
{
assert_param(id < TIMER_NUM);
timer[id].state = SOFT_TIMER_STOPPED;
}
3.3.5 Read State
If you want to know whether a timer is running or has stopped, it is also simple; just return its state:
uint8_t softTimer_GetState(uint16_t id)
{
return timer[id].state;
}
This may seem strange; why return the state instead of reading it directly? Remember that the timer array defined in section 3.2 is a static global variable, which can only be accessed by the current source file. When external files need to access it, they can only do so through function returns, which is a simple encapsulation to maintain the modularity of the program.
3.4 Testing
Finally, we need to verify whether our software timer achieves the expected functionality.
Define three timers:
The timer TMR_STRING_PRINT
executes only once, printing a string on Serial Port 1 after 1 second;
The timer TMR_TWINKLING
is a periodic timer with a period of 0.5 seconds, toggling the state of LED0 each time it expires, achieving LED0 blinking;
The timer TMR_DELAY_ON
executes once, turning on LED1 after 3 seconds. Unlike the first timer, this timer’s callback function is a no-operation function nop()
, and the operation of turning on LED1 is implemented by checking the timer’s state in the main loop, which may be useful in certain scenarios.
static uint8_t data[] = {1,2,3,4,5,6,7,8,9,0};
int main(void)
{
USART1_Init(115200);
TIM4_Init(TIME_BASE_MS);
TIM4_NVIC_Config();
LED_Init();
printf("I just grabbed a spoon.\r\n");
softTimer_Start(TMR_STRING_PRINT, MODE_ONE_SHOT, 1000, stringPrint, data, 5);
softTimer_Start(TMR_TWINKLING, MODE_PERIODIC, 500, LED0_Twinkling, NULL, 0);
softTimer_Start(TMR_DELAY_ON, MODE_ONE_SHOT, 3000, nop, NULL, 0);
while(1) {
softTimer_Update();
if(softTimer_GetState(TMR_DELAY_ON) == SOFT_TIMER_TIMEOUT) {
LED1_On();
}
}
}
This article is sourced from the internet, freely conveying knowledge, and the copyright belongs to the original author. If there are any copyright issues, please contact me for deletion.
Note
Due to recent changes in the WeChat public account’s push rules, to prevent missing articles, you can star and pin this account so that the articles pushed will appear in your subscription list.
You might also like:
How to Respond to HR Questions in Embedded Engineer Interviews?
Hello Series | Brief Basics of CMake
Reply with 1024 in the WeChat chat interface to obtain embedded resources; reply with m to view the article summary