概述

软件定时器是 FreeRTOS 提供的一个非常重要且实用的组件,它允许你在应用程序中创建和管理多个基于系统节拍(tick)的定时器,而无需依赖硬件定时器外设。FreeRTOS 软件定时器是一个强大的后台时间管理工具,非常适合执行非精确的周期性任务(如状态监测、按键扫描、LED 指示、发送心跳包等)。它的核心思想是将定时事件的响应从硬件中断转移到一个专门的中等优先级任务中,使得应用程序设计更灵活、更安全(避免在 ISR 中处理复杂逻辑),但开发者必须清楚其运行机制和限制。

1 核心概念

1) 软件实现

与硬件定时器不同,软件定时器完全由 FreeRTOS 内核通过系统节拍中断来管理。这意味着其精度受限于 configTICK_RATE_HZ(通常为 1ms 或 10ms 一个节拍)。

2) 回调函数

当软件定时器到期时,内核不会唤醒一个任务,而是调用一个预先设置好的回调函数。这个函数在 定时器守护任务 的上下文中执行。

3) 定时器守护任务

  • FreeRTOS 在启动调度器时(如果配置启用)会自动创建一个名为 “定时器服务任务” 或 “守护任务” 的任务,其优先级由 configTIMER_TASK_PRIORITY 定义。

  • 所有软件定时器的命令(启动、停止、复位等)都是通过一个队列(定时器命令队列)发送给这个守护任务来执行的。定时器回调函数也在这个任务的上下文中运行

  • 这意味着回调函数不能是阻塞型或计算密集型的,否则会阻塞所有其他软件定时器的处理。

4) 两种模式

  • 单次定时器:启动后,只到期执行一次回调函数,之后自动进入休眠状态。

  • 自动重载定时器:启动后,周期性地到期并执行回调函数,直到被手动停止。

2 工作原理图

2.1 框架结构

2.2 重要 API 函数

1) 创建定时器

TimerHandle_t xTimerCreate( const char * const pcTimerName,
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction );
  • pcTimerName: 调试用的名称。

  • xTimerPeriodInTicks: 定时周期,以系统节拍数为单位。例如,节拍频率为 1000Hz (1ms),要创建 100ms 定时器,则传入 100

  • uxAutoReload: 模式选择。pdTRUE 为自动重载,pdFALSE 为单次。

  • pvTimerID: 一个用户标识符(整数或指针),用于在回调函数中区分多个定时器,或在运行时查询/更改。

  • pxCallbackFunction: 到期时要执行的回调函数,格式为 void vCallbackFunction( TimerHandle_t xTimer )

2) 启动定时器

BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
  • xTimer: 要启动的定时器句柄。

  • xTicksToWait: 注意! 这是向定时器命令队列发送“启动命令”的阻塞时间,不是定时器的延时时间。如果在中断中调用,必须使用 xTimerStartFromISR 且此参数为 0。

3) 停止定时器

BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

4) 复位定时器

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
  • 将定时器的剩余时间重置为初始周期值。如果定时器未运行,则会启动它。

5) 更改定时器周期

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                               TickType_t xNewPeriod,
                               TickType_t xTicksToWait );

6) 查询定时器是否活跃

BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer );

7) 获取定时器 ID

void *pvTimerGetTimerID( TimerHandle_t xTimer );

8) 设置定时器 ID

void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

2.3 配置

在 FreeRTOSConfig.h 中,必须启用软件定时器并配置相关参数:

#define configUSE_TIMERS             1 // 必须设为 1
#define configTIMER_TASK_PRIORITY    ( configMAX_PRIORITIES - 1 ) // 守护任务优先级,通常较高
#define configTIMER_QUEUE_LENGTH     10 // 定时器命令队列长度
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) // 守护任务堆栈大小

2.4 注意事项

1) 回调函数限制

回调函数在守护任务中运行,绝不能调用任何会阻塞的 API(如 vTaskDelay, 带阻塞时间的队列/信号量操作)。应保持短小精悍。

