ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32与CC1125低功耗待机电流优化:从1.4mA到3.2uA的排查实录

STM32与CC1125低功耗待机电流优化:从1.4mA到3.2uA的排查实录 最近在做一个电池供电的无线采集节点主控是STM32F030C8T6射频端用的是TI的CC1125 Sub-1G收发器。整机待机电流的目标是5uA以内但我在调试过程中遇到了一个很典型的问题单独让CC1125进SLEEP电流正常单独让MCU进STANDBY电流也正常但只要两者一组合先让CC1125睡、再让STM32进STANDBY整机电流就卡在1.4mA下不来。这个标题放在工程社区里估计能引来一堆同病相怜的人。毕竟sleep这个词在不同的语境下坑过太多人——软件那边有delphi的sleep、js的sleep咱们嵌入式这边也有MCU的SLEEP、射频芯片的SLEEP。但这类问题从来不是玄学1.4mA这个数字本身就藏着线索。我最后把整机待机压到了3.2uA整个过程值得记录一下给后面做低功耗射频节点的人留个参考。1. 1.4mA不是略高而是CC1125停在IDLE的典型特征先说结论如果你的整机待机电流停留在毫安级第一反应不应该是怀疑电源、怀疑LDO、怀疑电容漏电而是先问一句——CC1125到底进没进SLEEP我实测下来这个量级的电流更像是CC1125停在了IDLE状态。1.1 正常待机的电流预期先算一笔账。CC1125的数据手册里SLEEP模式的典型电流是0.3uASTM32F030C8T6在STANDBY模式下典型电流在1.5uA左右。再加上LDO自身的静态电流按3uA算整机待机电流理论上应该压在5uA以内。我最初实测到的1.4mA比预期高出差不多三个数量级。这已经不是略微偏大的问题了而是有一个模块根本没有睡过去。1.2 IDLE电流为什么会对得上CC1125在IDLE状态下稳压器开启、晶体振荡器保持运行这部分的电流典型值大约在1.2mA到1.5mA。我万用表量到的1.4mA和这个区间高度吻合。所以从量级上基本可以判断CC1125并没有进入SLEEP而是停在了IDLE。这里有个很实用的经验通过电流的量级来反推芯片的状态。mA级电流先查射频端是否真的进入睡眠几十uA级电流查MCU的IO配置和上拉电阻几uA级电流查电容漏电、PCB清洗残渣、万用表精度这些细枝末节的问题。能进入这个思路排查方向就清楚了。2. 先别急着改代码确认CC1125是真的睡了还是假睡很多人遇到电流高第一反应是去翻MCU的STANDBY配置但这个问题其实应该从CC1125端开始排查。确认CC1125是否真正进入SLEEP有几个很直接的验证手段比反复读代码有效得多。2.1 通过唤醒行为判断芯片状态CC1125从SLEEP唤醒和从IDLE唤醒行为是完全不一样的。从SLEEP唤醒后芯片内部的寄存器会恢复到默认值因为SLEEP模式下稳压器被关断了寄存器内容不保留。所以你唤醒之后必须重新做初始化包括写入频率配置、滤波配置、校准等。而从IDLE唤醒寄存器内容还在直接发TX/RX命令就能用。我当时做了一个很简单的实验先让CC1125进SLEEP然后通过GPIO唤醒唤醒后直接尝试读寄存器配置。如果读出来的值和我写进去的不一样那就说明芯片确实经历了SLEEP掉电。实测下来寄存器内容全部回来了说明CC1125硬件上是真的进了SLEEP。那问题就很奇怪了——芯片本身进了SLEEP为什么整机电流还是1.4mA2.2 SLEEP命令的SPI时序细节这里有个容易忽略的点。CC1125的SLEEP命令是通过SPI写入strobe命令实现的具体是往芯片写0x38。strobe命令的时序要求是CSN拉低发送0x38然后CSN拉高。在CSN拉高之后芯片才会真正进入SLEEP状态。这个时序看起来简单但实际工程里发完命令和芯片真正进入SLEEP之间有几个微妙的地方。如果SPI总线上有其他干扰、CSN没有干净地拉高或者CSN拉高后又被其他信号拉低芯片都可能没有进入SLEEP。正常流程是这样// CC1125 Enter SLEEP command (0x38) CSN_LOW(); spi_write_byte(0x38); // strobe: SLEEP CSN_HIGH(); // 芯片此刻才开始进入SLEEP流程注意CSN拉高之后芯片需要一点时间完成内部状态切换虽然手册上这个时间很短但工程上稳妥的做法是CSN拉高后保持高电平不要再有任何操作给芯片一个干净的睡眠环境。2.3 实测结果CC1125确实睡了但又被拽醒了我通过唤醒行为验证CC1125确实进入了SLEEP而且SLEEP功耗本身没有问题——我飞线断开CC1125的SPI线和控制引脚单独给CC1125供电测电流SLEEP状态下能到0.5uA。那问题就锁定了CC1125进入SLEEP之后又被什么信号给唤醒了。而这个什么信号来源只可能是MCU一侧的引脚状态。这就引出了整篇文章最核心的部分——IO引脚在低功耗状态下的电平冲突。3. 真正要命的地方CSN被拉低等于亲手把CC1125从SLEEP拽回IDLE这是整个排查过程中最关键的发现。CC1125在SLEEP模式下CSN引脚有一个特性只要CSN被拉低芯片就会被唤醒。这是数据手册里明确写的唤醒机制SLEEP模式下芯片的SPI接口并没有完全断电它时刻检测着CSN的状态一旦发现CSN拉低就知道主机要来访问了于是从SLEEP回到IDLE等待命令。3.1 STM32F030 STANDBY模式下IO状态是保持的STM32F030进入STANDBY后IO引脚的状态会保持进入STANDBY之前的配置。也就是说你进入STANDBY前CSN是多少电平进STANDBY后CSN就是多少电平直到系统复位或者唤醒。这带来一个非常隐蔽的坑很多人的SPI代码里CSN是默认拉低的。比如在初始化的时候直接写GPIO_WriteBit(GPIOB, GPIO_Pin_12, Bit_RESET)让CSN保持低电平选中CC1125然后整个运行期间都没有再动过。发送SLEEP命令时CSN拉低、发命令、拉高看起来没问题。但如果代码里某个地方又把这个引脚拉低了比如关闭SPI外设时的清线操作、或者初始化流程中的默认状态芯片就被唤醒了。3.2 为什么CC1125自己进了SLEEP整机电流却不对我当时的代码流程看起来是没问题的// 1. CC1125进入SLEEP CC1125_EnterSleep(); // 2. 关闭SPI外设时钟 __HAL_RCC_SPI1_CLK_DISABLE(); // 3. MCU进入STANDBY HAL_PWR_EnterSTANDBYMode();但问题就藏在第2步到第3步之间。SPI外设时钟关闭SPI引脚的复用功能被释放但引脚本身的GPIO配置还是复用推挽输出输出寄存器的状态没有改变。如果此时CSN的ODR寄存器刚好是0那CSN维持低电平CC1125就会在MCU进入STANDBY的瞬间被唤醒。更好的流程是在发送完SLEEP命令之后CSN保持高电平然后重新配置CSN引脚为普通GPIO输出高电平。这样即使后面MCU进入STANDBYCSN也稳定在高电平不会触发CC1125的唤醒机制。3.3 还有一个隐藏问题SPI的SCLK和MOSI也不能浮空CC1125进入SLEEP后SPI接口的SCLK、MOSI、MISO都会变成高阻态。对于MCU一侧如果这几个引脚是推挽输出反而没问题因为推挽输出给的是一个确定电平。问题在于很多人会在进入STANDBY前把SPI引脚配置成输入浮空觉得这样不耗电。实际上输入浮空引脚在外部是高阻的情况下引脚电压会飘在一个不确定的中间电平CMOS输入级会处于导通和截止的临界状态产生微安级的穿透电流。一个引脚可能不明显但SPI四根线加控制线五六个引脚叠加待机电流能多出几十微安。这在要求uA级待机的场合是不能接受的。我最后采用的SPI引脚处理方案是CSN配置为输出高电平SCLK配置为输出低电平MOSI配置为输出低电平MISO配置为输入浮空。这样每个引脚都有确定电平不会产生额外漏电也不会误唤醒CC1125。4. 排查过程实录从1.4mA到3.2uA的逐项剥离下面是我实际的排查记录每一步都对应了电流的变化。这一节的价值在于如果你也遇到了类似问题可以按这个顺序进行快速定位。4.1 测试环境与测量方法先交代一下测试环境。我用的是直流稳压电源加高精度万用表的方案万用表串联在供电回路里用uA档测整机待机电流。这种测量方法的精度到0.1uA足够定位问题。测量低功耗电流时有个注意点用万用表uA档测量的本质是让电流流过万用表内部的采样电阻这个电阻会带来电压压降。如果被测系统的供电电压比较低压降可能影响系统正常工作。为了绕过这个问题我通常先用稳压电源的电流显示看个大概再用万用表串联测准最后用示波器看电流波形确认动态特征。4.2 逐步测试记录下面是完整的排查记录步骤操作内容实测电流说明1初始状态CC1125发送SLEEP命令MCU直接进STANDBY1.41mA典型异常状态2飞线断开CC1125的VDDMCU单独进STANDBY2.1uA确认MCU端正常3只给CC1125供电单独发SLEEP命令0.5uA确认CC1125端正常4恢复连接把CSN强制配置为输出高电平后进STANDBY352uA电流从mA级降到百uA级说明CSN确实是个关键路径5再把SCLK/MOSI配置为输出低电平MISO配置为输入浮空48uA多数引脚冲突解决仍有漏电路径6检查CC1125的GPIO0连接发现MCU侧PA0配了内部上拉11uAGPIO0在SLEEP时输出低电平与MCU内部上拉形成分压漏电7PA0改为输入浮空同时在板级加外部100k下拉电阻3.2uA所有漏电路径消除4.3 关于步骤6的进一步说明步骤6值得多说两句。CC1125的GPIO0被配置为唤醒源进入SLEEP后这个引脚会输出一个确定的电平。我用的是低电平唤醒所以SLEEP状态下GPIO0是低电平。而MCU的PA0也是STM32F030的WKUP1唤醒引脚被配置为内部上拉输入用来检测CC1125的唤醒信号。这个组合的问题在于CC1125的GPIO0是推挽输出低MCU的PA0是内部上拉两者之间就形成了一个电流回路3.3V通过MCU内部大约40k欧姆的上拉电阻流到CC1125的推挽输出管脚。算下来大概就是80uA级别实际测到的50uA也在这个量级范围内。虽然不足以造成mA级电流但在追求uA级待机的项目里这个坑必须填。4.4 交叉验证把CSN改回低电平电流立刻回到1.4mA排错过程中我没有只看电流下降就结束还做了反向验证。修好之后我把CSN重新配置为输出低电平再次进入STANDBY电流立刻又回到了1.36mA。这个交叉验证基本可以确认CSN被拉低唤醒CC1125是问题的核心原因。修复后再次验证电流回到3.2uA整个系统待机正常。5. 修复后的完整代码进入STANDBY前的IO管理模板经过这一轮排查我整理出了一套相对可靠的进入STANDBY前的GPIO配置模板。这套思路可以迁移到任何带外部射频芯片的低功耗设计里。5.1 CC1125进SLEEP的完整代码void CC1125_EnterSleepAndPrepareMCUStandby(void) { // 1. 确保SPI引脚处于复用功能状态 // 2. 发送CC1125 SLEEP strobe命令 CSN_LOW(); SPI1_SendByte(0x38); // CC1125_SLEEP while (SPI1_IsBusy()); CSN_HIGH(); // 3. 再等待一段时间确保CC1125完成状态切换 delay_us(500); // 4. 把SPI引脚从复用功能切换到通用GPIO设置确定电平 GPIO_InitTypeDef gpio; // CSN-高电平输出这是最关键的一步 gpio.Pin GPIO_PIN_4; // 以PA3/PA4为例按实际接线调整 gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CSN保持高 // SCLK-输出低 gpio.Pin GPIO_PIN_5; HAL_GPIO_Init(GPIOA, gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // MOSI-输出低 gpio.Pin GPIO_PIN_7; HAL_GPIO_Init(GPIOA, gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_RESET); // MISO-输入浮空 gpio.Pin GPIO_PIN_6; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, gpio); // 5. 关闭SPI外设时钟 __HAL_RCC_SPI1_CLK_DISABLE(); }这段代码的核心思路是在关闭SPI外设之前先把所有引脚切换成确定性电平。如果你使用的是标准外设库流程完全一样只是GPIO初始化函数不同。5.2 进入STANDBY的完整流程MCU进入STANDBY之前的处理有一个顺序问题。很多人一上来就调HAL_PWR_EnterSTANDBYMode()但前面几步没处理好照样白搭。我的标准流程如下void System_EnterStandby(void) { // 1. 关闭所有已打开的外设时钟 __HAL_RCC_USART1_CLK_DISABLE(); __HAL_RCC_USART2_CLK_DISABLE(); __HAL_RCC_I2C1_CLK_DISABLE(); __HAL_RCC_TIM14_CLK_DISABLE(); __HAL_RCC_ADC1_CLK_DISABLE(); // 2. 关闭ADCF030的ADC如果不关闭待机电流会多出不少 HAL_ADC_Stop(hadc); HAL_ADC_DeInit(hadc); // 3. 使能FLASH低功耗模式 __HAL_FLASH_SLEEP_POWERDOWN_ENABLE(); // 4. 处理所有GPIO引脚状态这里填入上一节的CC1125处理函数 CC1125_EnterSleepAndPrepareMCUStandby(); // 5. 清除唤醒标志位 __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); // 6. 进入STANDBY HAL_PWR_EnterSTANDBYMode(); }有几个容易漏的细节STM32F030的STANDBY唤醒源只有一个PA0WKUP1引脚。其他地方说PC13能做WKUP那是F0系列其他型号或F1系列的特性F030上不要踩这个坑。进入STANDBY前必须清除唤醒标志位PWR_FLAG_WU否则唤醒标志置位会导致MCU在进入STANDBY后立刻被唤醒。__HAL_FLASH_SLEEP_POWERDOWN_ENABLE()这个宏经常被忽略但在F030上FLASH低功耗模式能省下约1uA的电流。5.3 唤醒后的重新初始化从STANDBY唤醒后程序会从复位向量重新执行。因为CC1125经历了SLEEP寄存器内容已经丢失所以唤醒后必须完整地重新初始化CC1125包括频率配置、滤波参数、校准等。这里强调一点不要把CC1125的配置数据和MCU的初始化放在一起想当然地处理一定要在唤醒后重新走一遍射频配置流程。如果漏了校准步骤可能出现射频性能下降但通信还能用的假象这种问题比直接连不上还难排查。6. 引脚之外还有几个被忽视的偷电贼上面的排查解决了主要问题但在实际项目中还有一些不常被注意到的地方会偷电。我整理了几个在F030和CC1125组合中容易踩的点。6.1 CC1125的GPIO0/GPIO1配置CC1125的GPIO0和GPIO1可以配置为很多功能包括唤醒源、LNA使能、PA使能、TX/RX状态指示等。在低功耗设计里如果这两个引脚被配置为输出一定要确认它们在SLEEP状态下的电平是否和MCU侧引脚兼容。尤其要注意下面的情况如果GPIO0配置为high-Z on sleep睡眠时高阻而MCU侧配置为输入浮空那么这个引脚就会浮空可能产生微安级的漏电。反过来如果GPIO0配置为SLEEP时输出低电平而MCU侧配了内部上拉就会产生持续的电流路径。这两种组合都要避免。6.2 未使用的MCU引脚STM32F030有几十个引脚在低功耗设计中所有未使用的引脚都必须配置为模拟输入或者输出低电平不能保持默认的浮空输入状态。浮空输入引脚的电压不定CMOS输入级可能处于线性区产生穿透电流。我的习惯是把所有未用引脚统一配置为GPIO_MODE_ANALOG这个模式下引脚不连接任何输入缓冲或输出驱动是最省电的状态。6.3 板级设计上的漏电路径这类问题在原理图阶段就要注意不然后期改板很痛苦。举几个实测过的例子CC1125的电源去耦电容如果选得太大比如10uF以上在MCU进STANDBY但CC1125已经SLEEP的情况下电容通过芯片内部或外部电路缓慢放电会在一定时间内拉高待机电流的读数。当然电容本身不产生静态功耗但它会延长电压跌落时间影响测量判断。PCB上残留的助焊剂在潮湿环境下会形成微弱的导电通路电流可能达到几十到几百nA这个量级不好测但如果你把所有软件问题都排除了整机电流还是比理论值高可以清洗一下板子再测。排针、测试点、拨码开关等板级器件如果连接到MCU或射频芯片的引脚在SLEEP/STANDBY状态下可能引入外部干扰导致芯片被意外唤醒。6.4 低功耗测量的工具选择最后说下工具的问题。万用表uA档串联测量是最常用的方法但如果有条件建议用支持电流波形记录的可编程电源或者用高精度电流探头加示波器。低功耗系统里电流往往是脉冲式的MCU周期性唤醒、射频芯片周期性发射平均电流和峰值电流差别很大。用万用表只能看到平均值看不到电流波形会漏掉很多瞬态问题。我当时就是用示波器看到了每隔几百毫秒出现的一个200uA的脉冲才定位到一个多余的定时器没有关闭。7. 我在这个项目里总结的低功耗调试心法整个项目下来我对低功耗调试有了更具体的理解。现在处理这类问题我基本会按下面的思路快速收敛。7.1 永远先确认睡了没有遇到待机电流异常不要一上来就怀疑芯片坏了、电源有问题。先通过行为特征确认每个芯片是否真的进入了目标状态。正如前面说的CC1125是否真睡可以通过唤醒后寄存器内容来判断MCU是否真进STANDBY可以通过唤醒源是否有响应来判断。芯片状态确认了问题范围就缩小了一大半。7.2 IO状态是最大的坑90%的低功耗问题都出在IO状态上。尤其是带外部射频芯片的系统MCU和射频芯片之间的每一根连线在睡眠/待机状态下都必须有确定电平且不能违反对方芯片的唤醒条件。我的经验是进入低功耗前把每一个外部器件相关的引脚都在心里过一遍问自己三个问题——这个引脚现在是高还是低对方芯片在睡眠状态下希望这个引脚是什么电平如果电平不对会发生什么问完这三个问题大部分坑都能提前避免。7.3 电流量级能告诉你故障方向这不是我发明的理论但我在这个项目里体会得格外深。1mA级别的异常基本上可以判定有源器件工作在活动状态100uA级别基本是IO电平冲突或上拉电阻漏电10uA级别基本是悬空输入引脚的穿透电流或FLASH低功耗没开1uA级别才轮到电容漏电和PCB清洁度问题。有了这个量级判断你就不至于拿着放大镜找半天电容漏电却发现CC1125压根没睡。最后分享一个小技巧。如果你已经定位到了某个引脚有冲突但又不想在代码里反复编译烧录验证可以在进入STANDBY前的代码里加一个延时然后在这个延时期用万用表量目标引脚的电平。这样能快速确认引脚在进入STANDBY前最后一刻的实际状态比靠逻辑推断快得多。我用的调试手法是在HAL_PWR_EnterSTANDBYMode()之前加一个HAL_Delay(3000)然后在这个窗口期内用万用表量CSN、SCLK、MOSI、GPIO0这些关键引脚的电平全部确认无误后再去掉延时整机电流就是干净的3.2uA。这个方法在低功耗调试中非常实用推荐一试。
返回列表