概要

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;

软件定时器整体实现时序图

大家先初步看下整体的实现流程,然后带着疑问看下面的源码详解。
在这里插入图片描述

核心组件详解

  1. 定时器任务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);
            }
        }
    }
}
  1. 定时器命令队列 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

关键流程分析

  1. 定时器创建流程
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;
}
  1. 定时器启动流程
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);
}
  1. 到期定时器处理
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);
    }
}
  1. 定时器命令处理
    启动定时器 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();                                  \
}

总结

  1. 延迟处理:定时器回调在定时器任务上下文中执行,不会在中断中执行
  2. 链表排序:定时器链表按到期时间升序排列,提高查找效率
  3. TickCount 溢出处理:通过双链表机制正确处理32位TickCount溢出
  4. 命令队列:所有定时器操作通过队列序列化,保证线程安全
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