手把手教你为嵌入式设备编写一个简单的电池驱动(基于Linux Power Supply框架)
当你拿到一款新的嵌入式设备,尤其是带有电池的便携式产品时,如何快速为其开发一个可靠的电池状态监控驱动?Linux内核提供的Power Supply子系统正是为此而生。不同于传统的字符设备或块设备驱动,电源管理驱动有其独特的架构和设计哲学。
1. 开发环境准备与基础概念
在开始编写驱动之前,我们需要先搭建好开发环境并理解几个核心概念。嵌入式Linux驱动开发通常需要:
- 目标设备的交叉编译工具链
- 与设备内核版本匹配的内核源码树
- 电池管理IC的数据手册
- 基本的硬件调试工具(如逻辑分析仪、万用表等)
Power Supply子系统的核心思想是将各种电源设备(电池、USB充电器、AC适配器等)抽象为统一的软件接口。每个电源设备在内核中表现为一个power_supply结构体实例,通过sysfs向用户空间暴露标准化的属性接口。
典型的电池驱动需要关注以下属性:
enum power_supply_property { POWER_SUPPLY_PROP_STATUS, // 充电状态 POWER_SUPPLY_PROP_CAPACITY, // 剩余电量百分比 POWER_SUPPLY_PROP_VOLTAGE_NOW, // 当前电压 POWER_SUPPLY_PROP_TEMP, // 电池温度 // ...其他属性根据硬件支持情况添加 };提示:在开始编码前,务必仔细阅读电池管理IC的数据手册,确定其支持的通信接口(I2C/SPI/SMBus等)和寄存器映射。
2. 驱动框架搭建与初始化
创建一个基础的Power Supply驱动需要遵循Linux设备驱动的标准模式。我们以Platform Driver为例:
#include <linux/power_supply.h> #include <linux/platform_device.h> struct my_battery { struct power_supply *psy; struct i2c_client *client; // 其他设备特定数据 }; static int my_battery_probe(struct platform_device *pdev) { struct my_battery *battery; struct power_supply_config psy_cfg = {}; // 分配设备结构体 battery = devm_kzalloc(&pdev->dev, sizeof(*battery), GFP_KERNEL); if (!battery) return -ENOMEM; // 初始化power_supply描述 battery->psy_desc.name = "battery"; battery->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; battery->psy_desc.properties = my_battery_props; battery->psy_desc.num_properties = ARRAY_SIZE(my_battery_props); battery->psy_desc.get_property = my_battery_get_property; // 注册power_supply设备 psy_cfg.drv_data = battery; battery->psy = devm_power_supply_register(&pdev->dev, &battery->psy_desc, &psy_cfg); if (IS_ERR(battery->psy)) return PTR_ERR(battery->psy); // 初始化硬件接口(如I2C) battery->client = i2c_get_client_device(...); platform_set_drvdata(pdev, battery); return 0; } static const struct of_device_id my_battery_of_match[] = { { .compatible = "vendor,battery-ic" }, {}, }; MODULE_DEVICE_TABLE(of, my_battery_of_match); static struct platform_driver my_battery_driver = { .driver = { .name = "my-battery", .of_match_table = my_battery_of_match, }, .probe = my_battery_probe, }; module_platform_driver(my_battery_driver);关键点解析:
power_supply_desc结构体定义了设备的基本属性和回调函数devm_power_supply_register是资源管理版本的注册函数- 设备树兼容性字符串应与硬件匹配
3. 属性获取与硬件交互
驱动最核心的部分是实现属性获取回调函数,这需要与硬件实际交互:
static enum power_supply_property my_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, }; static int my_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct my_battery *battery = power_supply_get_drvdata(psy); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = read_battery_status(battery); break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = read_battery_soc(battery); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = read_battery_voltage(battery); break; // 其他属性处理... default: ret = -EINVAL; break; } return ret; }硬件访问示例(假设使用I2C接口):
static int read_battery_voltage(struct my_battery *battery) { u8 reg = VOLTAGE_REGISTER; u16 raw_value; // 读取电压寄存器 int ret = i2c_smbus_read_word_data(battery->client, reg); if (ret < 0) return ret; raw_value = (u16)ret; // 根据数据手册转换原始值为微伏 return (raw_value * 1000) / 2; // 示例转换公式 }注意:实际硬件寄存器地址和转换公式需参考具体芯片手册。某些高级电池管理IC可能还需要解锁寄存器访问等额外步骤。
4. 状态监控与事件通知
电池状态需要持续监控,常见实现方式有两种:
- 中断驱动:利用硬件提供的中断引脚(如低电量警报)
- 轮询方式:定时器定期检查状态变化
中断方式实现示例:
static irqreturn_t battery_irq_handler(int irq, void *dev_id) { struct my_battery *battery = dev_id; // 读取中断状态寄存器确定事件类型 u8 status = i2c_smbus_read_byte_data(battery->client, STATUS_REG); if (status & LOW_BATTERY_BIT) { pr_info("Low battery warning!\n"); } // 通知power supply子系统状态变化 power_supply_changed(battery->psy); return IRQ_HANDLED; }轮询方式实现示例:
static void battery_monitor_work(struct work_struct *work) { struct my_battery *battery = container_of(work, struct my_battery, monitor_work.work); // 检查状态变化 int old_soc = battery->last_soc; int new_soc = read_battery_soc(battery); if (old_soc != new_soc) { battery->last_soc = new_soc; power_supply_changed(battery->psy); } // 重新调度工作队列(例如每30秒检查一次) schedule_delayed_work(&battery->monitor_work, msecs_to_jiffies(30000)); }调试技巧:
通过sysfs查看驱动状态:
cat /sys/class/power_supply/battery/status cat /sys/class/power_supply/battery/capacity使用内核日志观察驱动行为:
dmesg | grep battery模拟状态变化测试驱动响应:
echo 50 > /sys/class/power_supply/battery/capacity
5. 高级功能与优化
基础功能实现后,可以考虑添加以下增强功能:
温度补偿:
static int get_compensated_capacity(struct my_battery *battery, int raw_soc) { int temp = read_battery_temp(battery); // 简单的线性温度补偿 if (temp < 10) return raw_soc - (10 - temp); else if (temp > 45) return raw_soc - (temp - 45); return raw_soc; }充电曲线优化:
static int calculate_time_to_full(struct my_battery *battery) { int current_ma = read_battery_current(battery); int capacity_mah = battery->design_capacity; int remaining_mah = (capacity_mah * battery->soc) / 100; if (current_ma <= 0) // 不在充电状态 return -1; return (capacity_mah - remaining_mah) * 3600 / current_ma; }电源路径管理(适用于同时有电池和外部电源的设备):
static void handle_power_source_change(struct my_battery *battery) { if (is_external_power_connected()) { // 切换到外部电源供电 set_charge_current(MAX_CHARGE_CURRENT); } else { // 切换到电池供电 set_charge_current(0); limit_power_consumption(); } power_supply_changed(battery->psy); }常见问题排查表:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| sysfs属性不显示 | 属性未在驱动中定义 | 检查power_supply_property数组 |
| 电量显示不更新 | 未调用power_supply_changed | 在状态变化时触发通知 |
| 读取值不正确 | 寄存器解析错误 | 验证数据手册和转换公式 |
| 驱动加载失败 | 设备树配置错误 | 检查compatible字符串和资源定义 |
6. 设备树配置与内核集成
要让驱动正常工作,通常需要配置设备树:
battery: battery@55 { compatible = "vendor,battery-ic"; reg = <0x55>; interrupt-parent = <&gpio>; interrupts = <17 IRQ_TYPE_EDGE_FALLING>; monitored-battery = <&main_battery>; // 电池参数 voltage-min-design-microvolt = <3000000>; voltage-max-design-microvolt = <4200000>; energy-full-design-microwatt-hours = <10000000>; };内核配置选项:
CONFIG_POWER_SUPPLY=y CONFIG_BATTERY_MY_VENDOR=y # 你的驱动配置项在驱动中解析设备树参数:
static int parse_dt(struct device *dev, struct my_battery *battery) { struct device_node *np = dev->of_node; if (!np) return -ENODEV; of_property_read_u32(np, "voltage-min-design-microvolt", &battery->min_voltage); of_property_read_u32(np, "voltage-max-design-microvolt", &battery->max_voltage); // 其他参数解析... return 0; }7. 用户空间交互与系统集成
完整的电池解决方案还需要用户空间配合:
uevent通知: 当驱动调用power_supply_changed()时,内核会发送uevent,用户空间服务(如upower)可以监听并处理这些事件。
典型的uevent内容:
POWER_SUPPLY_NAME=battery POWER_SUPPLY_STATUS=Charging POWER_SUPPLY_CAPACITY=85 POWER_SUPPLY_VOLTAGE_NOW=4200000Android电源管理集成: Android系统通过BatteryService监听这些属性变化,更新系统状态并触发相关广播。
低电量处理策略:
static void check_low_battery(struct my_battery *battery) { if (battery->soc < LOW_BATTERY_THRESHOLD) { // 触发低电量通知 battery->psy_desc.set_property = my_battery_set_property; power_supply_changed(battery->psy); // 可以在这里限制设备性能以延长续航 if (battery->soc < CRITICAL_BATTERY_THRESHOLD) { initiate_graceful_shutdown(); } } }在实际项目中,电池驱动的稳定性至关重要。建议添加充分的错误处理和恢复机制,比如I2C通信失败时的重试逻辑,以及无效寄存器值的过滤处理。