目录
一:STM32低功耗模式
1:1.2V 域 vs VDD 域 vs 调压器
2:三种低功耗模式本质
3:一张图解释停机和待机
4:正点例程采用睡眠模式
二:实验分析
1:正点实验内容
2:用到的几个宏
三:实验改动
rtos\22\ 改动清单
freertos_demo.c
FreeRTOSConfig.h
main.c
钩子函数:
运行逻辑总结
一:STM32低功耗模式
| 模式名称 | 进入 | 唤醒 | 对 1.2V 域时钟的影响 | 对 VDD 域时钟的影响 | 调压器 |
|---|---|---|---|---|---|
| 睡眠(立即休眠或退出是休眠) | WFI | 任意中断 | CPU CLK 关闭对其它时钟或模拟时钟源无影响 | 无 | 开启 |
| WFE | 唤醒事件 | ||||
| 停止 | PDDS 和 LPDS 位 +SLEEPDEEP 位+WFI 或 WFE | 任意 EXTI 线(在 EXTI 寄存器中配置,内部线和外部线) | 所有 1.2 V 域时钟都关闭 | HSI 和 HSE 振荡器关闭 | 开启或处于低功耗模式 |
| 待机 | PDDS 位 +SLEEPDEEP 位+WFI 或 WFE | WKUP 引脚上升沿、RTC 闹钟(闹钟 A 或闹钟 B)、RTC 唤醒事件、RTC 入侵事件、RTC 时间戳事件、NRST 引脚外部复位、IWDG 复位 | 所有 1.2 V 域时钟都关闭 | HSI 和 HSE 振荡器关闭 | 关闭 |
1:1.2V 域 vs VDD 域 vs 调压器
芯片供电秘密
F429 外接 3.3V(VDD),但 Cortex-M4 核心实际跑在1.2V。芯片内部有个调压器,把 3.3V → 1.2V:
外部 3.3V (VDD) │ ├──────────────────────────→ 直接给 GPIO、USART、SPI、RTC 备份域 │ ↑ 这部分就是 VDD 域 │ └──→ 调压器 → 1.2V ──→ CPU、NVIC、SRAM、Flash、DMA、大部分外设 ↑ 这部分就是 1.2V 域
2:三种低功耗模式本质
| 睡眠 | 停止 | 待机 | |
|---|---|---|---|
| CPU 钟 | 关 | 关 | 关 |
| 1.2V 域(外设、SRAM) | 开着 | 全关 | 全关 |
| VDD 域(GPIO、RTC) | 开着 | 开着 | 开着 |
| 调压器 | 开着 | 开着/半开 | 关 |
| 唤醒后 | 原地继续 | 程序从头跑(但 SRAM 还在) | 程序从头跑,SRAM 全丢 |
3:一张图解释停机和待机
睡眠模式:CPU 打盹,店还开着 拉下卷帘门(WFI)→ CPU 不干活了 但店里的灯(外设时钟)、收银机(SRAM)全开着 有人敲门(任意中断)→ 门一拉就起来 → 瞬间恢复 停止模式:打烊但不锁门 灯关了(1.2V 域时钟全关,包括外设、CPU) 收银机还通着电(调压器还在,SRAM 保持) 只有应急灯亮着(VDD 域——GPIO 能检测电平变化) 外部中断、RTC 闹钟 → 重新开灯 → 程序接着跑 待机模式:彻底断电只留一把钥匙 整店断电(调压器关了 → 1.2V 域没了 → SRAM 全丢) 唯一的唤醒方式:WKUP 引脚、RTC 闹钟、复位 醒了 = 重新开店(程序从 main 重新开始!跟复位一样)
低功耗 Tickless 就是用停止/待机模式:FreeRTOS 没有任务要跑的时候,不跑 SysTick 空转,直接把芯片塞进低功耗模式,RTC 闹钟到了再叫醒。
4:正点例程采用睡眠模式
睡眠模式:CPU 停了但外设和 SysTick 还活着——一个定时器中断立马能叫醒,程序原地继续,什么状态都不丢。实验做起来简单,改个配置就行。
停止/待机模式:醒了程序要从头跑或者重新初始化所有外设——FreeRTOS 状态全丢,恢复起来复杂度高一截。教学中一般不碰这两个。
正点的 Tickless 实验就是开了configUSE_TICKLESS_IDLE,Idle 任务在没有 Run 的时候不空转 SysTick,而是调WFI让 CPU 睡觉。SysTick 到点或者外部中断来了自动醒。代码很简单——改几个宏,配一个恢复函数。
二:实验分析
1:正点实验内容
代码完全复用二值信号量实验,只是多开了configUSE_TICKLESS_IDLE = 1和两个钩子:
// 进 WFI 前关掉不用 GPIO 的时钟,省电 void PRE_SLEEP_PROCESSING(void) { __HAL_RCC_GPIOA_CLK_DISABLE(); // 全关 __HAL_RCC_GPIOB_CLK_DISABLE(); ... } // WFI 醒来重新打开 void POST_SLEEP_PROCESSING(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); // 全开 ... }运行时流程:
task1 扫键(10ms 醒一次) task2 死等信号量(永远睡着) 没有任务要跑时 → Idle 跑 → 算下次谁要醒 → WFI 睡觉 → 省电 ↓ 到点了或中断来了 POST_SLEEP_PROCESSING → 恢复 GPIO 时钟 → 调度器继续跑
2:用到的几个宏
| 宏 | 作用 | 不改行不行 |
|---|---|---|
configUSE_TICKLESS_IDLE = 1 | 总开关 | 必须改 |
configEXPECTED_IDLE_TIME_BEFORE_SLEEP | 空闲时间 < 这个值就不进 WFI(防止刚睡就被叫醒,进出开销比省的电还大) | ✅ 默认 2 tick,不改也行 |
configPRE_SLEEP_PROCESSING | 进 WFI 前要执行的操作 | ✅ 默认空,我们没在宏里写,直接用弱函数PRE_SLEEP_PROCESSING()替代了 |
configPOST_SLEEP_PROCESSING | 出 WFI 后要执行的操作 | ✅ 同上 |
正点 PPT 把四个宏全介绍了是讲原理——告诉你 Tickless 有这些可配置的点。实际动手时configUSE_TICKLESS_IDLE = 1一开,另外三个要么有默认值、要么我们已经用弱函数实现了,不需要额外改。
三:实验改动
rtos\22\ 改动清单
freertos_demo.c
| 行号 | 内容 |
|---|---|
| 54-82 | PRE_SLEEP_PROCESSING+POST_SLEEP_PROCESSING— 睡前关 7 个 GPIO 时钟,醒后开回来 |
| 102-137 | 二值信号量 + 两个任务(复用 rtos\11\ 逻辑) |
FreeRTOSConfig.h
| 行号 | 内容 |
|---|---|
| 46 | configUSE_TICKLESS_IDLE0→1 |
main.c
| 行号 | 内容 |
|---|---|
| 26 | printf 标题 →"FreeRTOS Tickless Test!" |
Tickless 没有用户 API——只改一个宏 + 两个钩子,业务代码完全不变。可以编译烧录了。
钩子函数:
void PRE_SLEEP_PROCESSING(void) // 钩子1:睡前被 FreeRTOS 调用 → 关 GPIO 时钟 { __HAL_RCC_GPIOA_CLK_DISABLE(); ... } void POST_SLEEP_PROCESSING(void) // 钩子2:醒后被 FreeRTOS 调用 → 开回 GPIO 时钟 { __HAL_RCC_GPIOA_CLK_ENABLE(); ... }钩子 = FreeRTOS 在 Idle 任务里 WFI 的前后给你留的空位。你不写这两个函数 → WFI 照常工作,但 GPIO 时钟还开着 → 省电效果打折扣。你写了 → 睡前关掉、醒后开回 → 省更多电。
运行逻辑总结
正常运行时: task1:每 10ms 醒一次扫按键 task2:死等信号量,阻塞 没有任务跑的时候: CPU 不是傻等 SysTick → 是进 Idle 任务 → 调 PRE_SLEEP → WFI 睡觉 → 等中断叫醒
你什么键都不按的时候,系统大部分时间在 WFI 里睡觉,电流比普通 RTOS 低很多。正点的实验逻辑就是这么简单——用之前的二值信号量实验直接套上 Tickless 的开关。
实验现象就是原例程输出给你对比的:
二值信号量创建成功!! 信号量释放成功!! 获取信号量成功 ← 按 KEY0 触发一次 信号量释放成功!! 获取信号量成功 ← 按 KEY1 触发一次
功能一模一样,只是不按键的时候 CPU 在睡觉省电。