2) 命令队列

xTicksToWait 参数只影响命令发送到队列的过程,不影响定时器本身。在中断服务程序(ISR)中必须使用 FromISR 版本,且等待时间为 0。

3) 精度

软件定时器的精度是“至少”你设置的周期,可能会因为守护任务被更高优先级任务抢占而稍有延迟。不适用于对时间极其敏感的操作

4) 资源消耗

每个定时器需要少量 RAM 来存储其状态和命令队列。守护任务本身也消耗堆栈和 CPU 时间。

5) ID 的妙用

pvTimerID 非常有用。例如,你可以让多个不同的定时器(控制不同外设)共享同一个回调函数,在函数内部通过 pvTimerGetTimerID 获取 ID 来区分是哪个定时器到期,从而执行不同的操作。

6) 单次定时器的重启

单次定时器到期后,如果想再次使用,需要手动调用 xTimerStart 或 xTimerReset 来重启。

3 使用示例:创建一个 LED 闪烁定时器

#include "FreeRTOS.h"
#include "semphr.h"
#include "event_groups.h"
#include "queue.h"

TimerHandle_t xLEDTimer_on, xLEDTimer_off;

// 定时器回调函数
void vLEDTimerCallback_on( TimerHandle_t xTimer )
{
    // 这个函数在守护任务中执行
    TURN_ON_LED; // 简单地翻转 LED
    // 如果是单次定时器,这里执行一次就结束了。
    // 因为是自动重载,它会周期执行。
    xTimerStop( xLEDTimer_on, 250 );
    
    if( xTimerStart( xLEDTimer_off, 10 ) != pdPASS )
    {
        // 启动失败(命令队列满,100 ticks 内没空间)
    }
}


// 定时器回调函数
void vLEDTimerCallback_off( TimerHandle_t xTimer )
{
    // 这个函数在守护任务中执行
    TURN_OFF_LED; // 简单地翻转 LED
    // 如果是单次定时器,这里执行一次就结束了。
    // 因为是自动重载,它会周期执行。
    xTimerStop( xLEDTimer_off, 500 );
    
    if( xTimerStart( xLEDTimer_on, 10 ) != pdPASS )
    {
        // 启动失败(命令队列满,100 ticks 内没空间)
    }
}


void app_task_init( void )
{
    // 1. 创建一个自动重载定时器,周期 500ms (假设 tick = 10ms,则需 50 ticks)
    const TickType_t xTimerPeriod_on = pdMS_TO_TICKS(250); // 将毫秒转换为节拍数的便捷宏
    xLEDTimer_on = xTimerCreate(
                                "LED ON",          // 名称
                                xTimerPeriod_on,      // 周期
                                pdFALSE,                 // 自动重载
                                (void *)0,              // ID,这里用 0
                                vLEDTimerCallback_on    // 回调函数
                            );

    if( xLEDTimer_on != NULL )
    {
        // 2. 启动定时器(等待命令入队 100 ticks)
        if( xTimerStart( xLEDTimer_on, 100 ) != pdPASS )
        {
            // 启动失败(命令队列满,100 ticks 内没空间)
        }
    }
    
    
    // 1. 创建一个自动重载定时器,周期 500ms (假设 tick = 10ms,则需 50 ticks)
    const TickType_t xTimerPeriod_off = pdMS_TO_TICKS(2000); // 将毫秒转换为节拍数的便捷宏
    xLEDTimer_off = xTimerCreate(
                                "LED OFF",              // 名称
                                xTimerPeriod_off,        // 周期
                                pdFALSE,                 // 自动重载
                                (void *)1,               // ID,这里用 0
                                vLEDTimerCallback_off    // 回调函数
                            );

    if( xLEDTimer_off != NULL )
    {
//        // 2. 启动定时器(等待命令入队 100 ticks)
//        if( xTimerStart( xLEDTimer_off, 100 ) != pdPASS )
//        {
//            // 启动失败(命令队列满,100 ticks 内没空间)
//        }
    }
}

Logo

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

更多推荐