前两篇我们解决了“硬件怎么省电”的问题,现在压力给到了软件这边。
引入 RTOS(实时操作系统)是现代嵌入式开发的标配,但对于低功耗设计来说,传统的 RTOS 简直是一个**“恶魔”。因为它有一个必须存在的心跳机制 (Tick)**。
这就好比你只想好好睡一觉,但你的管家(RTOS)每隔 1 毫秒就跑过来摇醒你,问一句:“老板,有事要做吗?”如果没事,他又让你接着睡。哪怕你就在这种“睡1毫秒-醒10微秒”的折磨中度过,电量也会被这个管家败光。
这一篇,我们要控制这个“管家”,引入 Tickless 模式,并探讨在深度休眠下,软件如何保存记忆(Context)。
一、 传统 SysTick:低功耗的头号公敌
在 FreeRTOS 或 uCOS 中,为了维持任务调度和时间片轮转,通常会配置一个硬件定时器(SysTick),频率通常是 1000Hz(1ms 一次)。
-
物理代价:
-
频繁唤醒: 每秒 1000 次唤醒。
-
无效能耗: 每次唤醒包含:
Flash 唤醒延迟+ISR 入栈出栈+调度器检查+重新入睡。这些动作消耗的能量,往往比 CPU 真正干活的能量还多。 -
阻碍深睡: 因为间隔只有 1ms,MCU 根本来不及进入深度的 Stop 模式(因为进入和退出的时间成本太高),只能停留在浅睡眠(Sleep Mode),此时高频时钟还在跑,功耗高达 mA 级别。
-
二、 救世主:Tickless Idle 模式
核心思想: “除非有事(任务到期或中断触发),否则别叫我。”
1. 工作原理
当 OS 发现当前没有就绪任务(Idle Task 正在运行)时,它会查看下一个最近的任务还需要多久才执行。
-
场景: 现在的任务列表全是
vTaskDelay,最早的一个也要 200ms 后才醒。 -
操作:
-
关闭 SysTick 中断(管家闭嘴)。
-
计算出需要休眠的时间:200ms。
-
设置一个低功耗定时器 (LPTIM/RTC),定在 200ms 后响。
-
CPU 进入深度休眠 (WFI -> Stop Mode)。
-
唤醒后: 读取低功耗定时器走了多久,补偿 (Compensate) OS 的系统 tick 计数器,让系统觉得仿佛那 200ms 只是瞬间过去的。
-
三、 实战:STM32G0 的 Tickless 痛点
在 STM32G0 上实现 FreeRTOS 的 Tickless 并不像开启一个宏 configUSE_TICKLESS_IDLE 那么简单,这里有两个深坑。
1. SysTick 在 Stop 模式下会“死”
FreeRTOS 的默认 Tickless 实现是基于 SysTick 的。但是,STM32G0 进入 Stop 模式后,HCLK(高速时钟)会停止,SysTick 也就停了。
-
后果: 你的 CPU 睡死过去了,没人叫醒它(除非有外部中断)。
-
解决方案:重写 vPortSuppressTicksAndSleep
必须抛弃 SysTick,改用 LPTIM (Low Power Timer) 或 RTC Wakeup Timer。
-
LPTIM 优势: 可以使用 LSE (32.768kHz) 或 LSI 作为时钟源,在 Stop 模式下继续计数。
-
STM32G0 特性: LPTIM 支持异步计数,哪怕没有 APB 总线时钟也能工作,唤醒后再同步。
-
2. 时钟补偿的精度 (Drift)
当你用 LPTIM 唤醒后,你需要告诉 OS:“刚才过去了 200 个 tick”。
但 LSE 的频率是 32.768kHz,而 SysTick 可能是 64MHz 分频来的。两者之间存在偏差。
-
算法挑战: 必须处理好整数换算和余数。如果长期运行,微小的误差累积会导致系统时间变慢(Drift)。
四、 实战:RL78 的“傻瓜式” Tickless
相比于 STM32 需要自己写驱动去替换 SysTick,RL78 在硬件架构上对 Tickless 更友好。
1. Interval Timer (IT) 的天然优势
RL78 通常直接就用 Interval Timer (IT) 来做系统 Tick,而不是像 ARM 那样有一个内核级的 SysTick。
-
无缝衔接: IT 挂在 fIL (15kHz) 上。无论 CPU 是全速跑 (Run) 还是深度睡 (Stop),IT 的时钟源从未改变。
-
实现:
-
OS 发现要睡 200ms。
-
直接修改 IT 的比较寄存器,从 1ms (常规 tick) 改为 200ms 的计数值。
-
执行
STOP指令。 -
唤醒后,把寄存器改回 1ms。
-
-
优势: 不需要像 STM32 那样在两个定时器(SysTick 和 LPTIM)之间倒腾和换算,逻辑极简。
五、 深度进阶:上下文保持 (Context Restore)
如果为了极致省电,我们进入了 Standby (STM32) 模式,事情就变得复杂了。
1. 问题的本质:失忆
-
Stop 模式 (G0/RL78): RAM 数据保持不动,寄存器状态保持。唤醒 = 继续执行。这是做 Tickless 的首选。
-
Standby 模式 (G0): 只有 Backup Registers (几十个字节) 和 部分 SRAM (若配置保持) 活着。CPU 寄存器全部丢失。唤醒 = 复位 (Reset)。
2. 如何在 Standby 下“伪装”成没关机?
如果你的产品要在电池下跑 10 年,Stop 模式的 3µA 可能还是太高,你需要 Standby (0.1µA)。这时候 RTOS 怎么跑?
-
技术路线:
-
入睡前 (Pre-Sleep): 将 RTOS 的关键数据(如
xTickCount,下一个任务的指针,堆栈指针 SP)保存到 Backup SRAM 或 Backup Registers 中。设置 RTC 闹钟。 -
唤醒复位 (Reset_Handler):
-
芯片复位第一件事,检查
PWR->SR寄存器的SBF(Standby Flag) 标志位。 -
如果是冷启动: 跑正常的
main()初始化。 -
如果是唤醒: 跳过 C 库初始化(不要把 RAM 清零!),直接从 Backup 区域恢复 SP 指针,跳转回调度器。
-
-
-
难度: 极高。需要精通汇编启动文件 (.s)。通常不建议在跑复杂 RTOS 时使用 Standby,除非你的应用非常简单。
六、 总结:软件策略的选择
| 场景 | 传统 SysTick | Tickless (Stop模式) | Tickless (Standby模式) |
| 平均电流 | mA 级 | µA 级 | nA 级 |
| 响应速度 | 快 | 中 (有唤醒延迟) | 慢 (相当于重启) |
| RAM 数据 | 保持 | 保持 | 丢失 (需手动备份) |
| STM32G0 实现 | 默认 | 需改写 vPortSuppress 用 LPTIM | 极难,需改启动文件 |
| RL78/G23 实现 | 默认 | 简单,直接改 Interval Timer | 简单 (RAM 默认保持) |
推荐策略:
-
对于 STM32G0:使用 Tickless + Stop Mode 1。利用 LPTIM 唤醒。这是开发难度与功耗的最佳平衡点。
-
对于 RL78/G23:使用 Tickless + Stop Mode。得益于 RL78 在 Stop 模式下全 RAM 保持且功耗极低,通常不需要考虑更复杂的“掉电恢复”逻辑。
/******************************************
* 本文为作者《嵌入式开发基础与工程实践》系列文章之一。
* 关注即可订阅后续内容更新,翻阅往期信息,采用异步推送机制,无需主动轮询。
* 转发本文可视为一次网络广播,有助于更多节点接收该信息。
*****************************************/
2176

被折叠的 条评论
为什么被折叠?



