目录标题
1、软件定时器特性
使用定时器需要配置定时器周期、回调函数、类型(一次性、自动加载)。
定时器有两种状态:运行和冬眠。
2、守护任务
软件定时器的守护任务是一个负责管理和处理软件定时器的后台任务。守护任务通常在操作系统的调度器中运行,并负责以下几个主要功能:
- 创建和删除定时器:守护任务可以接收来自其他任务或用户的请求,根据这些请求来创建新的定时器或删除现有的定时器。
- 定时器回调函数的调用:守护任务会根据每个定时器的设定时间,调用相应的定时器回调函数。定时器回调函数是在定时器到期时自动触发的代码段,用于执行相应的任务或操作。
- 定时器的周期性调度:守护任务负责根据定时器的设定时间和要求,按时执行定时器回调函数。它会根据定时器的周期或延时来判断何时触发定时器。
- 定时器的暂停和恢复:守护任务可以控制定时器的暂停和恢复,以实现对定时器的动态管理。这样可以根据需要暂停定时器的触发,然后在适当的时候恢复它们。
软件定时器和守护任务之间使用队列进行通信。队列的使用可以使定时器和守护任务之间实现解耦,提高系统的可扩展性和灵活性。守护任务可以根据队列中的消息进行动态调度和管理定时器,而定时器任务只需负责按时触发定时器和发送相应的消息到队列,而无需关心具体的处理逻辑。
3、软件定时器API
3.1、创建软件定时器
创建软件定时器有两种方法:动态分配内存和静态分配内存。
//动态分配内存:
TimerHandle_t xTimerCreate(
const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
//静态分配内存:
TimerHandle_t xTimerCreateStatic(
const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer );
参数 | 描述 |
---|---|
pcTimerName | 定时器名称,用于调试和日志记录。 |
xTimerPeriodInTicks | 超时事件,定时器的周期,以时钟节拍(Tick)数为单位 |
uxAutoReload | 模式,pdTRUE:自动加载 pdFALSE:一次性 |
pvTimerID | 回调函数可以使用此参数判断是哪个定时器超时了 |
pxCallbackFunction | 回调函数 |
pxTimerBuffer | 执行StaticTimer_t结构体,用于存储定时器结构体 |
返回值 | 一个TimerHandle_t类型值,表示成功创建的定时器的句柄。 |
注意:在自己移植的库函数版本中使用创建软件定时器函数时,需要在FreeRTOSConfig.h中添加如下几个宏定义,
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( 2 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 256
否则会报如下错误:
.\Objects\2.axf: Error: L6218E: Undefined symbol xTimerCreate (referred from main.o).
Not enough information to list image symbols.
3.2、回调函数
在创建软件定时器时,需要指定回调函数,在定时器到期时,FreeRTOS自动调用该函数。
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );
void ATimerCallback( TimerHandle_t xTimer );
参数 | 描述 |
---|---|
xTimer | 定时器句柄 |
需要注意:回调函数要尽快实行,不能进入阻塞状态
示例:
void vTimerCallback(TimerHandle_t pxTimer) {
if (pxTimer == xTimer1) {
// Timer1 到期的处理逻辑
} else if (pxTimer == xTimer2) {
// Timer2 到期的处理逻辑
} else {
// 其他定时器到期的处理逻辑
}
}
TimerHandle_t xTimer1, xTimer2;
// 创建 Timer1,指定回调函数为 vTimerCallback
xTimer1 = xTimerCreate("Timer1", pdMS_TO_TICKS(1000), pdTRUE, 0, vTimerCallback);
// 创建 Timer2,指定回调函数为 vTimerCallback
xTimer2 = xTimerCreate("Timer2", pdMS_TO_TICKS(2000), pdTRUE, 0, vTimerCallback);
3.3、删除定时器
动态分配内存的定时器,当我们不需要使用时可以将其删除以回收内存。
BaseType_t xTimerDelete(
TimerHandle_t xTimer,
TickType_t xTicksToWait );
参数 | 描述 |
---|---|
xTimer | 定时器句柄 |
xTicksToWait | 超时时间 |
返回值 | pdPASS:成功 pdFAIL:与守护任务通信的队列满了,无法写入 |
3.4、启动/停止定时器
- 启动定时器
/*任务中使用*/
BaseType_t xTimerStart(
TimerHandle_t xTimer,
TickType_t xTicksToWait );
/*ISR中使用*/
BaseType_t xTimerStartFromISR(
TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
参数 | 描述 |
---|---|
xTimer | 软件定时器句柄 |
xTicksToWait | 阻塞等待超时时间 |
pxHigherPriorityTaskWoken | 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,则"*pxHigherPriorityTaskWoken = pdTRUE",表示需要进行任务调度 |
返回值 | pdPASS:成功 pdFAIL:失败(无法在指定时间内写入守护任务队列) |
- 停止定时器
/*任务中使用*/
BaseType_t xTimerStop(
TimerHandle_t xTimer,
TickType_t xTicksToWait );
/*ISR中使用*/
BaseType_t xTimerStopFromISR(
TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
参数 | 描述 |
---|---|
xTimer | 软件定时器句柄 |
xTicksToWait | 阻塞等待超时时间 |
pxHigherPriorityTaskWoken | 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,则"*pxHigherPriorityTaskWoken = pdTRUE",表示需要进行任务调度 |
返回值 | pdPASS:成功 pdFAIL:失败(无法在指定时间内将停止命令写入守护任务队列) |
3.5、修改定时器周期
若被修改的定时器此时处于休眠状态,使用此函数会将其唤醒。并且使用xTimerChangePeriod() 函数会使定时器的超时时间重新累计,从此函数被调用开始。
/*任务中使用*/
BaseType_t xTimerChangePeriod(
TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
/*ISR中使用*/'
BaseType_t xTimerChangePeriodFromISR(
TimerHandle_t xTimer,
TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken );
参数 | 描述 |
---|---|
xTimer | 软件定时器句柄 |
xNewPeriod | 新的周期 |
xTicksToWait | 命令写入队列的阻塞等待超时时间 |
pxHigherPriorityTaskWoken | 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,则"*pxHigherPriorityTaskWoken = pdTRUE",表示需要进行任务调度 |
返回值 | pdPASS:成功 pdFAIL:失败(无法在指定时间内将复位命令写入守护任务队列) |
3.6、复位定时器(Reset)
- 使用 xTimerReset() 函数可以让定时器的状态从冬眠态转换为运行态,相当于使用 xTimerStart() 函数。
- 使用xTimerReset() 函数会使定时器的超时时间重新累计,从此函数被调用开始。
/*任务中使用*/
BaseType_t xTimerReset(
TimerHandle_t xTimer,
TickType_t xTicksToWait );
/*ISR中使用*/
BaseType_t xTimerResetFromISR(
TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
参数 | 描述 |
---|---|
xTimer | 软件定时器句柄 |
xTicksToWait | 命令写入队列的阻塞等待超时时间 |
pxHigherPriorityTaskWoken | 向队列发出命令使得守护任务被唤醒,如果守护任务的优先级比当前任务的高,则"*pxHigherPriorityTaskWoken = pdTRUE",表示需要进行任务调度 |
返回值 | pdPASS:成功 pdFAIL:失败(无法在指定时间内将复位命令写入守护任务队列) |
3.7、定时器ID
- 获得定时器ID
void *pvTimerGetTimerID(
TimerHandle_t xTimer );
参数 | 描述 |
---|---|
xTimer | 软件定时器句柄 |
返回值 | 一个 void* 类型的指针,指向定时器的标识符 |
- 更新定时器ID
void vTimerSetTimerID(
TimerHandle_t xTimer,
void *pvNewID );
参数 | 描述 |
---|---|
xTimer | 软件定时器句柄 |
pvNewID | 新的定时器ID |
返回值 | 无 |
举例:
#define TIMER_PERIOD_MS (1000) // 定时器周期
#define SWITCH_COUNT (5) // 切换标识符的发送次数
typedef struct {
uint32_t send_count; // 发送次数
bool is_first_callback; // 是否为第一次回调
} timer_data_t;
// 定时器回调函数
void timer_callback(TimerHandle_t xTimer){
timer_data_t *pData = (timer_data_t*)pvTimerGetTimerID(xTimer); // 获取标识符
// 发送数据
send_data();
// 更新发送次数并判断是否需要切换标识符
pData->send_count++;
if(pData->send_count == SWITCH_COUNT){
void *pNewID;
if(pData->is_first_callback){
pNewID = NULL; // 第一次回调不带标识符
}else{
pNewID = &pData->send_count; // 第二次回调带标识符
}
vTimerSetTimerID(xTimer, pNewID); // 切换标识符
pData->is_first_callback = false;
}
}
void main(){
TimerHandle_t xTimer;
timer_data_t timer_data = {0};
void *pInitialID = &timer_data.send_count;
xTimer = xTimerCreate("timer", TIMER_PERIOD_MS / portTICK_PERIOD_MS,
pdTRUE, pInitialID, timer_callback); // 创建定时器
xTimerStart(xTimer, 0); // 启动定时器
while(1){
// 其他任务处理
}
}