FreeRTOS系列---软件定时器详解

发布于:2025-02-24 ⋅ 阅读:(19) ⋅ 点赞:(0)

FreeRTOS 的软件定时器(Software Timer)实现基于任务调度和队列机制,其核心设计围绕 守护任务(Timer Daemon Task) 和 命令队列(Timer Command Queue) 展开。

一、软件定时器的核心结构

1. 定时器控制块(Timer_t)

在 timers.c 中,定时器通过 Timer_t 结构体管理:

typedef struct tmrTimerControl {
    const char *pcTimerName;          // 定时器名称(调试用)
    ListItem_t xTimerListItem;        // 链表项,用于插入定时器列表
    TickType_t xTimerPeriodInTicks;   // 定时周期(以系统节拍为单位)
    void *pvTimerID;                  // 用户自定义ID
    TimerCallbackFunction_t pxCallbackFunction; // 回调函数
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxTimerNumber;    /**< An ID     assigned by trace tools such as FreeRTOS+Trace */
    #endif
    uint8_t ucStatus;                 // 状态位(激活/静态分配/自动重载)
} Timer_t;
  • ucStatus:通过位掩码管理定时器状态(如 tmrSTATUS_IS_ACTIVE 表示定时器已激活)。
2. 定时器列表管理

      双列表设计xActiveTimerList1 和 xActiveTimerList2 用于处理系统节拍溢出。

static List_t xActiveTimerList1, xActiveTimerList2;
static List_t *pxCurrentTimerList = &xActiveTimerList1;
static List_t *pxOverflowTimerList = &xActiveTimerList2;
  • 溢出处理:当系统节拍计数器溢出时,切换当前列表和溢出列表(prvSwitchTimerLists)。

二、守护任务(Timer Daemon Task)

1. 任务入口函数 prvTimerTask

守护任务的核心逻辑:

static void prvTimerTask(void *pvParameters) {
    TickType_t xNextExpireTime;
    BaseType_t xListWasEmpty;

    for (;;) {
        // 1. 获取下一个定时器的到期时间
        xNextExpireTime = prvGetNextExpireTime(&xListWasEmpty);
        
        // 2. 处理到期定时器或阻塞等待
        prvProcessTimerOrBlockTask(xNextExpireTime, xListWasEmpty);
        
        // 3. 处理来自队列的命令(如启动/停止定时器)
        prvProcessReceivedCommands();
    }
}
2. 关键操作流程
  • 步骤 1:获取下一个到期时间
    通过 prvGetNextExpireTime 从当前活动列表中获取最近到期时间。
  • 步骤 2:处理到期或阻塞
    • 如果定时器到期,调用 prvProcessExpiredTimer 处理回调。
    • 如果无到期定时器,通过 vQueueWaitForMessageRestricted 阻塞等待命令或超时。
  • 步骤 3:处理命令
    从命令队列 xTimerQueue 中读取并执行操作(如启动、停止、修改周期)。

三、命令队列与定时器操作

1. 命令队列 (xTimerQueue)
  • 作用:所有定时器操作(如 xTimerStart()xTimerStop())通过发送命令到队列,由守护任务统一处理。
  • 命令结构体DaemonTaskMessage_t 封装命令类型和参数。
     
    typedef struct tmrTimerQueueMessage {
        BaseType_t xMessageID; // 命令类型(如 tmrCOMMAND_START)
        union {
            TimerParameter_t xTimerParameters; // 定时器相关参数
            CallbackParameters_t xCallbackParameters; // 回调函数参数
        } u;
    } DaemonTaskMessage_t;
2. 定时器启动流程(以 xTimerStart 为例)
  1. 用户调用 xTimerStart()
    生成 DaemonTaskMessage_t 消息,发送到 xTimerQueue
  2. 守护任务接收消息
    在 prvProcessReceivedCommands 中解析命令,执行以下操作:
    • 将定时器插入活动列表(prvInsertTimerInActiveList)。
    • 如果定时器已到期,立即触发回调。

四、定时器到期处理

1. 处理到期定时器 (prvProcessExpiredTimer)

static void prvProcessExpiredTimer(TickType_t xNextExpireTime, TickType_t xTimeNow) {
    Timer_t *pxTimer = (Timer_t *)listGET_OWNER_OF_HEAD_ENTRY(pxCurrentTimerList);
    
    // 1. 从活动列表中移除定时器
    uxListRemove(&pxTimer->xTimerListItem);
    
    // 2. 如果是自动重载定时器,重新计算到期时间并插入列表
    if (pxTimer->ucStatus & tmrSTATUS_IS_AUTORELOAD) {
        prvReloadTimer(pxTimer, xNextExpireTime, xTimeNow);
    } else {
        pxTimer->ucStatus &= ~tmrSTATUS_IS_ACTIVE; // 标记为非激活
    }
    
    // 3. 执行回调函数
    pxTimer->pxCallbackFunction((TimerHandle_t)pxTimer);
}
2. 自动重载逻辑 (prvReloadTimer)
  • 计算新的到期时间:xNextExpireTime + xTimerPeriodInTicks
  • 将定时器重新插入活动列表,处理可能的溢出情况。

五、关键设计细节

1. 系统节拍溢出处理
  • 双列表切换:当检测到节拍计数器溢出(xTimeNow < xLastTime),调用 prvSwitchTimerLists 切换当前列表和溢出列表。
  • 溢出列表作用:存放因节拍溢出无法立即处理的定时器。
2. 中断安全操作
  • 中断中操作定时器:使用 xTimerStartFromISR 或 xTimerResetFromISR,通过 xQueueSendFromISR 发送命令到队列。
3. 静态与动态分配
  • 静态分配:通过 xTimerCreateStatic 使用预分配的 StaticTimer_t 内存。
  • 动态分配:通过 xTimerCreate 动态申请内存,需在删除时释放。

六、常见问题

  1. 回调函数为何不能阻塞?
    回调在守护任务上下文中执行,阻塞会导致其他定时器无法及时处理。

  2. 定时器精度如何保证?
    依赖系统节拍频率(configTICK_RATE_HZ),提高频率可提升精度,但增加系统负载。

  3. 命令队列溢出如何处理?
    需合理设置 configTIMER_QUEUE_LENGTH,并在发送命令时检查返回值。

总结

FreeRTOS 的软件定时器通过 守护任务 和 命令队列 实现异步管理,其核心设计包括:

  • 双列表处理节拍溢出:确保长时间运行下的稳定性。
  • 线程安全的命令队列:所有操作由守护任务统一处理。
  • 灵活的回调机制:支持单次、周期定时及动态修改。

通过分析源码,可以深入理解定时器的生命周期管理、命令处理流程及底层数据结构设计。