1. 先从现象说起:插上充电器就“定住”的板子
1.1 故障现场与现象复盘
这个问题,我在新项目的Bring-up阶段遇到过不止一次。一台基于MTK8765Q平台的工程板,关机状态下插上充电器,屏幕亮起来了,但画面死死卡在充电logo第一帧。正常情况下,关机充电应该显示出电池图标、电量百分比,充电进度条还要一点一点往前走。可现在它就像程序跑进了一个死循环,永远停在那张静态图上。更让人头疼的是,拉电流表一看,充电电流接近零——换句话说,它不只是画面卡住,而是压根没在充电。
这种故障在MTK平台的底层调试里非常典型,也非常容易劝退新人,因为它同时牵扯显示和充电两条看似不相干的链路,而且发生在系统引导阶段。它绝对不是改改动画资源、检查一下UI刷新逻辑就能解决的问题。想从头摸清,你得同时懂一点Bootloader启动流程、PMIC和电量计驱动、LCD显示初始化,还得会抓串口日志、读寄存器、量波形。这一套组合下来,刚入行的工程师往往不知道从哪里下手。
故障现象如果细分,大致有这么几条:屏幕能亮,说明LCM供电和背光通路是好的;画面稳定显示第一帧,说明LK阶段至少成功初始化了一次显示;充电电流为零,说明VBUS到电池之间的充电通路没有真正建立;长按电源键大概率也无法正常进入系统,因为整机被钉死在充电引导状态里。这几个现象组合在一起,几乎把问题范围缩小到了“充电模式引导之后、充电动画接管之前”这一段灰色地带。
1.2 为什么这类问题值得单独花篇幅
先说一个判断:MTK平台关机充电卡第一帧,是我这几年来见过的最能体现“底层系统复合故障”特征的问题之一。它不像单纯的LCD点不亮,那基本是显示链路的问题;也不像纯粹充不进电,那是电源链路的锅。它恰好卡在两条链路交汇的位置——系统已经画了一帧,说明引导前段OK,但后续的充电状态机没能推动画面,同时充电通路又没有真正建立,需要你把两条链路同时拆开、再合起来看。
这篇文章适合谁看?如果你正在做MTK平台系统BSP、底层驱动开发,或者在工厂端做整机验证与售后失效分析,遇到类似“关机充电卡画面”“关机充电不充电”“充电无电流”这类问题,都可以拿这篇文章当一份排查手册。即便你用的不是MTK,只要原理相通,这套排查方法论也完全可以迁移过去——因为关机充电这种东西,所有手机平台都是同一套底层逻辑:Boot阶段检测充电器、初始化电池计量、点亮屏幕、循环刷新充电动画。
2. 关机充电的完整链路:它远不止一个“动画”
2.1 从BootROM到LK:关机充电的第一跳
MTK平台的关机充电并不是Android系统启动完之后才跑的功能,它是在Boot阶段就开启了。芯片上电后BootROM先运行,然后引导LK——就是我们常说的bootloader。LK里的电源管理初始化会去访问PMIC,通过PMIC的中断和寄存器状态来判断当前是不是有充电器插在USB口上。这一步是整个关机充电功能的“地基”。
MTK平台对充电器类型的识别,通常依赖PMIC引脚上VBUS的电平变化以及充电器检测逻辑。如果插入的是普通AC适配器,VBUS会被拉高到一个固定电平;如果是USB插到电脑上,还要通过USB D+/D-的短接状态来区分标准下行端口和专用充电端口。LK拿到这个判断结果后,会决定本次引导走哪条路径——检测到充电器并且电量偏低,就会进入关机充电模式,也就是我们常说的off mode charging。
实际调试中我发现,很多“卡第一帧”的问题在第一步就已经埋雷了。比如充电器检测的GPIO配置错误,导致LK死活认为没有充电器插入;又比如PMIC的充电器检测引脚接触不良、滤波参数不对,导致状态位时而置位时而复位。这类问题时序上很“飘”,表现为十次充电有七八次正常,两三次卡住。遇到这种情况,别急着往内核代码里钻,先从硬件检测链路上找原因。
2.2 第一帧画面是谁画上去的
当我们说“卡在第一帧画面”,需要先搞清楚第一帧到底是谁画的。在MTK平台上,LK阶段会做完整的显示初始化:配置LCM串口的时序参数、拉高LCM供电的GPIO或LDO、点亮背光、分配framebuffer内存,然后把充电引导画面写进显存。这个过程和正常开机显示logo是一样的。
这一帧其实是一张“静态底图”,通常是一张开机logo或带电池框的底图。LK把这张图刷到屏幕上之后,正常情况下会继续拉起内核,然后由内核中的充电管理服务接管。之后的每一帧动画,包括电池电量百分比、充电状态、动态电池涟漪,全是内核起来之后由充电服务配合显示刷新线程持续绘制的。
明白了这一点,“卡在第一帧”的技术含义就清晰了:LK到内核的引导大概率是成功的,否则你连稳定的第一帧都看不到;真正的断点发生在“内核起来之后、服务接管显示刷新”的环节。这给了我一个重要的排查方向:别把所有精力都花在LK的显示初始化上,那一段十有八九是好的,重点要看内核充电服务为什么没有接管后续刷新。
2.3 为什么充电和显示会绑在一起
可能有朋友会问:显示卡住就查显示链路,充电没电流就查充电链路,为什么要放在一起分析?因为关机充电模式本身就是一套“极简运行环境”。它不会像正常开机那样把整个Android系统完整拉起来,而是以最少的初始化步骤去维持一个能充电、能看电量、能安全关机的精简状态。
在这个精简状态下,充电管理和显示刷新是紧紧耦合的。充电服务需要周期性读取电量计FG的电量、电流、温度,算出当前是否处于安全充电区间,再把数据传给显示刷新线程去更新画面。如果FG读数异常,充电状态机会卡在初始化或错误分支里,根本不会给UI发更新指令,屏幕自然就停在第一帧。反过来,如果显示刷新线程本身被锁死,即便充电状态机运行正常,屏幕上同样看不到任何变化。
所以,这个故障的实质往往是:充电链路的某个环节出了异常,传导到了显示链路;或者两条链路共享的某个资源(I2C控制器、GPIO、时钟域、电源域)出了问题,让两边同时宕机。排查思路必须是“从电源到显示”纵向打通,而不是横向切成两半各查各的。我见过不少工程师在显示驱动代码里翻了一天,最后发现根因在电量计的I2C上拉电阻虚焊,就是吃了没有把链路合并理解的亏。
3. 排查第一板斧:先把三件“低级错误”排除掉
3.1 串口日志里藏着大半答案
遇到这类问题,我通常先不急着改代码,拿一根串口线先抓一份完整的引导日志。日志里LK阶段和内核早期阶段的打印都在,先用关键词筛一遍,重点看四类信息:第一,LK有没有正确进入充电模式;第二,内核早期有没有充电相关驱动的报错;第三,显示驱动初始化日志是否完整;第四,内核日志是否正常走到最后的系统初始化流程。
举个例子,正常进入关机充电模式的日志里,会看到明确的充电器类型识别记录,类似“detect charger type xxx”这种输出;如果看到的是“charger type none”,基本可以断定检测链路出了问题。又比如内核阶段如果有I2C通信失败的报错,频率很高、且指向的正好是FG或充电IC的地址,那问题方向基本锁定在I2C链路上了。
抓日志有个细节要提醒:串口要提前接好,从按下电源键之前就开始记录,确保BootROM和LK最早期日志不丢。很多工程板的串口默认波特率是921600,但也有项目改低了,连不上时先确认这个参数。日志抓满后建议保存原始文件,每轮复测都归档,方便比对前后差异。这样坚持下来,你会发现很多“看起来随机”的问题其实都有固定的日志指纹。
3.2 充电器检测:确认系统眼里“有没有充电器”
如果串口日志看不出明显异常,第二步我会直接去验证“充电器检测”这个硬件判断。在MTK平台上,PMIC的充电器检测结果会映射到寄存器空间,可以通过串口控制台、调试命令或者寄存器读写工具直接读取。
重点确认三样东西:VBUS电压有效位是否置起、当前解析出的充电器类型是不是AC或DCP、以及PMIC对应中断有没有被正确清除。这三个状态只要有一个不对,后面什么动画、什么电流都属于空中楼阁。不过也别只盯着软件寄存器,最好用示波器实际抓一下VBUS的上升沿和D+/D-引脚电压,因为寄存器里的值可能来自硬件引脚的错误采样。
这里想提醒一个高频坑:很多板子的充电器检测引脚RC滤波参数取值不当,插入瞬间会有过冲和毛刺,导致LK首次采样结果不是DCP而是USB。系统因此按USB标准电流去充电,或者干脆认为充电器不可用。软件上可以通过加长去抖时间缓解,硬件上则要调整RC参数,从源头抑制毛刺。我遇到过一台机器,把去抖时间从50ms改到200ms之后,卡帧问题直接消失,就是典型的检测时序窗口问题。
3.3 LCD与背光:确认“第一帧”是真卡还是假卡
第三步是确认显示链路本身的健康度。先用万用表确认LCM供电、背光电压都起来了。如果屏幕亮着、画面稳定,说明LK显示初始化是成功的。接着用示波器看MIPI DSI的高低速信号切换,确认真机在进入内核阶段之后有没有被重新初始化一遍。
MTK平台上,无论normal boot还是charging boot,kernel起来后都会重新执行一轮panel初始化流程。如果这轮初始化时序不对,比如reset GPIO翻转顺序错误、LDO供电晚了一拍,画面就会停在LK那一帧不再更新。这类问题通常还伴随触控失灵或花屏,但如果是单纯“停在第一帧且背光亮”,则更多要联动充电链路排查。
实际操作中,我会把显示排查的结果分成三个状态记下来:黑屏或花屏、稳定静图但不更新、动态更新但内容异常。第一种才算显示严重异常,第二第三种都要把主力放到系统或充电链路上,别在显示面板上做无效折腾。这个分类习惯帮我避免了很多次在错误方向上的深挖。
4. 核心定位:充电回路、电量计、状态机的三位一体排查
4.1 充电IC/PMIC寄存器核对:充电通路有没有“合闸”
基础问题排除干净之后,就进入真正的核心排查。这一步要看的是:软件认为自己正在充电,但硬件物理通路到底有没有打通——也就是说,充电IC内部那个控制充电电流的“开关”到底有没有合上。
在MTK方案里,充电通路管理可能在PMIC内部,也可能由独立充电IC承担。我们需要通过I2C或寄存器总线去读取充电管理芯片的关键控制位:充电使能位是否置位、输入限流和充电电流配置是否符合当前充电器类型、状态位是否出现了done或stop或error、各种保护的触发标志是否被置位。
这个排查最好配合一个动作:把“上电瞬间所有关键寄存器默认值”完整读一份存底。我踩过一次很深的坑,某项目在LK里初始化充电IC时,不小心给它的看门狗寄存器写错了一个值,导致芯片内部看门狗超时后强制关闭充电通路。从表面看完全是“卡第一帧、电流为零”,但无论查软件状态机还是查FG通信都查不出问题,最后是靠拿寄存器dump和正常板子做diff才发现的。所以寄存器对比这种“笨办法”,往往是最快的办法。
4.2 电量计FG:充电状态的“眼睛”是否失明
电量计FG是关机充电模式下系统唯一的“电量信息源”。正常开机时,系统还能靠电池曲线、UI学习等算法做补偿;而关机充电的极简环境下,电池电量、瞬间电流、充饱判断几乎完全依赖FG的I2C读数。FG一旦失明,整套充电状态机都会瘫痪。
排查FG最直接的动作是读它的器件ID寄存器。如果I2C总线上能看到正确ID,说明通路基本健康;如果读到NACK或者超时,就要检查几件事:I2C地址有没有冲突、SCL/SDA对应的GPIO有没有被复用成其他功能、FG的电源域有没有先于I2C访问上电、以及总线上拉电阻阻值是否合适。
还有一个隐蔽情况值得单独说:I2C控制器的时钟依赖没有配好。关机充电模式为了省电,部分外设的电源域和时钟域默认是关闭的,如果驱动代码在访问FG前没有把所在I2C控制器的时钟打开,就会一直在总线上超时。这种问题用示波器看波形会发现SDA根本没动作,或者只有SCL没有SDA,方向很明确,只是容易被“寄存器明明存在、代码明明调了”的表象迷惑。
4.3 内核充电状态机:软件层面卡在哪一个phase
如果硬件链路都确认正常,下一步就要看内核里的充电状态机到底跑到了哪一步。MTK平台的充电管理实现了一套完整的状态机,大体上分为:初始检测、充电参数配置、充电执行、充满与异常处理这么几个阶段。我们可以通过内核导出的sysfs节点实时观察当前状态。
我常用的调试节点和它们代表的意义整理如下:
/sys/class/power_supply/battery/uevent:一站式查看状态、电量、充电类型等综合信息。/sys/class/power_supply/battery/current_now:实时充放电电流。/sys/class/power_supply/battery/voltage_now:实时电池电压。/sys/class/power_supply/battery/temp:电池温度,异常读数会把状态机直接推进到error分支。/sys/class/power_supply/ac/online:AC充电器在线状态。
这些数据的组合能帮我们判断软件卡在哪一步。比如online=1、voltage_now正常、但current_now一直为0,说明软件知道有充电器,但充电通路没开,这时回到4.1查寄存器;如果voltage_now是异常值或固定值,优先怀疑FG或ADC链路,回到4.2查通信;如果temp读数离谱,比如-40°C或100°C以上,检查NTC采样电路本身。
值得强调的是,如果这些节点数据在持续变化,而屏幕就是不动,那问题就被拆分得很干净了:充电链路没毛病,是显示刷新线程没有正确消费这些数据。这时要去看framebuffer刷新、显示服务线程是否被阻塞,而不是继续在充电领域里钻牛角尖。少走这一步,是我见到的很多工程师卡壳半天的原因。
4.4 显示刷新侧:卡住瞬间UI线程在做什么
显示刷新侧排查的核心是回答一个问题:当电池节点数据一切正常时,为什么屏幕呈现“定住”的状态。我一般会看两个方向:一个是framebuffer设备节点是否创建成功,充电模式下的显示服务能不能正常映射和写入;另一个是显示刷新线程或进程是否处于阻塞状态。
MTK平台在关机充电模式下,并不会像正常开机那样拉起完整的Graphics栈,而是跑一个精简的显示服务。它和normal boot完全不是同一条刷新路径,所以“正常开机显示OK、关机充电显示卡住”是完全可能出现的。排查时不要拿normal boot的逻辑去硬套,一定要确认当前这个精简显示服务有没有被创建、有没有在跑循环。
有一种比较经典的情况是:某个内核驱动的mutex被充电服务长期持有不放,显示刷新线程在等这把锁,两边都堵住。这种死锁单看一边都像“偶发异常”,把两个线程的堆栈都抓出来对比,才会发现互相等待的关系。遇到这种情况,优先通过调整初始化顺序或缩小锁粒度来解决,而不是盲目加超时。给超时常会掩盖真正的调度问题,后面还会在别的场景里爆雷。
5. 高频根因与修复方案速查
5.1 六个典型根因对照表
把我在多个项目里实际遇到、并且确认过根因的问题整理成一个速查表,方便大家直接对照自己的现象快速判断方向:
| 根因分类 | 典型现象 | 快速确认手段 | 修复方向 |
|---|---|---|---|
| FG电量计I2C通信失败 | 卡第一帧、current_now为0、FG器件ID读不到 | 读ID寄存器、看I2C波形 | 排查地址冲突、GPIO复用、电源或时钟依赖、上拉电阻 |
| 充电器检测类型错误 | 插入DCP被识别为USB或None | 读CHRDET状态、抓VBUS与D+/D-波形 | 调整RC滤波、加长去抖、校对DTS里的GPIO配置 |
| 充电IC寄存器配置异常 | VBUS正常但充电使能为0或保护位触发 | I2C dump全部寄存器并与基线diff | 修正LK初始化序列、补配WDT或EN位 |
| 电池温度检测异常 | temp数值离谱,状态机进入error | 读NTC ADC通道、比对阻值曲线 | 检查NTC电路、更换适配的热敏电阻 |
| 显示刷新线程阻塞或死锁 | uevent数据变化但屏幕不动 | 抓刷新线程与充电服务的堆栈 | 调整初始化顺序、缩小锁粒度 |
| 电源域或时钟域未及时使能 | 充电IC与FG间歇性访问失败 | 抓波形、观察偶发超时 | 修正DTS里regulator和clk依赖 |
5.2 一个容易被忽略的判断不一致问题
除表格里的内容之外,还有一个值得单独说的高频根因:LK和内核两侧对充电器类型的判断不一致。LK阶段明明识别出DCP充电器,第一帧也正常显示;但kernel起来后按自己的一套逻辑重新判断,结果判成USB甚至None,于是内核认为没有充电器,整条充电状态机压根不启动。
这种问题的典型特征就是:背光亮、第一帧稳定、电流为零、ac/online为0。排查方向很明确,把LK侧和内核侧的充电器检测容差、去抖参数、判断分支全部拉出来对比,统一成一致的标准即可。因为LK和内核各自维护了一套充电器检测逻辑,两边的寄存器配置或GPIO定义如果不一致,就必然出现这种“前面一张画正常、后面软件根本不知道有充电器”的撕裂现象。
6. 调试过程中踩过的坑与个人建议
6.1 先把“卡住”这件事定义清楚
我建议每次调试第一步,先确定“卡住”到底是哪种卡。是LK根本没画完、内核没起来?还是内核起来了但UI不刷新?最简单的区分方法还是看串口日志。如果内核日志完整打完,系统初始化流程都走完了,问题在充电服务或显示服务这一层;如果内核日志断在某个驱动注册函数附近,顺着最后几条日志找线索,大概率能一击命中。
我给自己的要求是,每次开调前先花五分钟把日志分成“LK阶段、kernel早期、kernel稳定、system_init完成”几个区间。这样后面80%的工作量都能省下来,不用在几万行日志里反复搜关键字。日志分段的习惯,比任何调试工具都基础,也更可靠。
6.2 串口和电流表要同时上
只看日志可能漏掉“实际没充”这种硬件层面的事实;只看电流表又不知道软件走到哪一步。我调试时一般把电流表串在充电器和设备之间,手里同步开着串口终端,每做一个动作就记一笔电流值和对应当前的日志关键字。
这样能快速区分四类情况:软件以为充了但硬件没充、硬件在充但软件不知道、两边都不在充、两边都在充但显示不刷新。分清楚这四类,问题范围立刻缩小一半。这一步真的很值得养成习惯,尤其是当你要在远程给产线同事指导下板子时,同步观察两个维度的数据能少跑很多冤枉路。
6.3 别忽略复位和欠压问题
关机充电模式对电源稳定性非常敏感。插入充电器的瞬间,VBUS要给一堆电容充电,如果输入路径的阻抗偏大,PMIC内部电压会出现短暂跌落,一旦跌到欠压阈值以下,充电IC或PMIC就会复位,然后整条状态机从头再来,表现为间歇性复现的卡第一帧。
遇到“十次里三四次正常、六七次卡死”的案例,优先检查VBUS路径上的阻抗、并联电容容量、以及充电IC的欠压锁定阈值配置。这类问题用静态代码分析很难看出来,必须结合示波器抓上电瞬间的电压跌落波形才能确认。把复现条件打上时间戳,找回规律,往往是破局的关键。
6.4 建立基线数据比什么都重要
在项目Bring-up初期,趁硬件还没出大问题,我会把充电IC所有寄存器默认值、FG器件ID、PMIC状态位、关键电源域的上电时序全部记录下来,存成一个表格。等出了问题,第一件事就是拿异常板的寄存器和基线板做diff。
我遇到过的不少所谓疑难杂症,最后都是靠这一招在十分钟内锁定的,比对着datasheet猜寄存器含义高效得多。寄存器读出来的值是客观的,只要基线准确,一个位一个位地比对,偏差通常就写在脸上。这个习惯对项目后期的问题回溯也非常有价值。
6.5 修改前先确认影响面
关机充电模式的初始化代码通常横跨LK和kernel两个工程,而且和normal boot共享大量初始化函数。改充电IC的寄存器配置,可能在正常开机时也产生副作用。所以任何修改,我都会列一个“这个改动会不会影响normal boot”的检查项,在确认只对充电模式生效后再合入。
这个习惯曾经帮我避免过至少两次线上回归事故。举个例子,为了修复关机充电的电流配置,有人直接把LK里的充电限流从1A改成2A,结果normal boot下的边充边玩场景也跟着用了2A,主板过热保护被触发。所以在动手改之前,先搞清楚这段代码在normal boot路径里会不会被调用,非常重要。
6.6 一个很有用的调试标记技巧
最后分享一个很小但很管用的技巧:每次调这类问题时,我都会在串口日志里加一行调试标记,类似在LK接近末尾处打印“CHKPOINT_1”,在kernel充电服务初始化完成后打印“CHKPOINT_2”。这行标记在正常板子上能看到连续打印,在故障板子上就能一目了然地看出断点在哪个模块之间。
它比反复翻log找关键字直观得多,尤其是当现场不是只有你一个人调试时,这种显眼标记能让所有人快速对齐问题阶段。配合前面的日志分段习惯,二三十行关键日志就能把一次引导过程描述清楚。希望这些经验能帮到正在被MTK平台关机充电问题折磨的同行。如果你也遇到过类似“第一帧陷阱”,欢迎带着你的排查思路来交流,不同项目的奇奇怪怪的根因,往往是最涨经验的部分。