返回首页

Device PM 设备电源管理学习笔记

本笔记学习 Zephyr 的设备级电源管理(Device Power Management),实现外设的动态电源控制。


示例路径

zephyr/samples/subsys/pm/device_pm/ - 设备级电源管理示例

核心概念

设备电源管理 vs 系统电源管理

类型作用范围控制方式
System PM整个系统系统状态转换
Device PM单个外设使用计数 + 运行时控制

设备电源状态

状态说明功耗特征
PM_DEVICE_STATE_ACTIVE正常运行相对较高
PM_DEVICE_STATE_SUSPENDED挂起相对较低
PM_DEVICE_STATE_OFF关闭取决于具体硬件实现

设备驱动中的 PM 实现

1. 定义 PM 回调函数

#include <zephyr/pm/device.h>

static int my_device_pm_action(const struct device *dev,
                               enum pm_device_action action)
{
    switch (action) {
    case PM_DEVICE_ACTION_RESUME:
        printk("设备恢复: %s\n", dev->name);
        /* 恢复外设状态,如重新初始化寄存器 */
        break;
    case PM_DEVICE_ACTION_SUSPEND:
        printk("设备挂起: %s\n", dev->name);
        /* 保存外设状态,如保存寄存器值 */
        break;
    case PM_DEVICE_ACTION_TURN_ON:
        printk("设备上电: %s\n", dev->name);
        break;
    case PM_DEVICE_ACTION_TURN_OFF:
        printk("设备断电: %s\n", dev->name);
        break;
    default:
        return -ENOTSUP;
    }
    return 0;
}

2. 注册 PM 回调

PM_DEVICE_DEFINE(my_driver, my_device_pm_action);

DEVICE_DEFINE(my_driver, "my_driver", &my_init,
              PM_DEVICE_GET(my_driver), NULL, NULL,
              POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &api);

3. 驱动初始化中启用运行时 PM

static int my_init(const struct device *dev)
{
    /* 初始化外设硬件... */
    
    /* 启用设备运行时电源管理 */
    return pm_device_runtime_enable(dev);
}

应用层使用 Device PM

1. 获取设备实例

const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(my_device));
if (!device_is_ready(dev)) {
    printk("设备未就绪\n");
    return -ENODEV;
}

2. 使用设备前获取

/* 设备自动恢复(如果之前被挂起) */
int ret = pm_device_runtime_get(dev);
if (ret < 0) {
    printk("获取设备失败: %d\n", ret);
    return ret;
}

/* 现在可以正常使用设备 */

3. 使用设备后释放

/* 设备可能被自动挂起 */
int ret = pm_device_runtime_put(dev);
if (ret == 1) {
    printk("异步挂起请求已排队\n");
}

4. 异步释放(推荐)

/* 非阻塞释放,允许系统在其他线程处理挂起 */
pm_device_runtime_put_async(dev);

完整示例:GPIO 设备电源管理

驱动代码 (my_gpio.c)

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/drivers/gpio.h>

static int my_gpio_pm_action(const struct device *dev,
                             enum pm_device_action action)
{
    switch (action) {
    case PM_DEVICE_ACTION_RESUME:
        printk("GPIO 恢复\n");
        /* 恢复 GPIO 配置 */
        break;
    case PM_DEVICE_ACTION_SUSPEND:
        printk("GPIO 挂起\n");
        /* 保存 GPIO 状态 */
        break;
    default:
        return -ENOTSUP;
    }
    return 0;
}

PM_DEVICE_DEFINE(my_gpio, my_gpio_pm_action);

static int my_gpio_init(const struct device *dev)
{
    return pm_device_runtime_enable(dev);
}

DEVICE_DEFINE(my_gpio, "my_gpio", my_gpio_init,
              PM_DEVICE_GET(my_gpio), NULL, NULL,
              POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL);

应用代码 (main.c)

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/drivers/gpio.h>

#define MY_GPIO DT_NODELABEL(gpio0)

int main(void)
{
    const struct device *gpio = DEVICE_DT_GET(MY_GPIO);
    
    if (!device_is_ready(gpio)) {
        printk("GPIO 未就绪\n");
        return -ENODEV;
    }
    
    /* 使用 GPIO 前获取 */
    pm_device_runtime_get(gpio);
    
    /* 配置 LED 引脚 */
    const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
    gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    
    /* 闪烁 LED */
    gpio_pin_set(gpio, led.pin, 1);
    k_msleep(100);
    gpio_pin_set(gpio, led.pin, 0);
    
    /* 使用完毕后释放(可能触发挂起) */
    pm_device_runtime_put(gpio);
    
    printk("完成\n");
    return 0;
}

设备依赖管理

设备可以声明依赖关系,确保父设备先于子设备恢复。

子设备示例

static int child_open(const struct device *dev)
{
    const struct device *parent;
    int ret;
    
    /* 确保父设备已恢复 */
    ret = pm_device_runtime_get(parent);
    if (ret < 0) return ret;
    
    /* 获取自身 */
    ret = pm_device_runtime_get(dev);
    if (ret < 0) {
        pm_device_runtime_put(parent);
        return ret;
    }
    
    return 0;
}

static int child_close(const struct device *dev)
{
    /* 释放自身 */
    pm_device_runtime_put(dev);
    
    /* 释放父设备 */
    pm_device_runtime_put(parent);
    return 0;
}

配置选项

# prj.conf
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_PM_DEVICE_RUNTIME=y

可选配置

# 启用设备运行时自动启用
CONFIG_PM_DEVICE_RUNTIME_AUTO=y

Devicetree 配置

/* 启用设备运行时 PM */
&gpio0 {
    status = "okay";
    zephyr,pm-device-runtime-auto;
};

nRF 设备电源特性

nRF54L15 外设电源控制

是否具备独立电源域、自动 runtime PM,以及具体挂起行为,要以当前 SoC 文档、外设驱动实现和设备树配置为准。

低功耗技巧

  1. 及时释放: 使用完外设后立即调用 pm_device_runtime_put()
  2. 异步释放: 使用 pm_device_runtime_put_async() 避免阻塞
  3. 依赖管理: 确保子设备正确管理父设备依赖

应用场景

  1. 传感器数据采集: 仅在采集时唤醒传感器
  2. 无线通信: 通信完成后挂起 Radio
  3. 显示屏: 静态显示时关闭显示控制器
  4. 存储设备: 写操作完成后关闭 Flash

注意事项

  1. 线程安全: PM 操作应从同一线程调用
  2. 中断处理: ISR 中只能使用非阻塞 API
  3. 状态保存: 挂起前确保保存必要状态
  4. 调试影响: 调试器可能阻止设备挂起

调试技巧

# 查看设备电源状态
# 启用 CONFIG_PM_DEBUG 后可在日志中查看状态转换

相关 API 总结

函数说明
pm_device_runtime_enable()启用运行时 PM
pm_device_runtime_disable()禁用运行时 PM
pm_device_runtime_get()获取设备(恢复)
pm_device_runtime_put()释放设备(挂起)
pm_device_runtime_put_async()异步释放设备
pm_device_state_get()获取设备状态

*小白 🤖 - 2026-03-17*