tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 软件定时器 + Tickless低功耗模式" author: "尚硅谷研究院" date: 2026-07-17
用生活理解:软件定时器像手机闹钟——设定时间到了就响铃(回调函数),可以是一次性的(单次提醒)或每天重复(周期提醒)。Tickless 低功耗像你晚上睡觉时关掉所有闹钟,第二天早上再一次性补上所有错过的报时——省电又不误事。
FreeRTOS 软件定时器是一种基于 Tick 计数的轻量级时间管理工具,完全由软件实现,不依赖硬件定时器资源。
软件定时器与硬件定时器的主要区别:
| 软件定时器 | 硬件定时器 |
|---|---|
| 依赖 RTOS 调度器进行计时 | 由芯片硬件提供,独立于 CPU |
| 精度受任务调度影响 | 精度高、分辨率高 |
| 不占用额外硬件资源 | 占用硬件定时器外设 |
定时器回调函数在定时器服务任务(Timer Service Task)的上下文中执行,因此回调函数中不能调用会导致阻塞的 API 函数(如 vTaskDelay、xQueueReceive 等)。
软件定时器完全基于系统 Tick 中断计数实现。所有对定时器的操作(启动、停止、复位等)都通过定时器命令队列发送消息给定时器服务任务,由该任务统一处理。
定时器服务任务:当 configUSE_TIMERS 设置为 1 时,启动调度器会自动创建此任务。它负责:
定时器状态机:
未创建 (Uncreated) → 已创建 (Created) → 运行中 (Running)
↑ │
└── 停止 ────────────┘
| 类型 | AutoReload | 行为 |
|---|---|---|
| 单次 (One-shot) | pdFALSE | 到期执行一次回调后停止 |
| 周期 (Periodic) | pdTRUE | 每到期一次执行一次回调,循环往复 |
| 函数 | 描述 |
|---|---|
xTimerCreate() |
动态创建软件定时器 |
xTimerStart() |
启动定时器(发命令到命令队列) |
xTimerStop() |
停止定时器 |
xTimerReset() |
复位定时器(重新开始计时) |
xTimerChangePeriod() |
更改定时周期 |
xTimerStartFromISR() |
在中断中启动定时器 |
xTimerStopFromISR() |
在中断中停止定时器 |
xTimerResetFromISR() |
在中断中复位定时器 |
所有 API 均通过命令队列发送消息给定时器服务任务,因此有阻塞时间参数(如 portMAX_DELAY)。
Tickless 模式是 FreeRTOS 的一种特殊运行模式,用于最小化系统时钟中断频率以降低功耗。在 Tickless 模式下,当所有任务都处于阻塞或挂起状态时,空闲任务会让 MCU 进入低功耗模式(通常是睡眠模式),并暂停 SysTick 中断,直到有事件需要处理才唤醒。
所有任务阻塞 → 空闲任务运行
↓
configPRE_SLEEP_PROCESSING(x) ← 关闭外设时钟等
↓
MCU 进入睡眠模式 (WFI)
SysTick 暂停
↓
中断/事件唤醒
↓
configPOST_SLEEP_PROCESSING(x) ← 恢复外设时钟
↓
补上错过的 Tick 计数
Tickless 的关键机制:退出低功耗后,FreeRTOS 会自动修正 Tick 计数值,确保系统时间不因休眠而丢失。
| 模式 | 说明 | 唤醒方式 |
|---|---|---|
| 睡眠 (Sleep) | 仅 CPU 停止,外设全工作 | 任何中断/事件 |
| 停机 (Stop) | SRAM/寄存器保持,1.8V 部分断电 | EXTI、RTC、USB 唤醒 |
| 待机 (Standby) | 最低功耗,SRAM 内容丢失 | NRST、IWDG、WKUP、RTC |
Tickless 默认使用睡眠模式(通过 __WFI 指令),任何中断均可唤醒。
| 宏 | 说明 |
|---|---|
configUSE_TICKLESS_IDLE |
使能 Tickless 模式(默认 0) |
configEXPECTED_IDLE_TIME_BEFORE_SLEEP |
进入低功耗的最短空闲时间(默认 2 ticks) |
configPRE_SLEEP_PROCESSING(x) |
进入低功耗前执行的操作(关闭外设时钟) |
configPOST_SLEEP_PROCESSING(x) |
退出低功耗后执行的操作(恢复外设时钟) |
File: 19_软件定时器/Core/Src/freertos_demo.c
#include "freertos_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h" // 软件定时器头文件
#include "LED.h"
#include "Key.h"
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);
// 定时器回调函数声明
void timer1_callback(TimerHandle_t xTimer); // 单次
void timer2_callback(TimerHandle_t xTimer); // 周期
TimerHandle_t timer1_handle;
TimerHandle_t timer2_handle;
void freertos_start(void)
{
// 创建单次定时器:500 ticks,不会自动重载
timer1_handle = xTimerCreate(
"timer1", // 定时器名称
(TickType_t)500, // 定时周期(Tick 数)
pdFALSE, // 单次模式(不自动重载)
(void *)1, // 定时器 ID
timer1_callback); // 回调函数
// 创建周期定时器:1000 ticks,自动重载
timer2_handle = xTimerCreate(
"timer2", // 定时器名称
(TickType_t)1000, // 定时周期(Tick 数)
pdTRUE, // 周期模式(自动重载)
(void *)2, // 定时器 ID
timer2_callback); // 回调函数
xTaskCreate((TaskFunction_t)start_task,
(char *)"start_task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handle);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t)task1,
(char *)"task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK,
(void *)NULL,
(UBaseType_t)TASK1_PRIORITY,
(TaskHandle_t *)&task1_handle);
vTaskDelete(NULL); // 启动任务自我删除
taskEXIT_CRITICAL();
}
void task1(void *pvParameters)
{
uint8_t key = 0;
BaseType_t res = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
// KEY1 按下 → 启动两个定时器
res = xTimerStart(timer1_handle, portMAX_DELAY);
if (res != pdFAIL)
printf("timer1 单次定时器启动成功\r\n");
res = xTimerStart(timer2_handle, portMAX_DELAY);
if (res != pdFAIL)
printf("timer2 周期定时器启动成功\r\n");
}
else if (key == KEY2_PRESS)
{
// KEY2 按下 → 停止两个定时器
res = xTimerStop(timer1_handle, portMAX_DELAY);
if (res != pdFAIL)
printf("timer1 单次定时器停止成功\r\n");
res = xTimerStop(timer2_handle, portMAX_DELAY);
if (res != pdFAIL)
printf("timer2 周期定时器停止成功\r\n");
}
vTaskDelay(500);
}
}
void timer1_callback(TimerHandle_t xTimer)
{
static uint16_t timer1_count = 0;
printf("timer1 定时回调=%d 次..\r\n", ++timer1_count);
}
void timer2_callback(TimerHandle_t xTimer)
{
static uint16_t timer2_count = 0;
printf("timer2 定时回调=%d 次..\r\n", ++timer2_count);
}
// FreeRTOSConfig.h 软件定时器相关配置
#define configUSE_TIMERS 1 // 使能软件定时器
#define configTIMER_TASK_PRIORITY 4 // 定时器服务任务优先级(configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH 5 // 定时器命令队列长度
#define configTIMER_TASK_STACK_DEPTH 256 // 定时器服务任务栈大小(128 * 2)
File: 20_Tickless低功耗/Core/Src/freertos_demo.c
#include "freertos_demo.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h" // 信号量头文件
#include "LED.h"
#include "Key.h"
QueueHandle_t sem_handle; // 二值信号量句柄
// 进入低功耗前关闭所有 GPIO 时钟
void PRE_SLEEP_PROCESSING()
{
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOC_CLK_DISABLE();
__HAL_RCC_GPIOD_CLK_DISABLE();
__HAL_RCC_GPIOE_CLK_DISABLE();
__HAL_RCC_GPIOF_CLK_DISABLE();
__HAL_RCC_GPIOG_CLK_DISABLE();
}
// 退出低功耗后恢复所有 GPIO 时钟
void POST_SLEEP_PROCESSING()
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
}
/* start_task / task1 / task2 创建与信号量实验代码... */
// (核心逻辑:task1 KEY1 → xSemaphoreGive,task2 xSemaphoreTake)
完整任务代码与前文信号量实验相同,增加了 configUSE_TICKLESS_IDLE 使能和睡眠前/后处理函数。
// FreeRTOSConfig.h Tickless 相关配置
#define configUSE_TICKLESS_IDLE 1 // 使能 Tickless 低功耗模式
#include "freertos_demo.h"
#define configPRE_SLEEP_PROCESSING(x) PRE_SLEEP_PROCESSING()
#define configPOST_SLEEP_PROCESSING(x) POST_SLEEP_PROCESSING()
configEXPECTED_IDLE_TIME_BEFORE_SLEEP 默认值 2,表示空闲时间超过 2 个 Tick 才进入低功耗。
| API | 参数 | 说明 |
|---|---|---|
xTimerCreate(name, period, autoReload, id, callback) |
name=定时器名, period=Tick数, autoReload=pdTRUE/pdFALSE, id=用户ID, callback=回调函数 | 创建软件定时器,返回句柄 |
xTimerStart(xTimer, blockTime) |
xTimer=句柄, blockTime=等待命令队列的阻塞时间 | 启动定时器 |
xTimerStop(xTimer, blockTime) |
同上 | 停止定时器 |
xTimerReset(xTimer, blockTime) |
同上 | 复位定时器(从头计时) |
xTimerChangePeriod(xTimer, newPeriod, blockTime) |
newPeriod=新周期 | 更改定时周期 |
| 配置宏 | 说明 |
|---|---|
configUSE_TICKLESS_IDLE |
使能 Tickless(0=关, 1=开) |
configEXPECTED_IDLE_TIME_BEFORE_SLEEP |
进入休眠前最小空闲 Tick 数 |
configPRE_SLEEP_PROCESSING(x) |
休眠前调用的用户函数 |
configPOST_SLEEP_PROCESSING(x) |
唤醒后调用的用户函数 |
定时器回调中不要调用阻塞 API:回调函数在定时器服务任务上下文中运行,调用 vTaskDelay()、xQueueReceive() 等阻塞函数会导致服务任务卡死,影响所有定时器。
xTimerStart 不是立即生效:所有定时器 API 都通过命令队列异步执行。返回值只表示命令是否成功放入队列,定时器实际状态变化有延迟。
configTIMER_TASK_PRIORITY 要合理设置:如果此优先级过低,定时器回调可能被其他任务长时间抢占,导致定时精度下降。
Tickless 下调试困难:进入低功耗后调试器可能断开连接。建议先关闭 Tickless 完成功能调试,最后再使能低功耗。
configPRE_SLEEP_PROCESSING 不能阻塞:此函数在空闲任务上下文中被调用,阻塞会影响系统进入低功耗。
configTOTAL_HEAP_SIZE 要足够:Tickless 模式会增加少量内存开销(用于记录休眠时间),堆大小不足可能导致创建任务失败。
Tickless 下中断频率过低可能有副作用:长时间休眠后,多个中断可能同时触发,需确保中断处理函数能处理累积事件。