资讯详情

资讯详情

PLL已锁定但外设无响应:低功耗唤醒时序排查实战

半夜两点半被测试同事一个电话拽起来说低功耗唤醒之后设备就是不动串口敲什么都没反应。我第一反应是看了下log里的标志位PLL lock唤醒源正确电源域状态也恢复了。从纸面上看一切正常可设备就是不跑。这种“状态正常但行为异常”的问题搞过SoC低功耗的人多少都遇到过几回。有些时候是因为PLL虽然lock了但时钟还没真正稳定外设就在这个窗口里去访问总线和寄存器直接挂死有些时候是固件里的唤醒路径少恢复了一个上下文寄存器看起来小事实际上整套外设逻辑全乱了。这篇文章就把我自己踩过的坑、排查的思路和几个可复用的验证方法整理一下给同样在搞低功耗唤醒、遇到类似“明明lock了却不响应”的工程师一点参考。1. 问题现象与定位思路1.1 这类故障到底长什么样先说表象。设备从睡眠模式唤醒通常都会有一段唤醒代码执行流程恢复时钟、恢复电源域、恢复外设上下文最后跳回主循环或者RTOS的任务调度。正常情况下唤醒后串口打印一条日志或者某个GPIO拉高表示系统活了。但遇到这类问题时系统死得五花八门CPU本身在跑但外设访问超时读寄存器全是0xFFFF或者0xDEADBEEF这类默认值。卡在某个等待标志的死循环里比如等待某个中断标志、DMA传输完成标志。唤醒中断只有一次之后再来了新的事件CPU完全无感知。系统能跑起来但RTOS调度异常任务全部卡在信号量等待上。这类问题的共性就是PLL的lock状态是真的电源也恢复到了正常电压但“局部”和“全局”之间存在一个用户没意识到的时序差异。我之前遇到的一个典型案例是某款Cortex-M内核的SoC低功耗模式下把主PLL关闭唤醒后重新开启。PLL lock标志位在10微秒内就置位了但紧接着CPU去访问一个挂在AHB总线上的外设直接超时。后来查下来PLL虽然锁定了但后续的时钟分频器还没稳定输出总线时钟依旧处于门控状态。PLL lock和总线时钟释放之间的窗口就是问题所在。1.2 为什么PLL锁定不是“一切正常”的充分条件很多人容易把PLL lock作为一个“万事大吉”的信号但实际上PLL lock只代表PLL自身的反馈环路已经稳定输出频率不再漂移。它不代表后续的时钟网络、分频器、门控单元都已经按时序完整恢复了。PLL在整个SoC时钟系统里的位置相当于水源。水厂PLL说“我出水了”但每家每户外设能不能接到水还得看中间的水管时钟树、阀门时钟门控开没开、水压够不够驱动能力。更复杂的是有些SoC在低功耗模式下不只是关PLL还会把整条时钟链路上的寄存器配置都重置掉唤醒后需要重新写分频因子和门控使能位。如果固件只等了PLL lock就往下走很容易在时钟链还没打通的时候去操作外设得到的就是无响应。所以排查这类问题时不要只看PLL lock这一个点而是要看从PLL到外设的整条链路锁相环本身、时钟分频、时钟门控、总线桥、外设复位状态一个个确认才能真正定位卡住的位置。2. PLL锁定背后的电源与时钟链2.1 从硬件角度看“lock”的真正含义从电路层面理解一下PLL lock。PLL里有一个鉴频鉴相器PFD它比较参考时钟和反馈时钟的相位差然后通过电荷泵和环路滤波器去调节压控振荡器VCO的频率。lock检测电路一般会监控PFD的误差当误差持续一段时间低于阈值就给出lock信号。问题在于这个“持续一段时间”往往比实际需要的时间短。芯片厂商为了保险通常会把lock的判定条件放宽一些让用户在正常启动时不会因为lock过慢而超时。但这也会导致一个副作用lock信号给出时PLL输出的频率可能还在“微调”过程中周期抖动jitter还没完全收敛。如果外设是那种对时序非常敏感的类型比如需要精确时钟沿的串行接口这个时期的抖动就足以导致采样错误或数据错位。我在实际调试中还遇到过一个情况参考时钟本身来自另一个PLL或者外部晶振低功耗唤醒时参考时钟源也经历了从关闭到开启的过程。PLL检测到参考时钟消失会立刻失锁唤醒后重新锁定。但如果参考时钟的稳定时间比PLL lock还慢PLL可能会在参考时钟还不稳定的时候误报lock。这种问题比较阴间你查PLL的配置寄存器一切正常查参考时钟的状态寄存器它显示“运行”但实际上晶振起振后的震荡幅度还没达到稳定电平。示波器上看参考时钟的频率已经对了但幅度在慢慢爬升PLL在这个阶段给出的lock标志并不可靠。这类问题的排查思路是去看参考时钟源有没有独立的“ready”信号。很多SoC里外部晶振或内部RC振荡器会提供一个时钟稳定标志比PLL lock多一道保障。唤醒代码里应该同时等待“参考时钟ready”和“PLL lock”甚至要先等参考时钟ready再启动PLL最后等PLL lock。顺序反了就会出现表面lock但实际不稳定的情况。2.2 时钟源切换与复位释放的连带问题低功耗唤醒后还有一类容易被忽略的问题时钟源切换和复位释放的顺序。很多SoC在低功耗模式下会把主时钟切换到低速时钟比如32kHz的RC或RTC时钟唤醒后再切回高速PLL时钟。切换动作本身需要遵循特定的握手协议先新时钟准备好再切换选择器最后等旧时钟关掉。如果固件里只是写了“时钟源选择器设为PLL”没有做切换握手那么切换瞬间可能产生毛刺glitch。毛刺打进外设的时钟树轻则让某个外设状态机异常重则导致寄存器写入错位产生莫名其妙的值。还有一种情况唤醒复位wakeup reset和系统上电复位power-on reset处理的外设复位逻辑不一样。有些SoC在低功耗唤醒时会额外复位一部分数字外设但复位释放的时间点由硬件自动控制软件无法干预。如果软件在复位释放之前就访问外设读回来的可能是复位中间态的值或者总线直接返回错误。我之前调试过一个I2C外设唤醒后无响应的问题。从log上看PLL lock、时钟切换都完成了但I2C控制器就是报总线忙。查寄存器发现I2C的ENABLE位是好的但状态机的内部状态没有恢复到IDLE而是卡在了一个中间态。后来把系统reset释放的延迟时间调大问题就消失了。原因就是这个SoC的I2C外设唤醒复位释放比总线时钟恢复慢了一拍软件在复位没释放完的时候写了寄存器写入被“吃掉”了一部分。所以遇到唤醒后外设无响应先确认外设的复位释放时序特别是那些独立于CPU复位的“功能复位functional reset”。这类复位释放的信号通常不在PLL lock的控制范围内但它的时序错误会导致外设行为异常。2.3 关键点lock与时钟稳定的时间差这里补充一个实测经验PLL lock之后至少再额外等待几微秒再访问高速外设。具体数值在不同芯片上有差异我习惯的做法是查芯片手册里的“PLL locking time”和“Clock stable time”两个参数前者是锁相环反馈环路稳定时间后者是时钟树输出到达稳定状态的时间。很多手册里这两个数值不一样差的这一段就是“软件要背锅”的地方。这个时间差还有一个来源时钟分频器的同步链。数字分频器通常由一组计数器实现从PLL输出到分频后的时钟计数器需要若干个PLL周期来同步。如果软件在PLL lock后立即去读分频后的时钟状态计数器可能还在跑第一个周期读到的频率值不是最终值。如果此时去配外设的波特率或者采样率算出来的参数就是基于一个错误频率外设自然会表现异常。我在一个具体项目里做过的验证是写一个测试代码PLL lock后每隔1微秒去读一次某个外设的版本寄存器看寄存器从“不可读/超时”到“可读且返回值正确”需要多久。实测下来有些外设要等3到5微秒才能稳定响应而PLL lock本身只要1到2微秒。这个时间差很短但在高速外设的初始化窗口里足以造成问题。3. 固件侧的隐性坑3.1 唤醒源标志与中断丢失有些时候PLL lock和时钟恢复都没问题设备无响应是因为中断系统出了问题。低功耗唤醒通常依赖某个唤醒源产生中断比如RTC闹钟、外部GPIO边沿、定时器等。但唤醒中断触发后如果固件没有正确清除中断标志后续新的唤醒事件就无法再触发中断设备看起来就是“没反应”。这种问题有个典型特征第一次唤醒成功后设备能正常跑但过了一会儿又睡着了第二次唤醒就死掉。排查时要看唤醒源的中断标志是否在唤醒路径中被清除而且要注意“清除位置”。有些外设要求先读状态寄存器再清除标志有些要求先清除标志再读状态顺序不对会把新产生的中断标志一起清掉。还有一个坑是中断优先级和NVIC配置。唤醒后如果固件没有恢复NVIC里外设中断的使能位和优先级那么即使外设产生了中断CPU也不会响应。很多SoC的低功耗模式会关闭整个NVIC的时钟唤醒后NVIC的大部分寄存器会恢复到默认值需要软件重新配置。我见过有人把外设初始化里配置NVIC的代码放在了“只在开机时执行一次”的分支结果唤醒后中断全没了但外设状态看着又是好的很难排查。3.2 状态保存与恢复的遗漏低功耗唤醒后外设无响应的另一个常见原因是外设上下文的保存/恢复不完整。有些外设的配置寄存器在睡眠模式下会断电唤醒后恢复默认值。如果固件只在系统启动时初始化一次这些寄存器睡眠唤醒后它们的值就丢了外设行为自然不对。这个问题在带多个电源域的SoC上特别突出。比如一个UART外设它的控制寄存器在“always-on”电源域里但波特率发生器在“powered-off”电源域里唤醒后波特率发生器的配置丢失UART输出乱码。软件里如果只恢复了控制寄存器没恢复波特率寄存器就会得到“看起来能发数据但数据全错”的现象。一套完整的唤醒恢复流程应该包括保存所有需要掉电保持的寄存器值通常在进入低功耗前做。唤醒后恢复电源域和时钟。按依赖顺序恢复外设上下文先恢复时钟相关再恢复控制逻辑最后恢复数据通路。确认每个外设的复位状态已正常释放再写寄存器。我做过的项目比较多最后总结出来的习惯是每个外设写一个单独的restore_xxx_context()函数里面列出这个外设所有需要恢复的寄存器按地址偏移从小到大顺序写并且通过读回验证保证写入成功。这套方法在调试唤醒问题时非常有用一旦某个外设无响应直接查它的restore函数里遗漏了哪个寄存器。3.3 外设寄存器默认态的“假死”“假死”的情况也比较常见设备看起来没响应但CPU其实是活着的只是某个外设的状态不对导致软件逻辑卡住了。比如一个GPIO唤醒源它配置成上升沿触发但唤醒后引脚电平还停留在高电平软件里没有做电平去抖和边沿重新检测就会一直认为中断没有发生。还有一个例子是DMA。低功耗唤醒时如果DMA的传输控制寄存器没被正确恢复DMA可能处于一种“半配置”状态让它继续传输会直接卡住等待一个永远不会来的完成中断。而软件的主逻辑可能正阻塞在DMA完成信号量上。这类问题的排查思路是先确认CPU是否在跑再确认软件阻塞在哪个位置。用调试器看一眼当前PC指针和调用栈就能定位到卡住的那一行。如果PC在信号量等待函数里那重点查是谁没有释放信号量如果PC在某个while循环里重点查这个循环等待的标志什么时候置位。我之前用过一个很有用的技巧在唤醒路径的关键节点加一些临时GPIO翻转比如PLL lock后翻一下、外设上下文恢复完成后翻一下、主循环跳转前翻一下。用逻辑分析仪抓GPIO波形就能清楚看到唤醒流程走没走完、卡在哪个阶段。这个手段比单纯看log可靠得多因为log输出本身依赖UART而UART可能就是无响应的外设之一。4. 实操排查方法与典型案例4.1 第一板斧波形实测锁定时间差前面说了那么多理论最终还是要靠测量来定位。第一步建议做的是把PLL lock标志输出到一个GPIO同时把系统唤醒事件也输出到一个GPIO用示波器或逻辑分析仪测量两个信号的时间关系。具体做法有两种如果SoC支持内部信号观测比如某些芯片的debug模式可以把clk_pll_lock和soc_wakeup_n这类内部信号映射到外部引脚直接观测。如果不支持在固件里手动翻转唤醒中断触发后立刻拉高一个GPIO读到PLL lock之后立刻拉低另一个GPIO。通过测量两个GPIO的间隔就能知道PLL lock相对唤醒事件的时间。这一步能帮你验证一件事PLL lock本身有没有问题。如果从唤醒事件到PLL lock的时间远大于手册标称值说明PLL的启动过程有问题可能是参考时钟没准备好也可能是PLL供电电压不够。如果时间正常那么问题大概率不在PLL本身而是出在PLL到外设之间的链路。我在一个项目中测出来的结果就是PLL lock时间正常只有2微秒但从PLL lock到某个外设的时钟稳定要多花8微秒。这个问题从软件根本发现不了只有加了GPIO翻转实测才能看到。4.2 第二板斧简化BSP逐个外设排除波形确认时钟链路没问题后就要排查固件流程了。建议把BSP做一次“减法”把唤醒路径里所有外设的初始化代码注释掉只保留最核心的CPU时钟和串口看能不能正常唤醒。如果串口能打印再逐个恢复外设初始化每次加一个唤醒一次直到找到哪个外设加进去之后导致无响应。这个方法看起来笨但非常有效。因为外设之间的耦合关系往往不直观A外设的初始化可能依赖B外设的时钟而B外设的时钟在唤醒后没恢复。通过逐个排除很快就能圈定问题外设。有一次我排查功耗相关问题时用这个方法找到了一个电源域管理芯片的I2C驱动有问题。那个驱动在初始化时会做一个“读芯片ID”的操作这个操作本身依赖I2C外设而I2C外设的时钟又依赖一个PLL。低功耗唤醒后PLL虽然lock了但I2C外设所在的总线桥有额外的恢复延时。在I2C驱动里加一个“等待总线桥ready”的循环之后整个唤醒流程就正常了。这个等待时间不是随便拍脑袋定的。我们在逻辑分析仪上抓到了总线桥的ready信号发现它在PLL lock之后约5微秒才拉高。代码里做的就是在PLL lock后先等待这个ready信号为高再让I2C驱动继续。这样既保证功能正确又能把等待时间控制在最短。4.3 第三板斧写一个“最小唤醒测试”另外一个推荐做法是在项目早期就写一个最小唤醒测试工程只保留最简单的唤醒源、一个GPIO翻转、一段串口打印代码。这个工程不跑RTOS、不初始化复杂的驱动只做一件事从睡眠唤醒后点亮一个LED或翻转一个GPIO。这个小工程有几点好处它是验证低功耗唤醒基本路径的基线如果连这个都跑不通说明问题在芯片本身或者最底层的时钟/电源配置。后续遇到任何唤醒问题都可以在这个最小工程的基础上做减法来对比。如果最小工程能跑通而完整应用跑不通问题大概率在应用代码里而不是在芯片配置上。我在新项目里通常会把最小唤醒测试放在驱动开发之前做。等这个工程验证通过再逐步叠加外设驱动、RTOS、应用任务。这个过程虽然慢但每一步都稳后期调试成本会低很多。最小唤醒测试还有一个变体不只测试唤醒路径还测试睡眠路径。进入低功耗前把所有唤醒源的状态打印出来唤醒后再打印一遍对比两次的结果能发现唤醒源配置在睡眠期间被硬件修改的情况。这类问题很难从代码上发现因为代码里明明配置的是上升沿触发但实际硬件在睡眠时把边沿检测电路关掉了唤醒后需要重新配置。5. 常见问题速查表现象可能原因排查手段PLL lock后外设总线无响应PLL到外设的时钟分频/门控未释放测量时钟树各节点的enable信号与PLL lock的时序关系唤醒后串口输出乱码波特率相关寄存器未恢复检查外设上下文恢复函数重点排查时钟分频配置唤醒后中断不触发NVIC没有恢复、中断标志未清除查看NVIC寄存器值和唤醒源中断标志状态唤醒后卡在等待标志的循环DMA残留传输、外设状态机未复位查看调用栈确认等待的标志由谁置位唤醒后CPU已跑但任务调度异常RTOS节拍定时器配置丢失确认Systick或系统定时器的时钟源和装载值在唤醒后是否重新配置唤醒后外设寄存器读回全为FF外设复位未释放、供电域未恢复检查外设功能复位信号和电源域状态寄存器唤醒后功耗异常偏高时钟门控恢复过度全部打开对比唤醒前后时钟门控寄存器的值确认是否只恢复了需要的外设这张表是我根据多年的项目经验整理的几乎涵盖了我遇到过的所有唤醒后无响应的原因。实际使用中对着表一项项排除比漫无目的地翻代码要高效得多。6. 结语一点个人经验说实话这个问题的根本症结在于很多SoC芯片手册对“唤醒时序”的描述不够直观。PLL lock只是其中一个时间节点而完整的上电链路里还有一堆时序要求。纸上看到的“lock”和真实世界里“外设已经可以正常工作”之间隔着一道“谁能用这个时钟”的鸿沟。我的建议是在项目初期就把唤醒时序图完整梳理一遍上电顺序、时钟稳定顺序、复位释放顺序、寄存器恢复顺序全部画出来挂在工位旁边。后面遇到问题对照时序图一步一步检查定位会快很多。同时所有的等待时间都不要用“拍脑袋”的固定延时去凑尽量通过硬件信号去判断状态。固定延时能解决眼前的问题但一旦芯片改版、时钟配置调整这些延时就得重新调试终究不是长久之计。最后再分享一个小技巧如果你手上有逻辑分析仪建议把唤醒相关的信号都接上比如PLL lock、时钟门控使能、外设复位释放、第一个唤醒中断。把这些信号都同步抓下来配合固件代码里的日志一起看基本能一次定位九成以上的唤醒无响应问题。我自己这十几年做下来觉得低功耗唤醒的问题说到底就是一句话信任状态更要信任时序。每一次调试都是在给自己补一堂时序课。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →