FreeRTOS:软件定时器(Software Timers)与时间管理

软件定时器(Software Timers)与时间管理

本课目标:掌握 FreeRTOS 软件定时器的概念、常用 API 与最佳实践,以及与任务延时(vTaskDelay / vTaskDelayUntil)的比较。附带实验示例:用软件定时器触发周期性处理并给出代码与流程图。


0. 为什么需要软件定时器?

软件定时器是 RTOS 提供的一种在内核定时服务(Timer Daemon Task)上下文中运行的回调机制,用于把时间驱动的动作从任务中解耦出来。它适合:

  • 想要在将来某个时间点执行短小操作
  • 不想为定时唤醒写一个单独的高优先级任务
  • 需要"轻量级的延时回调"而不涉及硬件定时器复杂性

软件定时器的回调在定时器服务任务里运行(即任务上下文),不是 ISR。回调中不应长时间阻塞;若需要长时间处理,应使用回调来通知一个任务(例如通过信号量或任务通知)。


1. 常用 Timer API(精讲)

1.1 xTimerCreate

c 复制代码
TimerHandle_t xTimerCreate(
    const char * const pcTimerName,
    const TickType_t xTimerPeriodInTicks,
    const UBaseType_t uxAutoReload,
    void * pvTimerID,
    TimerCallbackFunction_t pxCallbackFunction
);
  • pcTimerName:调试用名字
  • xTimerPeriodInTicks:以 tick 为单位的周期
  • uxAutoReloadpdTRUE 表示周期性(auto-reload),pdFALSE 表示一次性(one-shot)
  • pvTimerID:用户自定义指针(可在回调中通过 pvTimerGetTimerID 取回)
  • pxCallbackFunction:回调函数,签名为 void vTimerCallback(TimerHandle_t xTimer)

1.2 xTimerStart / xTimerReset / xTimerStop / xTimerChangePeriod

  • xTimerStart(xTimer, xTicksToWait):启动计时器(若已启动可返回成功或错误,视实现而定)
  • xTimerReset(xTimer, xTicksToWait):把计时器余时重置为原始周期(常用于 one-shot)
  • xTimerStop(xTimer, xTicksToWait):停止计时器
  • xTimerChangePeriod(xTimer, xNewPeriod, xTicksToWait):改变周期(常用于动态调整)

所有这些 API 都可以在任务上下文中阻塞一段 xTicksToWait,但最好不要在回调中阻塞

1.3 FromISR 版本

FreeRTOS 提供 ISR 安全的版本(如 xTimerStartFromISRxTimerStopFromISR 等),允许在中断中控制软件定时器。

1.4 xTimerDelete

删除计时器并释放资源:

c 复制代码
BaseType_t xTimerDelete(TimerHandle_t xTimer, TickType_t xTicksToWait);

2. 回调上下文的重要注意点

  • 回调执行于定时器服务任务 ,它有自己的优先级(由 configTIMER_TASK_PRIORITY 指定)。
  • 回调不是 ISR,不应使用 FromISR 版本的 API
  • 回调中不要做长时间工作 或调用可能阻塞的 API(例如 xSemaphoreTake(..., portMAX_DELAY)
  • 回调里安全可用的策略:发信号、给队列/任务通知(注意使用非阻塞 xQueueSendFromISR 的替代;在回调中使用标准版本 xQueueSend 是可以的,因为回调是任务上下文)

3. 软件定时器 vs vTaskDelay / vTaskDelayUntil

特性 软件定时器 vTaskDelay / vTaskDelayUntil
运行上下文 定时器服务任务回调 普通任务上下文
精度 受 tick 精度影响 受 tick 精度影响
适用 异步回调、一次性定时;减少任务数量 任务内周期性工作、需要复杂逻辑时
可阻塞 回调中尽量不要阻塞 任务可以阻塞等待或做复杂处理
开销 定时器服务任务调度 + 内存 简单直接,任务切换开销

选择建议

  • 若只是需要在未来某时触发一个"短小动作"(例如超时、重试、心跳触发)------用软件定时器
  • 若需要周期性且任务需要做大量工作------用独立任务 + vTaskDelayUntil
  • 若需要精确控制执行顺序和阻塞行为,把复杂工作放在任务中,用定时器作为触发器(回调只发通知)

4. 用软件定时器触发周期性处理

c 复制代码
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

static TimerHandle_t xPeriodicTimer = NULL;
static TaskHandle_t xWorkerTask = NULL;

void vTimerCallback(TimerHandle_t xTimer)
{
    // 在定时器服务任务中运行:通知 worker 任务
    xTaskNotifyGive(xWorkerTask);
}

void vWorkerTask(void *pv)
{
    for (;;) {
        // 等待来自定时器的通知
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        // 收到通知,执行工作
        printf("[Worker] Timer fired at tick %lu\n", (unsigned long)xTaskGetTickCount());
        // 模拟短工作
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

int main(void)
{
    // 创建周期性软件定时器,周期 500ms
    xPeriodicTimer = xTimerCreate("Periodic", pdMS_TO_TICKS(500), pdTRUE, NULL, vTimerCallback);
    if (xPeriodicTimer == NULL) {
        printf("Failed to create timer\n");
        return -1;
    }

    xTaskCreate(vWorkerTask, "Worker", 256, NULL, tskIDLE_PRIORITY + 2, &xWorkerTask);

    // 启动定时器
    if (xTimerStart(xPeriodicTimer, 0) != pdPASS) {
        printf("Failed to start timer\n");
        return -1;
    }

    vTaskStartScheduler();
    for (;;);
}

对比代码

c 复制代码
void vWorkerPeriodic(void *pv)
{
    TickType_t last = xTaskGetTickCount();
    const TickType_t period = pdMS_TO_TICKS(500);

    for (;;) {
        // 做工作
        printf("[WorkerDelay] tick %lu\n", (unsigned long)xTaskGetTickCount());
        vTaskDelayUntil(&last, period);
    }
}

对比要点

  • 使用 vTaskDelayUntil 的任务在其自身上下文里运行,做更多复杂工作更方便
  • 使用软件定时器可以减少任务数量(定时器回调分发),更易于集中管理超时/重试逻辑
相关推荐
laplace01232 小时前
LangChain 1.0 入门实战(Part 1)详细笔记
笔记·python·langchain·numpy·pandas
washingtin2 小时前
Get “https://registry-1.docker.io/v2/“: context deadline exceeded
java·开发语言
only-lucky2 小时前
Python版本OpenCV
开发语言·python·opencv
三万棵雪松2 小时前
【python-基础】
开发语言·python
一路往蓝-Anbo2 小时前
C语言从句柄到对象 (七) —— 给对象加把锁:RTOS 环境下的并发安全
java·c语言·开发语言·stm32·单片机·嵌入式硬件·算法
先做个垃圾出来………2 小时前
2610.转换二维数组
开发语言·python
jdlxx_dongfangxing2 小时前
每日课堂笔记(2026年1月1日)
笔记
行业探路者2 小时前
如何利用二维码提升标牌标识实用性和用户体验?
学习·音视频·语音识别·二维码·设备巡检
北岛寒沫2 小时前
北京大学国家发展研究院 经济学辅修 经济学原理课程笔记(第十四课 寡头)
经验分享·笔记·学习