--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 软件定时器 + Tickless低功耗模式" author: "尚硅谷研究院" date: 2026-07-17 created: 2026-07-17 --- # 软件定时器与 Tickless 低功耗 > **用生活理解**:软件定时器像手机闹钟——设定时间到了就响铃(回调函数),可以是一次性的(单次提醒)或每天重复(周期提醒)。Tickless 低功耗像你晚上睡觉时关掉所有闹钟,第二天早上再一次性补上所有错过的报时——省电又不误事。 ## 软件定时器 ### Basic concept FreeRTOS 软件定时器是一种基于 Tick 计数的轻量级时间管理工具,完全由软件实现,不依赖硬件定时器资源。 软件定时器与硬件定时器的主要区别: | 软件定时器 | 硬件定时器 | |-----------|-----------| | 依赖 RTOS 调度器进行计时 | 由芯片硬件提供,独立于 CPU | | 精度受任务调度影响 | 精度高、分辨率高 | | 不占用额外硬件资源 | 占用硬件定时器外设 | 定时器回调函数在**定时器服务任务**(Timer Service Task)的上下文中执行,因此回调函数中**不能调用会导致阻塞的 API 函数**(如 vTaskDelay、xQueueReceive 等)。 ### Working principle 软件定时器完全基于系统 Tick 中断计数实现。所有对定时器的操作(启动、停止、复位等)都通过**定时器命令队列**发送消息给定时器服务任务,由该任务统一处理。 **定时器服务任务**:当 `configUSE_TIMERS` 设置为 1 时,启动调度器会自动创建此任务。它负责: - 判断定时器是否超时 - 调用超时定时器的回调函数 - 处理定时器命令队列中的消息 **定时器状态机**: ``` 未创建 (Uncreated) → 已创建 (Created) → 运行中 (Running) ↑ │ └── 停止 ────────────┘ ``` - **Dormant(休眠)**:定时器已创建但未启动 - **Running(运行)**:定时器已启动,正在倒计时 - 单次定时器到期后自动回到 Dormant;周期定时器到期后自动重启 ### 单次定时器 vs 周期定时器 | 类型 | AutoReload | 行为 | |------|-----------|------| | 单次 (One-shot) | pdFALSE | 到期执行一次回调后停止 | | 周期 (Periodic) | pdTRUE | 每到期一次执行一次回调,循环往复 | ### Core API | 函数 | 描述 | |------|------| | `xTimerCreate()` | 动态创建软件定时器 | | `xTimerStart()` | 启动定时器(发命令到命令队列) | | `xTimerStop()` | 停止定时器 | | `xTimerReset()` | 复位定时器(重新开始计时) | | `xTimerChangePeriod()` | 更改定时周期 | | `xTimerStartFromISR()` | 在中断中启动定时器 | | `xTimerStopFromISR()` | 在中断中停止定时器 | | `xTimerResetFromISR()` | 在中断中复位定时器 | 所有 API 均通过命令队列发送消息给定时器服务任务,因此有**阻塞时间**参数(如 `portMAX_DELAY`)。 --- ## Tickless 低功耗模式 ### Basic concept Tickless 模式是 FreeRTOS 的一种特殊运行模式,用于最小化系统时钟中断频率以降低功耗。在 Tickless 模式下,当所有任务都处于阻塞或挂起状态时,空闲任务会让 MCU 进入低功耗模式(通常是睡眠模式),并暂停 SysTick 中断,直到有事件需要处理才唤醒。 ### Working principle ``` 所有任务阻塞 → 空闲任务运行 ↓ configPRE_SLEEP_PROCESSING(x) ← 关闭外设时钟等 ↓ MCU 进入睡眠模式 (WFI) SysTick 暂停 ↓ 中断/事件唤醒 ↓ configPOST_SLEEP_PROCESSING(x) ← 恢复外设时钟 ↓ 补上错过的 Tick 计数 ``` Tickless 的关键机制:退出低功耗后,FreeRTOS 会自动修正 Tick 计数值,确保系统时间不因休眠而丢失。 ### STM32 低功耗模式 | 模式 | 说明 | 唤醒方式 | |------|------|---------| | 睡眠 (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)` | 退出低功耗后执行的操作(恢复外设时钟) | ## Experiment ### Experiment 1: 软件定时器 #### Requirements - 创建单次定时器 timer1(500 ticks)和周期定时器 timer2(1000 ticks) - KEY1 → 启动两个定时器,KEY2 → 停止两个定时器 - 观察回调函数的执行次数 #### Code Implementation **File: 19_软件定时器/Core/Src/freertos_demo.c** ```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); } ``` #### Key Config ```c // 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) ``` ### Experiment 2: Tickless 低功耗 #### Requirements - 基于二值信号量实验(task1 按键释放信号量 → task2 获取信号量) - 使能 Tickless 模式,编写睡眠前/后处理函数 - 对比功耗变化 #### Code Implementation **File: 20_Tickless低功耗/Core/Src/freertos_demo.c** ```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` 使能和睡眠前/后处理函数。 #### Key Config ```c // 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 才进入低功耗。 ## Core API Reference Table ### 软件定时器 API | 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=新周期 | 更改定时周期 | ### Tickless 配置 API | 配置宏 | 说明 | |--------|------| | `configUSE_TICKLESS_IDLE` | 使能 Tickless(0=关, 1=开) | | `configEXPECTED_IDLE_TIME_BEFORE_SLEEP` | 进入休眠前最小空闲 Tick 数 | | `configPRE_SLEEP_PROCESSING(x)` | 休眠前调用的用户函数 | | `configPOST_SLEEP_PROCESSING(x)` | 唤醒后调用的用户函数 | ## 常见问题与避坑 1. **定时器回调中不要调用阻塞 API**:回调函数在定时器服务任务上下文中运行,调用 `vTaskDelay()`、`xQueueReceive()` 等阻塞函数会导致服务任务卡死,影响所有定时器。 2. **`xTimerStart` 不是立即生效**:所有定时器 API 都通过命令队列异步执行。返回值只表示命令是否成功放入队列,定时器实际状态变化有延迟。 3. **`configTIMER_TASK_PRIORITY` 要合理设置**:如果此优先级过低,定时器回调可能被其他任务长时间抢占,导致定时精度下降。 4. **Tickless 下调试困难**:进入低功耗后调试器可能断开连接。建议先关闭 Tickless 完成功能调试,最后再使能低功耗。 5. **`configPRE_SLEEP_PROCESSING` 不能阻塞**:此函数在空闲任务上下文中被调用,阻塞会影响系统进入低功耗。 6. **`configTOTAL_HEAP_SIZE` 要足够**:Tickless 模式会增加少量内存开销(用于记录休眠时间),堆大小不足可能导致创建任务失败。 7. **Tickless 下中断频率过低可能有副作用**:长时间休眠后,多个中断可能同时触发,需确保中断处理函数能处理累积事件。 ![STM32 低功耗模式](./assets/stm32_low_power_modes.png) ![定时器状态图](./assets/timer_state_diagram.png)