尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GD32三种低功耗模式详解:从睡眠到待机,续航从月变年

GD32三种低功耗模式详解:从睡眠到待机,续航从月变年 前阵子帮朋友改一个温湿度记录仪主控是GD32F303。原来装3节AAA电池一个月出头就得换朋友怀疑单片机漏电拿万用表一测正常运行电流确实只有十几毫安问题在于它几乎从不睡觉偶尔进一下低功耗也是那种“掩耳盗铃”式的假睡——核心停了外设还在跑电流一点没降下来。后来我把GD32的睡眠、深度睡眠、待机三种低功耗模式完整梳理了一遍产品重新设计后换成CR2032纽扣电池续航从“月”变成了“年”。这篇就把三种模式讲透每个模式到底关了哪些东西、代码怎么写、唤醒源怎么选、实测功耗怎么量以及从STM32迁过来容易踩的坑。正在做电池供电设备、或者打算用GD32替代STM32做低功耗项目的朋友可以参考。1. 三种模式到底关了什么、留了什么GD32的低功耗模式不是简单地把主频降下来而是从内核、时钟、外设、SRAM、电源调节器这几个维度分别做取舍。想用好它先得搞明白每个模式的动作范围。1.1 睡眠模式内核休息外设继续上班睡眠模式对应的是Cortex-M3内核的WFI/WFE指令。执行之后CPU停止取指、停止执行指令但系统时钟仍然在跑所有外设都在正常运转。也就是说USART收到数据能产生中断定时器到点能触发中断DMA照常搬运数据SysTick也还在一毫秒一毫秒地数。这个模式的好处是唤醒速度极快。因为时钟没有停中断来了之后CPU几乎不需要恢复时间直接进中断服务程序处理完回到WFI之后的下一行代码继续跑。它的代价是功耗降低有限。内核停了但外设时钟和Flash电路都还在耗电。GD32F303在8MHz主频下睡眠模式实测电流大约在1mA上下比正常运行十几毫安低了一个数量级但对纽扣电池来说还是太高只能在短时间等待场景里用。适用场景很明确CPU在等一件很快发生的事比如等待DMA搬运完成、等待串口发完数据、等待用户按键去抖。如果这个等待时间超过10ms就值得考虑深度睡眠。1.2 深度睡眠模式时钟全停SRAM保住深度睡眠模式在GD32里对应的是Cortex-M3的SLEEPDEEP位加电源控制寄存器配合。进入之后HSE、HSI、PLL全部被关闭所有外设的时钟都停了CPU自然也没法执行代码。但SRAM内容保留寄存器状态也保留代码中定义的全局变量、当前程序运行到哪一步这些信息都不会丢。这个“状态保留”非常关键。唤醒之后不需要像复位那样重新初始化一切你可以把现场数据继续用甚至可以在中断服务程序里判断一下醒来原因再决定下一步干什么。深度睡眠的功耗比睡眠低了一个量级。我实测GD32F303在深度睡眠、LDO低功耗模式下整机电流大概在9μA左右。如果外设引脚处理得当还能再低一点。这个量级已经适合纽扣电池做定时唤醒的数据采集了。需要注意一点深度睡眠模式并不是只有一个固定配置。GD32库函数的第二个参数可以选择稳压器工作在正常模式还是低功耗模式。低功耗模式下省电但唤醒后稳压器需要时间恢复不能立刻支撑大电流负载。如果唤醒后要马上跑无线发射或者点亮大屏建议用正常模式或者唤醒后先加延时再上高负载。1.3 待机模式除了唤醒逻辑全部断电待机模式是GD32最狠的一档。进入待机后内核、SRAM、外设寄存器、绝大部分时钟全部断电。整个芯片只剩下备份域、待机唤醒电路、以及复位移除相关逻辑还在工作。SRAM里的数据会丢所有全局变量重新上电后都是初值。唤醒后的行为等效于一次复位。芯片会从启动向量重新开始执行SystemInit会重新跑一遍main函数会重新进入。你想知道自己是“第一次上电”还是“从待机唤醒”只能通过复位标志和备份寄存器来判断。因为断电范围最大功耗自然也最低。GD32F303待机模式实测整机电流在1.8μA左右。这个数字非常接近一块CR2032电池的自放电水平。代价是唤醒后的恢复成本高。深度睡眠醒来后是“接着往下走”待机醒来后是“从头再来”。所有外设要重新初始化所有现场数据要不就得存在备份寄存器里要不就得从Flash重新加载。如果你做的是智能门锁、烟雾报警器、电子标签这种“平时完全休眠来事件或定时器触发后才干活”的产品待机模式是首选。如果产品需要频繁唤醒比如每秒钟都要采样一次传感器待机模式反而不合适因为每次都从头初始化这段时间的电流和工作量反而会拉高平均功耗。1.4 一张表看穿GD32的三档功耗项目睡眠模式深度睡眠模式待机模式CPU内核停止停止掉电系统时钟继续运行全部关闭全部关闭外设时钟继续运行全部关闭掉电SRAM内容保留保留丢失寄存器状态保留保留丢失唤醒源任意中断/事件EXTI、RTC等WKUP引脚、RTC闹钟、NRST唤醒后行为继续执行继续执行等效复位典型电流实测参考1mA级9μA级1.8μA级典型应用短时等待定时采集按键唤醒、长期待机这张表基于GD32F303实测不同系列和不同主频会有差异但相对关系是一致的。搞清楚了这层就能避免一个常见错误不是所有低功耗需求都得用待机有时候深度睡眠反而更合适。2. 从库函数到底层寄存器进入低功耗的动作拆解GD32标准外设库把低功耗模式封装得很简单一个函数就能进去。但只背函数名永远调不准因为不同系列的库函数参数有差异而且进入前后要处理的事情远比函数本身多。2.1 睡眠模式为什么一行代码就够了睡眠模式的标准写法pmu_to_sleepmode(WFI_CMD);有的系列库函数里叫pmu_to_sleepmode有的叫pmu_to_sleep但核心动作就是执行CPU的WFI指令。所谓WFI就是Wait For InterruptCPU停在这里直到一个中断到来。这里有个很多人第一次用会理解错的点WFI被中断唤醒后CPU会先执行中断服务程序执行完毕再返回WFI的下一条指令。也就是说你不能在WFI后面紧跟一句“唤醒后处理逻辑”的代码指望它立刻执行。正确的做法是把“唤醒后干什么”写在中断服务程序里或者用标志位在WFI返回后再处理。如果你希望中断返回后不回到原来的执行流而是直接再睡过去可以设置Cortex-M3的SLEEPONEXIT位。这在纯中断驱动的程序里很常见主循环只做一件事——进睡眠所有工作都在中断里完成。2.2 深度睡眠SLEEPDEEP、LDO参数和WFI的关系深度睡眠的标准写法pmu_to_deepsleepmode(PMU_LDO_LOWPOWER, WFI_CMD);这句函数内部做了三件事。第一设置SCB系统控制寄存器里的SLEEPDEEP位。这一位是决定WFI/WFE指令执行后到底进入普通睡眠还是深度睡眠的关键。睡眠模式不设这一位深度睡眠设置这一位。第二配置电源控制寄存器把模式指向深度睡眠而不是待机。GD32的电源控制寄存器里有一个模式选择位这位置0就是深度睡眠置1就是待机。库函数帮你区分好了。第三选择LDO的工作模式。PMU_LDO_LOWPOWER表示稳压器进入低功耗模式进一步省电如果选择PMU_LDO_NORMAL稳压器保持正常输出唤醒后负载能力更强。执行WFI后芯片才会真正睡过去。所以库函数的形式是“配置模式参数 然后WFI”不是单独一个模式切换指令。唤醒后要特别注意深度睡眠把HSE、HSI、PLL都关了即使你原来跑的是内部HSI唤醒后时钟树也不会自动恢复原样。正确做法是唤醒后调用一次系统时钟初始化函数把时钟重新配置好再操作外设。这一步漏掉最常见的现象就是串口波特率变乱、定时器时间不对、ADC采样值跳动。如果用的是GD32标准外设库的工程模板时钟初始化的函数一般在system_gd32f10x.c里叫SystemInit。你可以在唤醒后重新调用它再把外设的时钟使能和分频重新做一遍。2.3 待机模式的额外几步与唤醒后的复位行为待机模式的标准写法pmu_backup_write_enable(); pmu_flag_clear(PMU_FLAG_STANDBY); pmu_wakeup_pin_enable(PMU_WAKEUP_PIN0); pmu_to_standbymode(PMU_LDO_LOWPOWER, WFI_CMD);进入待机前必须做三件事。第一使能备份域写访问。因为待机模式下备份寄存器是唯一能保存用户数据的地方而备份域默认写保护。调用pmu_backup_write_enable()取消写保护后才能操作备份寄存器和RTC。第二清掉待机标志位。GD32的电源控制寄存器里有个Standby标志记录芯片是否发生过待机唤醒。如果不清除它可能影响下一次进入待机的判断逻辑甚至在某些系列里导致意外行为。养成进入前清标志、唤醒后读标志并清除的习惯能少很多坑。第三使能WKUP唤醒引脚。待机模式的唤醒源有限常用的引脚唤醒必须提前配置。WKUP引脚通常是PA0上升沿触发。你需要在硬件上把PA0接一个按键到高电平或者接一个默认低电平、事件到来时拉高的信号源。引脚如果悬空噪声导致的反复上升沿会把芯片从待机里频繁叫醒整机功耗直接报废。执行pmu_to_standbymode后芯片立刻断电所有代码执行流到此为止。唤醒后芯片从启动向量重新跑你可以把它理解为一次“没有按键复位的上电过程”。2.4 唤醒之后的第一件事重新喂饱时钟这个建议针对深度睡眠因为待机唤醒本身就是复位会自动走SystemInit。很多人在深度睡眠唤醒后只做外设重新初始化忘了时钟。看起来代码没问题但实测就是不对。原因在于深度睡眠把PLL关掉了如果你原来主频来自PLL唤醒后CPU可能还在用低速时钟或者时钟没恢复彻底。我的习惯是写一个system_clock_recover()函数里面包含调用SystemInit()重新配置系统时钟重新使能需要用到的外设时钟重新配置串口波特率相关寄存器如果外设初始化里没做把这个函数放在所有唤醒处理的最前面然后再判断唤醒源、恢复外设。这样做之后深度睡眠和睡眠的切换就非常稳定了。3. 唤醒源设计什么时候该醒来用哪个源低功耗模式选得再好唤醒源设计不对整个系统还是白搭。唤醒源决定了你能不能在正确的时间醒来以及醒来后能不能快速定位自己为什么醒了。3.1 RTC闹钟定时唤醒数据采集场景首选定时唤醒最常用的是RTC闹钟。比如温湿度记录仪每5分钟醒一次采集数据、存Flash、继续睡。这种场景用RTC最合适因为深度睡眠和待机模式下只要备份域还在供电RTC就能靠32.768kHz晶振继续跑。RTC配置一般分几步使能备份域访问和RTC时钟选择RTC时钟源通常是LXTAL外部低速晶振配置预分频得到1Hz的计数时钟设置闹钟值使能RTC闹钟中断或事件唤醒伪代码如下rtc_configuration(); rtc_alarm_config(rtc_counter_get() 300); /* 5分钟 */ nvic_irq_enable(RTC_IRQn, 2, 0); pmu_to_deepsleepmode(PMU_LDO_LOWPOWER, WFI_CMD);要注意的是RTC承载了时间基准进入低功耗前千万不要再把RTC相关寄存器重新初始化一遍否则时间会跳变。正确做法是初始化一次后以后每次唤醒都不动RTC配置只读计数或设置下次闹钟。还有一点备用电池的设计。深度睡眠和待机模式下如果主电源完全断开但RTC还要继续走必须有VBAT备用电源。很多开发板直接把VBAT接主电源这样整机断电后RTC时间会丢。如果是做需要长期走时的产品VBAT必须接纽扣电池或超级电容。3.2 EXTI与WKUP引脚按键唤醒的门道睡眠模式和深度睡眠模式可以被EXTI外部中断唤醒。你可以把GPIO配置成下降沿或上升沿触发比如门磁传感器报警、人体红外触发、按键按下都存在EXTI线上从而唤醒芯片。待机模式的引脚唤醒不一样。它不能走EXTI中断只能靠专用的WKUP引脚上升沿。这是因为待机模式下NVIC都已经掉电了中断机制不工作唤醒只能靠独立的电源管理逻辑。具体到GD32F103系列WKUP引脚是PA0。如果你的产品上电默认PA0是低电平按键按下拉高就可以用待机模式做到“按一下起来工作再按一下继续睡”的效果。注意WKUP引脚唤醒后不需要清中断标志因为它本质是事件唤醒。但如果你想区分唤醒原因还是要读电源控制寄存器的标志位。按键唤醒有个硬件细节按键两端最好并联一个100nF电容滤除机械抖动。虽然WKUP唤醒不经过CPU毛刺也可能直接触发唤醒造成设备“没按却醒了”。加电容后能明显减少误唤醒。3.3 用复位标志判断“我是怎么醒的”待机唤醒后代码会从main重新跑如果你不在main开头判断唤醒原因就不知道系统是上电还是被唤醒。好在GD32提供了标志位。if (RESET ! pmu_flag_get(PMU_FLAG_STANDBY)) { /* 从待机模式唤醒 */ pmu_flag_clear(PMU_FLAG_STANDBY); /* 从备份寄存器恢复现场 */ }对于深度睡眠由于不经过复位你可以在唤醒函数里直接判断当前走到哪一步不需要标志位。但为了排查问题我建议每次唤醒后记录一个递增计数存到备份寄存器里方便后期确认唤醒次数和异常复位次数。不要把关键现场数据只存在普通全局变量里。待机模式SRAM会丢只有备份寄存器能保留数据。GD32的备份寄存器数量足够存几个关键状态比如设备编号、唤醒次数、上次采集时间。每次进入待机前写备份寄存器唤醒后读出来比存在Flash快得多也不存在擦写寿命问题。4. 实测功耗与电池寿命别只看数据手册数据手册上的低功耗电流都是在理想条件下测的没有外设、引脚不浮空、LDO配置最优。实际产品里一个LED下拉电阻就可能吃掉你几十微安。想判断一个方案能不能用必须自己测自己算。4.1 功耗测量万用表、采样电阻、功耗分析仪最入门的方法是万用表串联电流档。但低功耗测量有个很阴的坑万用表在mA档和μA档之间切换时内阻不一样。你用μA档测低功耗电流内阻可能高达几百欧设备一醒来电流瞬间拉高在这几百欧上产生的压降就足以让芯片欠压复位。结果就是你越认真地测低功耗板子越容易反复复位。我的做法是两种用一台支持自动量程的数字万用表测平均电流大概判断量级。在电源回路串联一个10Ω精密采样电阻用示波器测电阻两端电压再除以10得到电流波形。示波器可以看到睡眠、唤醒、工作的完整电流变化过程定位哪个阶段耗电。如果条件允许强烈建议用功耗分析仪或者J-Link配套的能量分析工具能直接给出电量积分算出每个阶段的电荷消耗。测量时还有两个细节。第一测试前把所有串口打印关掉。调试串口连着USB转串口模块时模块会从目标板取电测出来的电流根本不是单片机的。第二程序进入低功耗后把调试器断开。J-Link、ST-Link这些调试器会维持内核调试接口的时钟导致芯片无法进入最低功耗。4.2 从μA到年电池寿命估算全过程以我实测过的GD32F303温湿度记录仪为例设计参数如下深度睡眠电流9μA每5分钟唤醒一次每次醒来工作50ms工作电流12mA其余时间深度睡眠计算平均电流每次循环时间是300秒工作50ms即0.05秒睡眠299.95秒。工作阶段占空比0.05 / 300 0.0001667平均电流 12mA × 0.0001667 0.009mA × (1 - 0.0001667)这一步算下来大约是0.002mA 0.0089985mA也就是0.011mA左右约11μA。如果改用睡眠模式背景电流1mA平均电流会变成大约1.002mA直接差了90倍。电池容量用CR2032额定容量210mAh考虑实际可用打八折约168mAh。寿命估算168mAh / 0.011mA ≈ 15272小时 ≈ 636天接近1.7年。如果换成待机模式背景电流降到1.8μA但唤醒后要重新初始化等工作时间可能从50ms变成150ms平均电流大约是12mA × 0.0005 0.0018mA × 0.9995 ≈ 0.0078mA寿命可以拉到2.5年以上。这个对比说明一个道理决定电池寿命的不只是睡眠电流还有唤醒频率和工作时间。唤醒越频繁越要权衡“省电模式”和“唤醒恢复开销”之间的关系。4.3 那些偷偷吃掉微安的“隐形漏洞”只优化单片机不优化整板低功耗设计就失败了一半。常见偷电点有几个。GPIO浮空排第一。芯片进入低功耗前没被初始化、或者被配置成浮空输入的引脚会通过输入缓冲产生漏电流一个引脚几百纳安到几微安不等几十个引脚加起来非常可观。进入低功耗前把所有用不到的GPIO统一配置成模拟输入或者输出低电平能显著降低整板电流。LED限流电阻是第二号元凶。很多人只关了LED但限流电阻一端连着GPIOGPIO输出低电平时电阻上的电流确实是零。问题是如果GPIO配置成高电平或者浮空电阻上就有电流。更隐蔽的是LED的漏电流很小但串联的1k电阻如果接在3.3V电源和地之间哪怕没有LED电阻本身就有毫安级电流。所以不用LED时应该把对应GPIO配置成模拟输入而不是只输出低电平。板上稳压器是第三号。很多LDO静态电流5μA起步你单片机再怎么降到2μA整机还是有七八微安。如果做长期待机选静态电流低于1μA的LDO才有意义或者用负载开关在待机时把不用的电路断电。滤波电容漏电和电阻分压电路也是隐藏开销。特别是有电池电压检测分压电阻的产品两个100k电阻直接挂在电池两端就是33μA的持续电流比你单片机待机还高一截。正确做法是用MOS管或者单片机GPIO控制分压电阻的接地端只在检测时接通。5. 移植和调试阶段的坑从STM32过来尤其注意很多团队用GD32是因为它可以兼容STM32。但这个“兼容”主要体现在引脚和软件框架上低功耗部分还是有不少差异。坑踩多了总结成下面几条。5.1 库函数版本和寄存器命名不是ST的翻版GD32早期对外设库的设计参考了STM32标准外设库但函数名、参数定义和寄存器位命名都改过。直接拿STM32的低功耗例程往GD32上套编译都过不了。比如STM32F1的PWR_EnterSTOPModeGD32对应的是pmu_to_deepsleepmodeSTM32的PWR_EnterStandbyModeGD32对应的是pmu_to_standbymode。参数也从“模式枚举”变成了“LDO模式 WFI/WFE命令”。更重要的一点不同系列的GD32库函数也有差异。GD32F10x、GD32F30x、GD32E230、GD32L233它们的PMU外设设计不完全一样库函数接口也不完全一样。项目里一定要以你所用系列对应版本的固件库为准。我见过有人把GD32F30x的例程直接拷到E230上函数名碰巧一样但底层寄存器偏移都对不上调试了半天。我的做法是尽量用官方提供的GD32 Embedded Builder或官方固件库模板而不是自己从零拼。官方模板里的system_gd32系列文件已经把时钟初始化和启动文件配好了低功耗相关的PMU驱动也在库里省很多事。5.2 调试器一接芯片就睡不着这是低功耗调试里最挫败的一种情况代码看起来完全正确传感器电流就是降不下来。折腾半天发现是J-Link还插着。调试器为了维持调试会话会强制芯片保持调试时钟低功耗模式根本进不去或者进去后立刻被调试请求唤醒。不只是在线调试哪怕程序已经烧录完毕但J-Link的线还连着目标板的SWD引脚也可能有影响。低功耗调试建议按下面的流程先把程序烧录到Flash拔掉调试器用外部电源或电池给板子供电用万用表或功耗分析仪测电流如果在Keil里必须在线调试可以在进入低功耗前设置一个断点等芯片睡过去后调试器就会报“Cannot access target”这时候基本可以确定睡眠代码执行到了。不要试图在这种状态下继续单步只会把芯片踩醒。GD32在Linux下的开发环境也一样用gcc工具链交叉编译J-Link的JLinkExe烧录流程没问题但低功耗测量必须断开调试器这是硬规则。5.3 唤醒后外设时钟没恢复串口乱码、ADC乱跳深度睡眠唤醒后系统时钟可能还处于未恢复状态。如果你直接发串口数据波特率是按错误的时钟算的出来就是乱码。ADC采样也一样采样时间、转换时间全部依赖ADC时钟时钟不对结果就不稳定。这个问题的排查方法很简单在唤醒处理函数最开始重新调用系统时钟初始化然后再初始化外设。如果你用的是HSE PLL倍频配置尤其要注意因为PLL在深度睡眠期间是关断的唤醒后不会自动重新锁定。还有一个容易忽略的点如果深度睡眠前用了低功耗LDO模式唤醒后刚开始给外部负载供电的能力有限。如果你一醒来就开始执行Flash写入或者RF发射可能把电压拉低导致复位。稳妥的做法是唤醒后先做51微秒级延时或者把LDO切回PMU_LDO_NORMAL等稳压器稳定再干重活。5.4 GPIO浮空不只是费电还可能直接唤醒不成功GPIO浮空的第一个危害是漏电流第二危害更隐蔽它可能让唤醒源无法正常工作。比如WKUP引脚PA0如果你在硬件上外接了一个按键到GND按下后拉低但单片机上没配内部上拉那待机模式下引脚状态不确定系统可能反复唤醒也可能完全唤不醒。GD32的WKUP唤醒方向是上升沿要求默认状态是确定的低电平事件来时拉高。如果引脚悬空上升沿的触发条件不成立。建议硬件设计时给WKUP引脚加一个外部下拉电阻10k或者芯片支持内部下拉就配置内部下拉确保默认电平确定。对于其他GPIO进入低功耗前的统一处理方式gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_ALL); gpio_init(GPIOB, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_ALL); gpio_init(GPIOC, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_ALL);把用不到的引脚全部设成模拟输入既避免了漏电流也不影响芯片状态。唤醒后再恢复成实际需要的功能。但要注意有些引脚承担特殊功能比如WKUP、RTC晶振引脚、复位引脚不能一股脑配置成模拟输入。做批量配置前先过一遍原理图把特殊引脚摘出来。最后分享一个我调试低功耗的习惯永远先测“最小系统的底电流”。也就是只保留MCU、最小复位电路、电源电路跑一个空循环进入深度睡眠的测试程序测出芯片本身的底电流。然后一层层加代码、加外设、加中断每一步重新测一次电流。这样一旦电流异常立刻能锁定是哪个环节引入的。别一开始就整套代码直接睡眠那样出了问题根本找不到元凶。低功耗设计没有太多玄学就是把模式选对、唤醒源接对、引脚管好、电流测准。做到这四步电池供电的设备就能稳定跑上一年以上。
返回列表