15-软件定时器与Tickless低功耗.md 13 KB


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

#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

// 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

#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

// 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 低功耗模式 定时器状态图