4、设备电源管理:设备运行时PM、设备自动PM、设备强制PM
好,咱们接着聊设备电源管理。这部分内容,说白了就是怎么让单个设备在系统里「省着点用电」。Zephyr 在这方面给了我们三种手段:运行时 PM、自动 PM 和强制 PM。我刚开始接触时也觉得有点绕,但用熟了就会发现,它们各有各的用武之地。
4.1 设备运行时 PM(Runtime PM)
运行时 PM,是我个人用得最多的一种方式。它的核心思想是:设备不用的时候就挂起,要用的时候再唤醒。你想想看,一个传感器可能每秒只采样一次,剩下 999 毫秒都在闲着。如果这期间还给它供电,那纯粹是浪费。
在 Zephyr 里,运行时 PM 需要设备驱动主动配合。驱动里要实现两个回调函数:pm_control 和 pm_device_action。前者负责处理电源状态切换,后者负责执行具体的挂起或恢复动作。
我举个例子,一个简单的 UART 驱动,运行时 PM 的实现大概长这样:
static int uart_pm_control(const struct device *dev,
enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
/* 关闭时钟,保存寄存器状态 */
uart_disable_clocks(dev);
break;
case PM_DEVICE_ACTION_RESUME:
/* 恢复时钟,重新配置寄存器 */
uart_enable_clocks(dev);
uart_restore_regs(dev);
break;
default:
return -ENOTSUP;
}
return 0;
}
嗯,这里要注意:运行时 PM 的挂起和恢复,是由设备的使用计数触发的。当设备被打开时,计数加一;关闭时,计数减一。计数归零后,系统就会自动调用挂起函数。我在项目中遇到过一个问题:某个驱动忘记在 close 时释放设备,导致计数永远不为零,设备一直醒着。排查了半天才发现是引用计数没处理好。
pm_device_dump() 打印所有设备的状态。看看哪些设备该睡没睡,一目了然。
4.2 设备自动 PM(Automatic PM)
自动 PM 听起来很智能,对吧?其实它就是在运行时 PM 的基础上,加了一层「自动决策」的逻辑。系统会监控设备的使用情况,如果发现某个设备长时间空闲,就自动把它挂起。
Zephyr 的自动 PM 是通过一个内核线程实现的。这个线程会定期检查所有注册了自动 PM 的设备,看看它们是否满足挂起条件。默认的检查周期是 100 毫秒,你可以通过 CONFIG_PM_DEVICE_AUTO_SLEEP_TICK 调整。
我个人习惯把自动 PM 用在那些「间歇性工作」的设备上。比如一个温度传感器,每 5 秒采集一次数据。如果用手动控制,我得在每次采集前后手动挂起和恢复,太麻烦了。交给自动 PM,它自己会判断空闲时间,然后自动挂起。
但自动 PM 也不是万能的。我曾经踩过一个坑:某个 SPI 闪存芯片,挂起后重新唤醒需要 10 毫秒。自动 PM 觉得它空闲了 200 毫秒,就把它挂了。结果下次读写时,唤醒时间比实际工作还长,反而更耗电。所以,自动 PM 适合那些唤醒延迟很短的设备。
4.3 设备强制 PM(Forced PM)
强制 PM,说白了就是「我不管你在干什么,现在立刻给我睡」。这通常用在系统要进入深度睡眠时,或者某个紧急情况下需要快速关闭外设。
在 Zephyr 里,强制 PM 通过 pm_device_force_power_state() 函数实现。调用这个函数后,设备会立即进入指定的电源状态,不管它当前是否在使用中。
我举个例子,系统要进入 Suspend-to-RAM 模式时,需要把所有外设都关掉。这时候用强制 PM 最合适:
void system_suspend_handler(void)
{
const struct device *devs[] = {
DEVICE_DT_GET(DT_NODELABEL(uart0)),
DEVICE_DT_GET(DT_NODELABEL(spi1)),
DEVICE_DT_GET(DT_NODELABEL(i2c2)),
};
for (int i = 0; i < ARRAY_SIZE(devs); i++) {
pm_device_force_power_state(devs[i], PM_DEVICE_STATE_SUSPEND);
}
}
强制 PM 有个风险:如果设备正在传输数据,强行挂起可能导致数据丢失。我在项目中遇到过这种情况:强制挂起一个正在写数据的 eMMC,结果文件系统损坏了。后来我加了一个检查,确保设备空闲了再强制挂起。
4.4 三种方式的对比与选择
说了这么多,你可能想问:到底该用哪种?我整理了一个表格,方便你对比:
| 特性 | 运行时 PM | 自动 PM | 强制 PM |
|---|---|---|---|
| 控制粒度 | 精细(驱动层控制) | 中等(系统层决策) | 粗糙(强制切换) |
| 使用场景 | 设备使用频率可预测 | 设备间歇性工作 | 系统深度睡眠或紧急关闭 |
| 唤醒延迟 | 低(驱动优化) | 中等(需检查空闲时间) | 高(可能丢失数据) |
| 实现复杂度 | 高(需修改驱动) | 低(配置即可) | 低(直接调用 API) |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
我个人建议:优先用运行时 PM,因为它最灵活,也最省电。如果设备的工作模式比较固定,可以考虑自动 PM。强制 PM 嘛,留着当「核武器」用就行,别动不动就掏出来。
好了,设备电源管理这部分就聊到这儿。记住,电源管理不是「越省电越好」,而是「在满足功能的前提下,尽量省电」。下一章咱们聊聊系统级的电源管理策略,那个更有意思。