设备电源管理API:从枚举到约束的实战解析
设备电源管理,说白了就是让每个外设在合适的时候睡大觉,该干活时立刻醒。Zephyr 的 PM 框架给了我们一套非常完整的 API,今天我就带你把这套东西吃透。
PM_DEVICE_ACTION 枚举:设备能干什么
先看这个枚举,它定义了设备可以执行的电源操作。我刚开始接触时觉得不就几个状态吗?后来发现这里面的门道不少。
enum pm_device_action {
PM_DEVICE_ACTION_RESUME, // 恢复运行
PM_DEVICE_ACTION_SUSPEND, // 进入休眠
PM_DEVICE_ACTION_TURN_ON, // 上电
PM_DEVICE_ACTION_TURN_OFF, // 断电
PM_DEVICE_ACTION_SUSPEND_RAM, // 保留RAM的休眠
};
每个动作对应一个具体的电源状态转换。举个例子,PM_DEVICE_ACTION_SUSPEND 不是直接把设备掐断,而是让驱动自己决定怎么省电。我在一个 BLE 项目中就踩过坑——直接断电导致蓝牙连接丢失,后来改用 SUSPEND_RAM 才保住连接状态。
| 动作 | 典型场景 | 功耗影响 |
|---|---|---|
| RESUME | 从休眠唤醒 | 恢复正常功耗 |
| SUSPEND | 空闲时待机 | 降低至待机功耗 |
| TURN_OFF | 长时间不用 | 接近零功耗 |
| SUSPEND_RAM | 需要快速恢复 | 保留RAM供电 |
设备电源管理回调函数
每个设备驱动都要注册一个回调函数,Zephyr 通过它来通知设备切换电源状态。这个回调的签名很简单:
typedef int (*pm_device_cb_t)(const struct device *dev,
enum pm_device_action action);
驱动里怎么写?我给你看个实际例子:
static int my_uart_pm_callback(const struct device *dev,
enum pm_device_action action)
{
switch (action) {
case PM_DEVICE_ACTION_RESUME:
// 恢复时钟、重新配置寄存器
uart_clock_enable(dev);
uart_reg_restore(dev);
break;
case PM_DEVICE_ACTION_SUSPEND:
// 保存寄存器、关闭时钟
uart_reg_save(dev);
uart_clock_disable(dev);
break;
case PM_DEVICE_ACTION_TURN_OFF:
// 彻底断电
uart_power_off(dev);
break;
default:
return -ENOTSUP;
}
return 0;
}
我个人习惯在 SUSPEND 里只做最必要的保存,别一股脑全存。有一次我保存了所有寄存器,结果唤醒时间从 200us 飙到 2ms,功耗反而高了。
运行时动态电源管理
Zephyr 的运行时 PM 是自动的。系统会跟踪每个设备的使用计数,当计数降到零时自动挂起。你不需要手动调用 suspend/resume,框架帮你搞定。
怎么用?三步走:
- 在设备树里给设备加上
power-domain属性 - 驱动里实现
pm_device_cb_t回调 - 应用层调用
pm_device_get()和pm_device_put()
// 使用前获取设备
pm_device_get(dev);
// 使用设备...
uart_send(dev, data, len);
// 使用完释放
pm_device_put(dev);
你想想看,这样多省心。系统会在 pm_device_put 后自动判断是否要挂起。不过要注意,频繁的 get/put 会有开销,我建议在批量操作前后调用,而不是每发一个字节就搞一次。
小技巧: 用
pm_device_state_get() 查看设备当前状态,调试时特别有用。我曾经靠这个发现一个 SPI 设备一直卡在 ACTIVE 状态,原来是驱动里忘了释放。
设备约束:别让系统睡过头
设备约束是个好东西。有时候你希望某个设备保持唤醒,即使它暂时没被使用。比如音频播放时,DMA 控制器不能睡,否则声音会断。
// 设置约束,阻止设备进入低功耗
pm_device_constraint_set(dev, true);
// 解除约束
pm_device_constraint_set(dev, false);
约束的典型应用场景:
- 实时性要求高:比如电机控制,唤醒延迟不能接受
- 周期性任务:ADC 采样间隔很短,频繁 suspend/resume 不划算
- 外设依赖链:I2C 总线上的传感器,总线本身不能先睡
注意: 约束用多了会抵消省电效果。我曾经在一个项目里给所有外设都加了约束,结果系统根本进不了深度睡眠,功耗比不优化还高。约束要精准,只给真正需要的设备加。
知识结构总览
下面这张图把今天讲的内容串起来了,你可以看到从枚举到约束的完整链路:
避坑指南
最后分享几个我实际项目中遇到的坑:
- 回调里别做耗时操作:PM 回调是在中断上下文或关键路径上调用的,我曾经在回调里做了 flash 写入,结果系统直接死锁。
- 约束要成对使用:set 和 clear 必须匹配,否则设备永远醒着。我习惯在代码里用
__ASSERT检查约束计数。 - 测试唤醒时间:不同设备的唤醒时间差异很大,UART 可能 100us,WiFi 模块可能要 10ms。设计电源策略时一定要实测。
核心要点: PM_DEVICE_ACTION 是命令,回调是执行者,运行时 PM 是调度器,约束是保险丝。四者配合才能实现真正的低功耗。