一、 基础原理

定时器的通用实现逻辑,这是理解 FreeRTOS 的前提:

  • 硬件基础:硬件定时器每隔 1ms 产生一次 SysTick 中断(FreeRTOS 的系统节拍中断),中断里会累加一个全局计数值xTickCount(你笔记里的 “tik com x tik/Count”)。
  • 软件定时器结构体:每个软件定时器对应一个结构体,核心成员:
typedef struct tmrTimerControl
{
    const char * pcTimerName;                   /* 定时器名称(仅用于调试)*/
    ListItem_t xTimerListItem;                  /* 定时器链表节点(核心字段)*/
    TickType_t xTimerPeriodInTicks;             /* 定时器周期(单位:系统节拍 Tick)*/
    void * pvTimerID;                           /* 定时器唯一标识 ID*/
    TimerCallbackFunction_t pxCallbackFunction; /* 定时器超时回调函数指针*/
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxTimerNumber;              /* 定时器追踪编号(仅启用追踪功能时生效)*/
    #endif
    uint8_t ucStatus;                           /**< 定时器状态位(8 位位图,存储多个状态标识)
                                                     - 包含的核心状态:
                                                     1. 内存分配方式(静态分配/动态分配);
                                                     2. 定时器激活状态(是否处于运行中);
                                                     3. 其他内部管理状态(如是否等待删除) */
} xTIMER;

    超时时间:比如设置 100ms,就是xTickCount + 100(记为xExpireTime),当xTickCount累加到这个值时触发回调;

    定时器链表:所有定时器通过xTimerListItem挂到一个链表中,且链表xExpireTime从小到大排序

    • 如果不排序,每次中断都要遍历所有定时器判断是否超时;排序后只需要检查链表第一个节点(最早超时的定时器),效率极高。

    二、FreeRTOS定时中断与传统RTOS的差异

    1 传统做法(直接在中断中处理)

    • 流程:Tick 中断发生 -> 遍历定时器链表 -> 发现超时 -> 直接调用回调函数
    • 缺点
      • 如果回调函数执行时间长,会长时间占用中断上下文,导致其他高优先级中断无法响应,破坏系统的实时性。
      • 中断上下文中不能调用某些阻塞 API(如信号量、队列接收等),限制了回调函数的功能。

    2 FreeRTOS 的做法(守护任务/daemon Task 机制)

    FreeRTOS 为了保证中断的快速退出提高灵活性,采用了“委托”机制。

    • 核心组件
      1. 定时器命令队列 (xTimerQueue):用于接收创建、启动、停止、修改周期等命令。
      2. 定时器守护任务 (prvTimerTask):一个优先级较高的独立任务,专门负责处理定时器逻辑。
    • 工作流程
      1. Tick 中断中:仅负责两件事——① 更新全局计数值 xTickCount;② 触发系统任务调度。关键点:Tick 中断不检查定时器链表、不判断定时器是否超时、也不向 xTimerQueue 发送任何消息,中断会快速退出。
      2. 守护任务中(优先级由configTIMER_TASK_PRIORITY决定
        1. 主动计算定时器链表中“下一个最早超时的时间”,然后阻塞等待 xTimerQueue 消息(阻塞超时时间 = 计算出的超时差值);
        2. 被唤醒的两种情况:

          情况1:收到用户操作(启动/停止/修改定时器)的命令消息(来自 xTimerQueue);

          情况2:阻塞等待超时(无新命令,且链表头定时器的超时时间已到);

        3. 若因“阻塞超时”唤醒:主动遍历定时器链表,处理所有超时的定时器,在任务上下文中执行回调函数;

        4. 若因“收到命令”唤醒:处理命令(如插入/移除定时器链表节点),然后重新计算超时时间,再次阻塞等待。

    • 优点
      • 中断极快退出,系统实时性高。
      • 回调函数运行在任务上下文中,可以使用所有 FreeRTOS API(包括阻塞调用)。
      • 可以通过配置 configTIMER_TASK_PRIORITY 灵活控制定时器处理的优先级。
    • 缺点:多了队列 / 任务的开销

    总结一下就是:

    FreeRTOS 定时器设计的核心规则 ——所有对定时器的操作(启动 / 停止 / 删除 / 修改周期)都不是直接生效,而是通过向队列发消息,由守护任务统一处理

    三,定时任务流程

    底层函数基本都是适用 xTimerGenericCommand

    步骤 阶段 执行者 核心动作 定时器状态 是否在链表中? 备注
    1 创建 用户任务 xTimerCreate
    分配内存,初始化结构体
    未激活
    (Inactive)
    ❌ 否 此时只是个空壳对象
    2 发送命令 用户任务 xTimerStart
    仅发送消息到队列
    未激活
    (Inactive)
    ❌ 否 关键点: 函数返回时,定时器仍不在链表中!
    3 处理命令 守护任务 prvProcessReceivedCommands
    读取队列消息,执行插入操作
    激活
    (Active)
    ✅ 是 此刻才真正进入有序链表,开始计时
    4 运行/触发 守护任务 prvProcessExpiredTimers
    检查时间,执行回调
    运行中 ✅ 是 周期性定时器会在此步重新计算时间并重插链表
    5 停止 用户任务 xTimerStop
    发送停止消息到队列
    未激活
    (Inactive)
    ❌ 否 (稍后移除) 同启动,需等守护任务读到消息后才从链表移除

    1.定时任务启动函数

    #define xTimerStart( xTimer, xTicksToWait ) \
        xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START, ( xTaskGetTickCount() ), NULL, ( xTicksToWait ) )
    

    可以看到,启动操作不是同步完成的,而是异步的。它只是通过队列发个消息给守护任务说“我要启动”。如果队列满了,调用者可以选择阻塞等待队列有空位。

    以下为xTimerGenericCommand的一部分:
    判断调用来源:是任务上下文 还是 中断上下文?
            if( xCommandID < tmrFIRST_FROM_ISR_COMMAND ) 
            {
                // --- 任务上下文 (如 xTimerStart) ---
                
                // 检查调度器是否正在运行
                if( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING )
                {
                    // 正常发送:如果队列满,根据 xTicksToWait 决定是否阻塞
                    xReturn = xQueueSendToBack( xTimerQueue, &xMessage, xTicksToWait );
                }
                else
                {
                    // 调度器未运行(如在 main 函数刚启动时):不能阻塞,立即发送或失败
                    xReturn = xQueueSendToBack( xTimerQueue, &xMessage, tmrNO_DELAY );
                }
            }
            else
            {
                // --- 中断上下文 (如 xTimerStartFromISR) ---
                // 使用中断安全的发送函数
                xReturn = xQueueSendToBackFromISR( xTimerQueue, &xMessage, pxHigherPriorityTaskWoken );
            }

    2.守护任务

    static portTASK_FUNCTION( prvTimerTask, pvParameters )
    {
        TickType_t xNextExpireTime;    // 存储链表中“下一个最早超时的定时器时间”
        BaseType_t xListWasEmpty;      // 标记定时器链表是否为空
    
        /* Just to avoid compiler warnings. */
        ( void ) pvParameters;         // 屏蔽参数未使用的编译警告(任务参数无意义)
    
        #if ( configUSE_DAEMON_TASK_STARTUP_HOOK == 1 )
            {
                extern void vApplicationDaemonTaskStartupHook( void );
    
                /* 应用层钩子函数:守护任务启动时执行(可选)
                 * 用途:开发者可以在守护任务启动后、正式工作前执行初始化逻辑
                 * 比如初始化定时器相关的硬件/全局变量 */
                vApplicationDaemonTaskStartupHook();
            }
        #endif /* configUSE_DAEMON_TASK_STARTUP_HOOK */
    
        // 死循环:守护任务的核心(任务一旦启动,永远运行)
        for( ; ; )
        {
            /* 步骤1:查询定时器链表,获取关键信息
             * 1. 检查定时器链表(xTimerList)是否为空 → 结果存入xListWasEmpty
             * 2. 如果链表非空,计算“下一个最早超时的定时器时间” → 存入xNextExpireTime
             * (链表已按超时时间排序,只需取第一个节点的超时时间) */
            xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
    
            /* 步骤2:核心逻辑——阻塞等待/处理超时
             * 作用:
             * 1. 如果链表为空 → 阻塞等待xTimerQueue的命令(启动/停止等);
             * 2. 如果链表非空 → 阻塞等待“xNextExpireTime时间” 或 “xTimerQueue有命令”(二者满足其一就唤醒);
             * 3. 如果等待超时(即xNextExpireTime到了)→ 立即处理已超时的定时器(调用回调函数);
             * 这就是你之前问的“精华逻辑”:既等命令,又等超时,二选一唤醒 */
            prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
    
            /* 步骤3:处理命令队列中的所有待执行命令
             * 包括:启动定时器(插入链表)、停止定时器(移除链表)、修改周期、删除定时器等
             * 你之前问的“启动定时器时插入链表”,就是在这个函数里执行的! */
            prvProcessReceivedCommands();
        }
    }
    关键数据结构:有序链表
    • 存储方式:所有激活的定时器挂载在同一个双向链表 (xActiveTimerList) 中。
    • 排序规则:严格按照绝对超时时间 (ExpireTime) 从小到大排序。
    • 优化效果
      • 链表头永远是最早过期的定时器。
      • 检查超时无需遍历全表,只需看头节点 ( O(1)O(1) 复杂度)。

    注意注意:prvProcessReceivedCommands中也实现了处理ISR任务通知中的队列消息

    四、定时任务总结

    Logo

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

    更多推荐