
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 bootkernel起来后都会重新执行一轮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/onlineAC充电器在线状态。这些数据的组合能帮我们判断软件卡在哪一步。比如online1、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平台关机充电问题折磨的同行。如果你也遇到过类似“第一帧陷阱”欢迎带着你的排查思路来交流不同项目的奇奇怪怪的根因往往是最涨经验的部分。