【freeRtos软件定时器---源码级详解】
·
概要
FreeRTOS 的软件定时器是通过一个独立的定时器任务(Timer Task)来管理的,其核心组件包括
// 主要数据结构(在 timers.c 中)
typedef struct tmrTimerControl {
const char *pcTimerName; // 定时器名称
ListItem_t xTimerListItem; // 定时器链表项
TickType_t xTimerPeriodInTicks; // 定时周期
UBaseType_t uxAutoReload; // 自动重载标志
void *pvTimerID; // 用户标识符
TimerCallbackFunction_t pxCallbackFunction; // 回调函数
} xTIMER;
软件定时器整体实现时序图
大家先初步看下整体的实现流程,然后带着疑问看下面的源码详解。

核心组件详解
- 定时器任务prvTimerTask
static void prvTimerTask(void *pvParameters) {
TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;
for (;;) {
// 1. 从定时器命令队列读取命令
if (xQueueReceive(xTimerQueue, &xMessage, portMAX_DELAY) == pdPASS) {
// 处理命令:创建/启动/停止/复位/删除定时器
prvProcessReceivedCommands();
}
// 2. 处理到期定时器
xNextExpireTime = prvGetNextExpireTime(&xListWasEmpty);
if (xListWasEmpty == pdFALSE) {
// 处理到期回调
prvProcessExpiredTimer(xNextExpireTime, xTimeNow);
}
// 3. 将定时器任务挂起直到下一个定时器到期
if (xListWasEmpty == pdFALSE) {
vTaskSuspendAll();
xTimeNow = xTaskGetTickCount();
xNextExpireTime = prvGetNextExpireTime(&xListWasEmpty);
xTaskResumeAll();
if (xListWasEmpty == pdFALSE) {
vTaskDelayUntil(&xLastTime, xNextExpireTime - xTimeNow);
}
}
}
}
- 定时器命令队列 xTimerQueue
// 队列消息结构
typedef struct tmrTimerQueueMessage {
BaseType_t xMessageID; // 命令类型
TimerHandle_t xTimer; // 定时器句柄
TickType_t xExpiryTime; // 到期时间
TickType_t xNewPeriod; // 新周期
} TimerMessage_t;
// 命令类型枚举
#define tmrCOMMAND_START 0
#define tmrCOMMAND_STOP 1
#define tmrCOMMAND_RESET 2
#define tmrCOMMAND_CHANGE_PERIOD 3
#define tmrCOMMAND_DELETE 4
#define tmrCOMMAND_START_FROM_ISR 5
#define tmrCOMMAND_STOP_FROM_ISR 6
#define tmrCOMMAND_RESET_FROM_ISR 7
#define tmrCOMMAND_CHANGE_PERIOD_FROM_ISR 8
关键流程分析
- 定时器创建流程
TimerHandle_t xTimerCreate(const char *pcTimerName,
TickType_t xTimerPeriod,
UBaseType_t uxAutoReload,
void *pvTimerID,
TimerCallbackFunction_t pxCallbackFunction) {
xTIMER *pxNewTimer;
// 分配内存
pxNewTimer = (xTIMER *)pvPortMalloc(sizeof(xTIMER));
// 初始化定时器控制块
pxNewTimer->pcTimerName = pcTimerName;
pxNewTimer->xTimerPeriodInTicks = xTimerPeriod;
pxNewTimer->uxAutoReload = uxAutoReload;
pxNewTimer->pvTimerID = pvTimerID;
pxNewTimer->pxCallbackFunction = pxCallbackFunction;
// 初始化链表项
vListInitialiseItem(&(pxNewTimer->xTimerListItem));
listSET_LIST_ITEM_OWNER(&(pxNewTimer->xTimerListItem), pxNewTimer);
return pxNewTimer;
}
- 定时器启动流程
BaseType_t xTimerStart(TimerHandle_t xTimer, TickType_t xTicksToWait) {
TimerMessage_t xMessage;
// 发送启动命令到定时器队列
xMessage.xMessageID = tmrCOMMAND_START;
xMessage.xTimer = xTimer;
xMessage.xExpiryTime = xTaskGetTickCount() + pxTimer->xTimerPeriodInTicks;
// 向定时器队列发送消息
return xQueueSend(xTimerQueue, &xMessage, xTicksToWait);
}
- 到期定时器处理
static void prvProcessExpiredTimer(TickType_t xNextExpireTime, TickType_t xTimeNow) {
xTIMER *pxTimer;
// 从到期链表中移除定时器
pxTimer = (xTIMER *)listGET_OWNER_OF_HEAD_ENTRY(pxCurrentTimerList);
uxListRemove(&(pxTimer->xTimerListItem));
// 执行回调函数
pxTimer->pxCallbackFunction((TimerHandle_t)pxTimer);
// 如果是自动重载定时器,重新计算到期时间并重新插入链表
if (pxTimer->uxAutoReload == pdTRUE) {
pxTimer->xTimerListItem.xItemValue = xNextExpireTime + pxTimer->xTimerPeriodInTicks;
prvInsertTimerInActiveList(pxTimer, xNextExpireTime + pxTimer->xTimerPeriodInTicks, xTimeNow, xNextExpireTime);
}
}
- 定时器命令处理
启动定时器 tmrCOMMAND_START
case tmrCOMMAND_START:
case tmrCOMMAND_START_FROM_ISR:
/* 计算定时器的绝对到期时间 */
if (prvInsertTimerInActiveList(pxTimer,
xMessage.xExpiryTime,
xTimeNow,
xMessage.xExpiryTime) != pdFALSE)
{
/* 定时器已成功插入活动链表 */
pxTimer->ucStatus |= tmrSTATUS_IS_ACTIVE;
/* 如果插入的定时器是新的第一个到期定时器 */
if (listCURRENT_LIST_LENGTH(&(pxTimer->xTimerListItem)) == 1)
{
/* 检查是否需要唤醒定时器任务 */
if (pxTimer->xTimerListItem.xItemValue <= xNextExpireTime)
{
xTaskResumeAll();
xTaskResume(xTimerTaskHandle);
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
break;
1)使用 prvInsertTimerInActiveList() 将定时器插入到合适位置
2)设置 tmrSTATUS_IS_ACTIVE 标志
3)如果定时器成为新的最早到期定时器,需要唤醒定时器任务
停止定时器 tmrCOMMAND_STOP
case tmrCOMMAND_STOP:
case tmrCOMMAND_STOP_FROM_ISR:
/* 如果定时器在活动链表中 */
if ((pxTimer->ucStatus & tmrSTATUS_IS_ACTIVE) != 0)
{
/* 从活动链表中移除 */
uxListRemove(&(pxTimer->xTimerListItem));
/* 清除活动标志 */
pxTimer->ucStatus &= ~tmrSTATUS_IS_ACTIVE;
traceTIMER_STOP(pxTimer);
}
else
{
mtCOVERAGE_TEST_MARKER();
}
break;
1)检查 tmrSTATUS_IS_ACTIVE 标志
2)从链表移除,但不删除定时器控制块
3)清除活动状态标志
复位定时器 tmrCOMMAND_RESET
case tmrCOMMAND_RESET:
case tmrCOMMAND_RESET_FROM_ISR:
/* 如果定时器在活动链表中 */
if ((pxTimer->ucStatus & tmrSTATUS_IS_ACTIVE) != 0)
{
/* 重新计算到期时间 = 当前时间 + 周期 */
xMessage.xExpiryTime = xTimeNow + pxTimer->xTimerPeriodInTicks;
/* 从原链表移除,然后重新插入 */
uxListRemove(&(pxTimer->xTimerListItem));
/* 使用新的到期时间重新插入 */
if (prvInsertTimerInActiveList(pxTimer,
xMessage.xExpiryTime,
xTimeNow,
xMessage.xExpiryTime) != pdFALSE)
{
traceTIMER_RESET(pxTimer);
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 定时器未启动,重置操作无效 */
mtCOVERAGE_TEST_MARKER();
}
break;
1)相当于"停止后重新启动"
2)使用新的到期时间重新插入链表
改变周期 tmrCOMMAND_CHANGE_PERIOD
case tmrCOMMAND_CHANGE_PERIOD:
case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR:
/* 更新定时器周期 */
pxTimer->xTimerPeriodInTicks = xMessage.xNewPeriod;
/* 重新计算到期时间 */
xMessage.xExpiryTime = xTimeNow + pxTimer->xTimerPeriodInTicks;
/* 如果定时器当前是活动的 */
if ((pxTimer->ucStatus & tmrSTATUS_IS_ACTIVE) != 0)
{
/* 从原链表移除 */
uxListRemove(&(pxTimer->xTimerListItem));
/* 使用新周期重新插入 */
if (prvInsertTimerInActiveList(pxTimer,
xMessage.xExpiryTime,
xTimeNow,
xMessage.xExpiryTime) != pdFALSE)
{
traceTIMER_CHANGE_PERIOD(pxTimer, pxTimer->xTimerPeriodInTicks);
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
break;
1)先更新周期值
2)如果定时器正在运行,需要重新计算到期时间并重新插入
删除定时器 tmrCOMMAND_DELETE
case tmrCOMMAND_DELETE:
/* 如果定时器在活动链表中,先移除 */
if ((pxTimer->ucStatus & tmrSTATUS_IS_ACTIVE) != 0)
{
uxListRemove(&(pxTimer->xTimerListItem));
pxTimer->ucStatus &= ~tmrSTATUS_IS_ACTIVE;
}
/* 释放定时器控制块内存 */
vPortFree(pxTimer);
traceTIMER_DELETE(pxTimer);
break;
1)如果定时器正在运行,先从链表移除
2)释放定时器控制块内存
注意:不能在中断中调用删除命令
定时器链表管理机制
FreeRTOS 使用两个链表管理定时器:
static List_t xActiveTimerList1; // 当前活动定时器链表
static List_t xActiveTimerList2; // 备用定时器链表
static List_t *pxCurrentTimerList; // 指向当前链表
static List_t *pxOverflowTimerList; // 指向溢出链表
// 链表切换机制(处理TickCount溢出)
#define prvSwitchTimerLists() { \
List_t *pxTemp; \
pxTemp = pxCurrentTimerList; \
pxCurrentTimerList = pxOverflowTimerList; \
pxOverflowTimerList = pxTemp; \
xNumOfOverflows++; \
prvCheckForValidListAndQueue(); \
}
总结
- 延迟处理:定时器回调在定时器任务上下文中执行,不会在中断中执行
- 链表排序:定时器链表按到期时间升序排列,提高查找效率
- TickCount 溢出处理:通过双链表机制正确处理32位TickCount溢出
- 命令队列:所有定时器操作通过队列序列化,保证线程安全
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)