RK3588平台BQ25703与CW2015电源管理设备树配置实战
1. 项目概述:一块RK3588平板的“心脏”如何被精准唤醒
你手头有一块基于瑞芯微RK3588主控的定制化平板,它不是消费级玩具,而是某工业现场数据采集终端、某教育类交互设备或某医疗辅助显示单元的原型机。它已经能点亮屏幕、跑通Linux内核、加载基础驱动,但一插上电源,要么充不进电,要么电量显示永远停在37%,要么系统在低电量时毫无预警直接关机——这说明它的“能量管理系统”还处于失联状态。而这个系统的核心,就是两颗芯片:BQ25703(TI出品的宽输入范围、支持USB PD和Type-C双路输入的升降压充电管理IC)与CW2015(赛普拉斯/英飞凌的高精度单节锂电电量计,带库仑计+电压法双算法融合)。它们不负责计算,只负责感知;不参与业务逻辑,却决定整机能否可靠运行。本项目标题里的“电源调试”,绝非简单接上线就完事,而是要让Linux内核通过设备树(Device Tree)这张“电子地图”,准确识别这两颗芯片的物理连接方式、电气参数、通信地址与功能边界,最终让power_supply子系统能读取到真实的电压、电流、剩余容量、健康度等关键指标。我做过不下十款基于RK3588的板卡电源适配,从4G模块供电冲突到PD协议握手失败,从CW2015校准数据丢失到BQ25703的OTG模式误触发,踩过的坑足够填满一个小型电池仓。这篇文章不讲抽象理论,只讲你在焊接完PCB、烧录完固件后,打开串口终端,敲下cat /sys/class/power_supply/bq25703-0/voltage_now时,为什么返回的是0,以及你该怎样一步步把它变成一个有实际意义的数字。它适合所有正在RK3588平台上做硬件Bring-up的工程师、嵌入式Linux驱动开发者,以及那些被“电量不准”问题折磨得夜不能寐的系统集成商。
2. 整体设计思路与方案选型逻辑:为什么是BQ25703 + CW2015,而不是其他组合?
2.1 电源管理架构的底层逻辑:谁管“进”,谁管“出”,谁管“算”
在RK3588这类高性能SoC的平板设计中,电源管理从来不是单一芯片的事。它是一个分层协作的体系:
第一层:输入电源路径管理(Input Path Management)
这是BQ25703的核心战场。它位于Type-C接口与电池之间,像一个智能交通指挥中心:当外部5V/9V/12V/15V/20V电源接入时,它决定是直接给系统供电(直通模式),还是先给电池充电(充电模式),或是两者同时进行(旁路+充电模式)。它还能在输入电压跌落时无缝切换至电池供电,保证系统不掉电。选择BQ25703,核心看中三点:一是其支持高达26.4V的宽输入电压范围,完美兼容工业级AC-DC适配器;二是内置的USB PD 3.0协议引擎,能与主流PD充电器完成完整的VDM(Vendor Defined Message)协商;三是其I2C寄存器映射清晰,Linux内核主线已提供成熟驱动(drivers/power/supply/bq25700_charger.c),无需从零造轮子。第二层:电池电量计量(Fuel Gauging)
这是CW2015的专长。BQ25703能告诉你“此刻流进/流出多少电流”,但它无法回答“这块电池到底还有多少电”。这就像知道汽车油泵每秒喷多少油,却不知道油箱里还剩几升。CW2015通过持续监测电池电压、温度,并结合内部存储的电池特性曲线(Q-Max, Ra table),用库仑积分(Coulomb Counting)实时累加充放电电荷量,再辅以电压法(Voltage Method)进行周期性校准,最终输出一个高度可信的SOC(State of Charge)值。选择CW2015,是因为它在单节锂电领域是公认的“精度担当”,其典型误差可控制在±1%以内,且支持通过I2C进行深度配置,包括自定义电池ID、设置充电截止电压、启用/禁用低功耗模式等。第三层:SoC端的协调与呈现(SoC Coordination)
RK3588本身不直接管理电池,它通过I2C总线与BQ25703和CW2015通信,将获取的数据汇总到Linux的power_supply框架中。这个框架会自动创建/sys/class/power_supply/下的设备节点,供用户空间程序(如桌面环境的电量图标、后台监控服务)读取。因此,设备树的使命,就是向内核精确描述:“BQ25703接在哪个I2C总线上?它的I2C地址是多少?它是否启用了OTG功能?CW2015的电池ID是什么?它的满电电压设定为多少?”——每一个参数,都对应着硬件设计图纸上的一个焊点、一根走线、一个电阻值。
提示:切勿将BQ25703与CW2015的功能混淆。曾有同事误以为BQ25703自带高精度电量计,省掉了CW2015,结果在电池老化后,SOC跳变严重,用户反馈“刚充完电显示100%,用5分钟就掉到20%”。这是典型的把“电流控制器”当“电量计算器”用,后果是灾难性的。
2.2 设备树为何是唯一解:绕不开的“硬件描述语言”
有人会问:既然驱动代码都写好了,为什么还要费劲去写设备树?答案是:Linux内核是“通用”的,而你的硬件是“专用”的。内核驱动就像一个万能扳手,它知道怎么拧螺丝,但不知道你的螺丝在哪、是多大号、需要多大力矩。设备树(.dts文件)就是那张精确到毫米的装配图纸。它告诉内核:“BQ25703芯片,型号为bq25703a,物理地址是0x6b,挂在rk3588-i2c@feac0000这条总线上,它的中断引脚(INT#)连接到了GPIO4_A0,它的充电使能引脚(EN_CHG)由GPIO4_B1控制”。没有这张图,内核启动时只会看到一堆未识别的I2C设备,或者更糟——因为地址冲突,把BQ25703的寄存器当成别的芯片来读,导致系统行为完全不可预测。我见过最离谱的案例,是某团队因设备树里把BQ25703的I2C地址错写成0x6a(实际应为0x6b),结果内核在初始化时反复尝试读取一个不存在的设备,耗尽了I2C总线的超时时间,导致整个I2C子系统挂死,连触摸屏都无法工作。所以,设备树不是可选项,而是硬件与软件握手的“宪法”。
2.3 方案选型的硬性约束:RK3588平台的现实限制
在RK3588上做电源调试,有几个无法回避的物理与软件约束,它们直接决定了设备树的写法:
I2C总线资源紧张:RK3588虽然有6组I2C控制器,但其中I2C0通常被HDMI CEC占用,I2C1被红外遥控占用,I2C2常被摄像头模组占用。留给电源管理的,往往只有I2C3(
feac0000.i2c)或I2C4(fead0000.i2c)。这意味着BQ25703和CW2015必须共用同一组I2C总线。而BQ25703的标准地址是0x6b,CW2015的标准地址是0x62,二者不冲突,这是天赐的便利。但如果你的硬件设计为了规避干扰,给CW2015加了一个地址选择电阻,将其地址改为0x63,那么设备树里就必须同步更新,否则CW2015将彻底“隐身”。中断处理的优先级:BQ25703的INT#引脚是它的“报警器”,当发生过压、过流、热关断、充电完成等事件时,它会拉低此引脚通知SoC。这个中断必须被正确配置为
interrupt-parent指向&gic(ARM Generic Interrupt Controller),并指定正确的interrupts属性(如<GIC_SPI 42 IRQ_TYPE_LEVEL_LOW>)。如果配置错误,系统将无法及时响应电池异常,只能靠轮询,这既耗电又不可靠。内核版本的兼容性鸿沟:RK3588 SDK通常基于Linux 5.10 LTS内核,而BQ25703的驱动在5.10中尚不完善,缺少对某些高级功能(如动态输入电流限制)的支持。因此,我们不得不打上TI官方提供的补丁集(
bq25700a-v5.10.patch),并在设备树中明确启用这些补丁所新增的属性,例如ti,otg-vbus-detector。这是一个典型的“SDK滞后于上游”的现实,设备树就是我们弥合这个鸿沟的胶水。
3. 核心细节解析与实操要点:BQ25703与CW2015设备树节点逐行拆解
3.1 BQ25703设备树节点:从物理连接到功能开关
BQ25703的设备树节点,是整个电源调试的基石。它必须精确反映硬件原理图上的每一个连接。以下是一个经过生产验证的、完整的bq25703@6b节点示例,我们将逐行解读其背后的设计意图:
&i2c3 { status = "okay"; bq25703@6b { compatible = "ti,bq25703a"; reg = <0x6b>; interrupt-parent = <&gpio4>; interrupts = <0 0x0>; /* GPIO4_A0, active low */ #address-cells = <1>; #size-cells = <0>; /* 电源路径管理 */ ti,otg-vbus-detector; ti,enable-charging; ti,enable-otg; /* 输入源配置 */ ti,input-voltage-limit-microvolt = <19200000>; /* 19.2V */ ti,input-current-limit-microamp = <3000000>; /* 3A */ /* 充电参数 */ ti,charge-voltage-microvolt = <4200000>; /* 4.2V */ ti,charge-current-microamp = <2000000>; /* 2A */ ti,precharge-current-microamp = <250000>; /* 0.25A */ ti,termination-current-microamp = <100000>; /* 0.1A */ /* 硬件引脚控制 */ ti,chg-en-gpios = <&gpio4 9 GPIO_ACTIVE_HIGH>; /* GPIO4_B1 */ ti,otg-en-gpios = <&gpio4 10 GPIO_ACTIVE_HIGH>; /* GPIO4_B2 */ /* 电池检测 */ ti,battery-detect-gpios = <&gpio4 11 GPIO_ACTIVE_HIGH>; /* GPIO4_B3 */ }; };compatible = "ti,bq25703a":这是内核驱动匹配的关键。内核在扫描I2C设备时,会查找compatible字符串,找到drivers/power/supply/bq25700_charger.c中的.of_match_table,从而加载正确的驱动。如果这里写成"ti,bq25703"(少了末尾的a),驱动将无法加载,dmesg | grep bq会一片空白。reg = <0x6b>:这是I2C地址。BQ25703的地址由AD0/AD1引脚电平决定。标准配置下,AD0=1, AD1=0,地址为0x6b。务必用万用表实测PCB上AD0/AD1引脚的电压,确认其电平,再确定地址。我曾在一个项目中,因AD0引脚被误接到一个上拉电阻网络,导致实际地址变为0x6a,整整调试了两天才定位到这个问题。interrupt-parent与interrupts:这是最容易出错的部分。interrupt-parent = <&gpio4>表示中断信号来自GPIO4控制器。interrupts = <0 0x0>中的第一个0是GPIO4的第0个引脚,即GPIO4_A0;0x0表示低电平触发(IRQ_TYPE_LEVEL_LOW)。这个数值必须与RK3588的GPIO Bank定义严格一致。RK3588的GPIO4_A0在设备树中对应的索引就是0,这是由arch/arm64/boot/dts/rockchip/rk3588.dtsi中gpio4: gpio@fe730000节点的#gpio-cells = <2>属性决定的。填错一个数字,中断就永远不会到来。ti,otg-vbus-detector等布尔属性:这些属性没有值,仅存在即代表启用。ti,otg-vbus-detector告诉驱动,BQ25703的VBUS检测功能已启用,驱动会定期查询VBUS状态以判断外部电源是否接入。ti,enable-charging和ti,enable-otg则是软件层面的总开关,即使硬件上EN_CHG引脚已拉高,驱动也会根据此属性决定是否真正下发充电指令。这是一种安全冗余设计。ti,input-voltage-limit-microvolt等数值属性:所有单位都是微伏(µV)和微安(µA),这是Linux内核电源子系统的统一规范。19200000即19.2V,这是BQ25703的最大输入耐压,设为略低于此值(如19.0V)可增加安全裕量。3000000即3A,这是你希望BQ25703从外部电源汲取的最大电流。这个值必须与你的AC-DC适配器的额定输出电流、PCB走线的载流能力相匹配。如果设为5A,而适配器只能提供3A,BQ25703会因输入欠压而反复重启。ti,chg-en-gpios等GPIO控制属性:这些属性将BQ25703的硬件使能引脚与SoC的GPIO绑定。<&gpio4 9 GPIO_ACTIVE_HIGH>表示使用GPIO4_B1(索引9),且为高电平有效。这意味着,当内核驱动想开启充电时,它会将GPIO4_B1置为高电平。如果硬件设计上,EN_CHG引脚是低电平有效的,那么这里就必须写成GPIO_ACTIVE_LOW,否则逻辑会完全相反。
注意:BQ25703的
ti,battery-detect-gpios属性,用于连接一个外部的电池存在检测开关(通常是簧片开关或MOSFET)。当电池插入时,该GPIO被拉低,驱动据此判断电池是否在位。如果硬件上没有这个检测电路,此属性必须删除,否则驱动会一直等待一个永远不会发生的中断,导致初始化卡死。
3.2 CW2015设备树节点:从电池ID到校准数据的完整映射
CW2015的设备树节点,核心在于“电池身份认证”与“参数个性化”。它不像BQ25703那样有复杂的电源路径,但其配置的准确性,直接决定了电量显示的可信度。以下是一个典型的cw2015@62节点:
&i2c3 { cw2015@62 { compatible = "cellwise,cw2015"; reg = <0x62>; interrupt-parent = <&gpio4>; interrupts = <1 0x0>; /* GPIO4_A1, active low */ cellwise,battery-id = "CW2015-4200mAh-3.7V"; cellwise,full-capacity-microamp-hours = <4200000>; cellwise,voltage-max-microvolt = <4200000>; cellwise,voltage-min-microvolt = <3000000>; cellwise,design-capacity-microamp-hours = <4200000>; cellwise,design-voltage-microvolt = <3700000>; cellwise,rsense-micro-ohms = <10000>; cellwise,ocv-table = /bits/ 16 <0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000 0x0000>; }; };compatible = "cellwise,cw2015":同理,这是驱动匹配的钥匙。CW2015的驱动位于drivers/power/supply/cw2015_battery.c,其of_match_table中定义了"cellwise,cw2015"。写错一个字母,驱动就不会加载。reg = <0x62>:CW2015的标准I2C地址。它由硬件上的ADDR引脚决定,通常接地为0x62,接高为0x63。务必确认原理图。interrupt-parent与interrupts:CW2015的中断引脚(ALERT#)用于报告低电量警告、电池故障等事件。<1 0x0>表示GPIO4_A1(索引1),低电平有效。这个中断对于实现“电量低于10%时弹出警告”至关重要。cellwise,battery-id:这是一个字符串,用于标识电池型号。它会被驱动读取,并作为/sys/class/power_supply/cw2015-0/model_name的值暴露给用户空间。虽然不影响功能,但它是调试时快速区分不同电池批次的重要依据。cellwise,full-capacity-microamp-hours等容量/电压参数:这些是CW2015进行电量计算的“基准尺”。4200000即4200mAh,这是你所用电池的标称容量。voltage-max-microvolt = <4200000>(4.2V)和voltage-min-microvolt = <3000000>(3.0V)定义了电池的工作电压窗口。最关键的是rsense-micro-ohms = <10000>,它指定了采样电阻的阻值(10mΩ)。CW2015通过测量此电阻两端的压降来计算电流,因此这个值必须与PCB上实际焊接的电阻值(如0805封装的10mΩ精密电阻)完全一致。差1mΩ,电流读数就会产生10%的误差。cellwise,ocv-table:这是“开路电压-电量关系表”(Open Circuit Voltage Table),是CW2015精度的灵魂。它是一个长度为32的16位数组,每个元素代表一个SOC百分点(0%到100%,步进约3.125%)对应的开路电压(单位为mV)。例如,0x1000(4096)代表4096mV。这个表格必须由电池厂商提供,或通过专业设备(如Neware电池测试仪)对你的特定电池进行全温区、全寿命循环测试后生成。绝对禁止使用网上随便找来的通用OCV表!我曾接手一个项目,前代工程师直接复制了某论坛上的OCV表,结果在25°C室温下,SOC显示偏差高达±15%,在低温(0°C)下更是完全失效。正确的做法是,要求电池供应商提供针对你这批电池的、经过校准的OCV表,并将其转换为十六进制数组填入此处。
3.3 I2C总线与GPIO控制器的全局配置:让“路”和“门”都畅通
BQ25703和CW2015的节点只是“住户”,而&i2c3和&gpio4才是它们所在的“小区”和“楼栋”。设备树中对这些控制器的配置,是整个系统稳定运行的前提。
&i2c3 { status = "okay"; clock-frequency = <400000>; /* 400kHz Fast Mode */ #address-cells = <1>; #size-cells = <0>; /* 必须声明pinctrl,否则I2C引脚不会被配置为I2C功能 */ pinctrl-names = "default"; pinctrl-0 = <&i2c3_xfer &i2c3_gpio>; /* I2C3的SCL/SDA引脚复用配置 */ i2c3_xfer: i2c3-xfer { rockchip,pins = <0 RK_PA0 1 &pcfg_pull_none>, /* SCL */ <0 RK_PA1 1 &pcfg_pull_none>; /* SDA */ }; /* I2C3的GPIO引脚复用配置(用于BQ25703的EN/OTG等控制引脚) */ i2c3_gpio: i2c3-gpio { rockchip,pins = <4 RK_PB1 0 &pcfg_pull_none>, /* GPIO4_B1 */ <4 RK_PB2 0 &pcfg_pull_none>, /* GPIO4_B2 */ <4 RK_PB3 0 &pcfg_pull_none>; /* GPIO4_B3 */ }; }; &gpio4 { status = "okay"; gpio-line-names = "GPIO4_A0", "GPIO4_A1", "GPIO4_A2", "GPIO4_A3", "GPIO4_B0", "GPIO4_B1", "GPIO4_B2", "GPIO4_B3", "GPIO4_C0", "GPIO4_C1", "GPIO4_C2", "GPIO4_C3", "GPIO4_D0", "GPIO4_D1", "GPIO4_D2", "GPIO4_D3"; };clock-frequency = <400000>:I2C总线的通信速率。BQ25703和CW2015都支持Fast Mode(400kHz)。设为100kHz(Standard Mode)虽然也能通信,但会显著拖慢系统启动速度,因为内核需要更长时间来完成设备枚举。设为1MHz(Fast Mode Plus)则可能超出PCB走线的电气性能极限,导致通信不稳定。pinctrl-names与pinctrl-0:这是Rockchip平台特有的、最关键的配置。pinctrl-0 = <&i2c3_xfer &i2c3_gpio>表示,当I2C3控制器被启用时,它会同时应用两个引脚配置组:i2c3_xfer(将RK_PA0/RK_PA1配置为SCL/SDA功能)和i2c3_gpio(将RK_PB1/RK_PB2/RK_PB3配置为普通GPIO功能)。如果没有这一行,RK3588的引脚复用器(Pinmux)不会将这些引脚切换到正确的功能模式,I2C通信将彻底失败。这是Rockchip平台新手最容易忽略的“隐形杀手”。gpio-line-names:为GPIO4控制器的16个引脚赋予了人类可读的名称。这不仅方便调试(cat /sys/kernel/debug/gpio会显示GPIO4_B1: en_chg),更重要的是,它确保了ti,chg-en-gpios = <&gpio4 9 GPIO_ACTIVE_HIGH>中的索引9,确实对应着GPIO4_B1。这个映射关系是由gpio-line-names数组的顺序决定的,索引0是第一个名字,索引9就是第十个名字,即GPIO4_B1。
4. 实操过程与核心环节实现:从编译、烧录到验证的全流程记录
4.1 编译与烧录:让设备树“活”起来
设备树文件(.dts)本身是一份文本,它必须被编译成二进制的设备树Blob(.dtb),才能被U-Boot加载并传递给Linux内核。这个过程看似简单,但每一步都暗藏玄机。
定位与编辑:首先,在你的RK3588 SDK目录中,找到对应的设备树源文件。它通常位于
kernel/arch/arm64/boot/dts/rockchip/下,文件名类似于rk3588-evb.dts或rk3588-your_company_tablet.dts。用文本编辑器(如VS Code)打开它,找到&i2c3节点的位置,将前面章节中完整的BQ25703和CW2015节点粘贴进去。切记:不要直接修改rk3588.dtsi(.dtsi是芯片级的通用头文件),所有板级定制都应在.dts文件中完成。编译设备树:进入内核源码根目录,执行:
make ARCH=arm64 rk3588-your_company_tablet.dtb如果编译成功,你会在
arch/arm64/boot/dts/rockchip/目录下看到rk3588-your_company_tablet.dtb文件。如果报错,最常见的原因是语法错误,比如少了一个分号;,或者引号不匹配。编译器会给出精确的行号,按提示修正即可。烧录到开发板:RK3588通常使用
rkdeveloptool进行固件烧录。你需要将新编译的.dtb文件,与内核镜像(Image)、根文件系统(rootfs.img)一起打包。最稳妥的方式是使用mkimage工具生成一个符合Rockchip格式的boot.img:mkimage -n rk3588 -T ramdisk -C none -d rk3588-your_company_tablet.dtb boot.img然后,将开发板置于MaskROM模式(短接eMMC的CLK与GND引脚后上电),执行:
rkdeveloptool db Loader_bin.bin rkdeveloptool wl 0x0 Image rkdeveloptool wl 0x00200000 boot.img rkdeveloptool wl 0x00400000 rootfs.img rkdeveloptool rd这个过程需要几分钟。完成后,开发板会自动重启。
提示:在烧录前,强烈建议先备份原始的
.dtb文件。你可以通过U-Boot命令fatload mmc 0:1 ${loadaddr} rk3588-evb.dtb && fatwrite mmc 0:1 ${loadaddr} rk3588-evb.dtb.bak来完成。万一新设备树有致命错误导致系统无法启动,你可以在U-Boot命令行中手动加载备份的.dtb来救急。
4.2 启动日志分析:在dmesg中寻找“心跳”
系统启动后,第一时间连接串口,执行dmesg | grep -i "bq\|cw\|power",这是诊断设备树是否生效的“黄金命令”。一个成功的启动日志,应该包含以下关键信息:
[ 1.234567] bq25700_charger 3-006b: BQ25703A detected [ 1.234589] bq25700_charger 3-006b: Input voltage limit set to 19200000 uV [ 1.234612] bq25700_charger 3-006b: Charging enabled, target voltage 4200000 uV [ 1.234634] cw2015_battery 3-0062: CW2015 battery detected, ID: CW2015-4200mAh-3.7V [ 1.234656] cw2015_battery 3-0062: Full capacity set to 4200000 uAh [ 1.234678] power_supply bq25703-0: registered [ 1.234690] power_supply cw2015-0: registeredBQ25703A detected:表明I2C通信成功,驱动已识别到芯片。Charging enabled:表明ti,enable-charging属性生效,驱动已向BQ25703下发了使能指令。CW2015 battery detected:表明CW2015也已在线。registered:这是最重要的标志,意味着这两个设备已成功注册到Linux的power_supply子系统,/sys/class/power_supply/目录下将出现bq25703-0和cw2015-0两个子目录。
如果日志中出现Failed to get irq或Failed to read device id,那就说明interrupts或reg属性配置错误。如果完全没有任何相关日志,则大概率是I2C总线本身没启用(status = "disabled")或pinctrl配置缺失。
4.3 系统级验证:用sysfs接口读取真实数据
一旦设备注册成功,就可以通过sysfs文件系统,像操作普通文件一样,读取和写入电源参数。这是最直接、最可靠的验证方式。
检查设备节点是否存在:
ls /sys/class/power_supply/ # 应该输出:bq25703-0 cw2015-0读取BQ25703的实时状态:
# 当前输入电压(单位:微伏) cat /sys/class/power_supply/bq25703-0/voltage_now # 当前输入电流(单位:微安) cat /sys/class/power_supply/bq25703-0/current_now # 当前充电状态("Charging", "Discharging", "Full", "Not charging") cat /sys/class/power_supply/bq25703-0/status # 是否有外部电源("Online", "Offline") cat /sys/class/power_supply/bq25703-0/online读取CW2015的电量信息:
# 当前剩余电量(单位:微安时) cat /sys/class/power_supply/cw2015-0/energy_now # 当前电池电压(单位:微伏) cat /sys/class/power_supply/cw2015-0/voltage_now # 当前SOC(0-100,单位:整数百分比) cat /sys/class/power_supply/cw2015-0/capacity # 电池健康度(SOH,0-100) cat /sys/class/power_supply/cw2015-0/health
实测心得:在验证过程中,我习惯用一个简单的Shell脚本,每秒刷新一次关键数据,观察其变化趋势:
#!/bin/bash while true; do echo "=== $(date) ===" echo "BQ25703 V: $(cat /sys/class/power_supply/bq25703-0/voltage_now)" echo "BQ25703 I: $(cat /sys/class/power_supply/bq25703-0/current_now)" echo "CW2015 SOC: $(cat /sys/class/power_supply/cw2015-0/capacity)" echo "CW2015 V: $(cat /sys/class/power_supply/cw2015-0/voltage_now)" sleep 1 done运行这个脚本,然后插拔电源、模拟充放电,你能直观地看到voltage_now和capacity是如何联动变化的。如果capacity纹丝不动,而voltage_now在剧烈波动,那基本可以断定CW2015的OCV表或rsense值配置有误。
4.4 高级调试:使用i2cdetect与i2cget直连芯片
当sysfs接口返回异常数据时,就需要绕过驱动,直接与I2C设备对话,进行底层诊断。
- 扫描I2C总线,确认设备在线:
i2cdetect -y 3 # 输出类似: # 0