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

资讯详情

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

树莓派Pico低功耗实战:从毫安到微安的完整指南

树莓派Pico低功耗实战:从毫安到微安的完整指南 我做树莓派 Pico 电池项目是从一个教训开始的第一次写完数据采集逻辑往代码里加了machine.lightsleep(60000)以为这事就完了。万用表串进去一测待机电流 12mA和没睡差不多。后来把板载 LED、ADC 引脚、GPIO 悬空、USB 连接挨个排查了一遍才把待机电流从毫安级压到微安级。回头看低功耗这件事从来不是“调一个 API 就完事”而是要从芯片的功耗状态、板级电路、外设状态三个层面同时下手。这篇文章就把树莓派 Pico 低功耗软件控制这条链路完整梳理一遍覆盖 MicroPython 和 C SDK 两套 API 的实际用法以及我在真实项目里通过万用表和 INA219 实测得到的数据和踩坑经验应该能帮正准备做电池供电项目的朋友少走不少弯路。1. 认识 RP2040 的四种功耗状态低功耗 API 背后到底在操作什么1.1 一张表看懂 Run / Sleep / Dormant / Shutdown很多人在网上搜“树莓派 Pico 低功耗”上来就问“哪个函数能休眠”但 API 只是表面那层皮。RP2040 芯片本身有四个差异极大的功耗状态理解它们之后你再看 SDK 和 MicroPython 的函数名就能清楚知道每个函数把你带到了哪个状态。状态进入方式CPU外设/时钟RAM唤醒方式电流量级参考Run正常运行运行全部可用保留-约 20~40 mASleepsleep_mode() / machine.lightsleep()停止大部分时钟保留保留任意中断约 1~3 mADormantdormant_run_from_xosc() / machine.deepsleep()停止只保留 XOSC 和唤醒逻辑保留GPIO、RTC 等指定源约 100~300 uAShutdown拉低 3V3_EN 或外部关断断电断电丢失重新上电约 1~5 uA这个表是我手上 Pico 在 3.3V 供电、无 USB、无调试器条件下的实测数量级不同批次和板卡会有浮动但趋势不会变。Sleep 本质上就是 Cortex-M0 的 WFIARM 里“停了 CPU 但世界还在转”的状态Dormant 是 RP2040 特有的它把几乎所有时钟停掉只保留外部晶振和极少唤醒逻辑有点像你关掉家里所有电器但留了一个门铃Shutdown 则完全是另一回事它不是软件指令而是把板载 3V3_EN 拉低让整个 3.3V 域断电RAM 里的东西全没了只能靠重新上电启动。有一类常见理解错误是觉得“休眠函数一定比主频函数省电”。实际上如果只调sleep_mode()但不处理外设和 GPIO电流可能只比运行模式低一点点因为外设时钟还在跑引脚漏电路径还在。低功耗是系统性工程API 只是开关。1.2 板级因素决定功耗下限电源芯片、指示灯、USB 都算在内新手很容易忽略一个事实你在树莓派 Pico 上测到的功耗不是 RP2040 芯片的功耗而是整个开发板的功耗。板载 RT6150 电源芯片、VBUS 检测电阻、电源 LED 都在消耗电流。甚至 USB 连接时芯片的 USB PHY 也会处于工作状态某些休眠模式下 USB 可能会阻止芯片进入更深的状态。因此有几条硬性要求测低功耗之前先确认没有插着 USB 线否则你测到的数字至少多出几毫安。板载电源 LEDPico 上电后常亮那颗无法用软件关闭它直接挂在 VBUS 检测电路上。追求极致功耗的产品建议换用 Pico 裸片或自己设计底板。板载 LEDGPIO25是可以用软件关闭的但很多人在初始化时把它拉高了休眠前忘了复位。如果你做的是可穿戴、传感器节点这类把“一个月的待机电流”看得比什么都重的项目板级功耗甚至比芯片休眠模式更重要。同样一颗 RP2040放在官方 Pico 板上和放在你自己画的 3cm×2cm 最小系统板上休眠电流可能差出几十微安。2. MicroPython 和 C SDK两套低功耗 API 的调用逻辑与执行差异2.1 machine.lightsleep()最简单但别忽略唤醒后的状态如果你用 MicroPython进入睡眠的 API 就是machine.lightsleep()。代码简单from machine import Pin, lightsleep led Pin(25, Pin.OUT, value0) # 先关掉板载LED print(准备睡5秒) lightsleep(5000) print(醒来了)调用lightsleep()之后CPU 停止外设基本保留RAM 完整唤醒后从下一行继续执行。可以传毫秒数也可以不传参数然后用引脚中断或定时器唤醒。系统时钟不会因为lightsleep()而丢失醒来后不需要重新初始化时钟。这里最容易被忽略的是“唤醒后继续执行”这件事。比如你在循环里准备休眠前把某些外设关闭了那么唤醒后要记得按顺序重新打开反过来休眠前调了machine.freq()降频唤醒后频率还停留在低频不会自动恢复。我见过有人说“为什么唤醒后程序变慢了”多半就是这个原因。lightsleep()还有一点要注意如果你在运行中开了很多 Timer、UART、I2C它们的中断可能会在 sleep 期间把你立刻唤醒甚至导致进不去睡眠。稳妥做法是休眠前禁用不必要的中断和外设sleep 结束后再恢复。2.2 machine.deepsleep() 在 RP2040 上要重新理解如果你是从 ESP32 或 STM32 转过来的得先把之前的“deep sleep”印象清空。RP2040 并没有一个像 STM32 那样“停掉一切、靠 RTC 唤醒并保留 RAM”的完整低功耗停机模式MicroPython 的machine.deepsleep()在 RP2040 上的底层实现走的是 dormant 模式。所以在 RP2040 上调用from machine import deepsleep, reset_cause, DEEPSLEEP_RESET print(进入深睡) deepsleep(10_000) # 10秒后唤醒 print(唤醒后这里会重新执行)它和lightsleep()有一个本质区别唤醒后的行为接近一次复位。虽然 dormant 状态本身 RAM 没有断电但 MicroPython 的解释器在唤醒后通常表现为重新启动此前创建的变量、对象基本都不在了。要判断当前是不是从深睡唤醒的可以看reset_cause()if reset_cause() DEEPSLEEP_RESET: print(这是从 deepsleep 回来后的首次启动)这一点决定了你的程序结构不要在 deepsleep 之后依赖任何“睡眠前保存的变量”要么把关键数据提前写到 flash要么在启动阶段重建所有状态。还有一个 RP2040 特有的限制休眠唤醒需要时钟源而 RTC 唤醒依赖 32.768kHz 外部晶振。树莓派 Pico 板子上没有这个晶振所以想用machine.deepsleep()的定时唤醒在标准 Pico 板上往往不可用或不准确。如果不焊接外部 32.768kHz 晶振就需要用 GPIO 唤醒或者外接一个独立 RTC 芯片来解决定时唤醒。提示不同版本的 MicroPython 固件RP2040 的 deepsleep 行为存在差异。我的建议是如果你准备在产品里用深睡一定先做一轮“唤醒后行为”验证别默认它能像 STM32 一样完美恢复现场。2.3 C SDK 的 sleep_mode / dormant_run_from_xosc 精确控制如果你想用 C SDK 做低功耗能控制的东西比 MicroPython 细得多。最基础的休眠是sleep_mode()#include pico/stdlib.h #include hardware/clocks.h // 先准备好唤醒源GPIO10 下降沿唤醒 void gpio_wake_handler(void) { // 唤醒后回调可以做标志位 } int main() { stdio_init_all(); gpio_init(10); gpio_set_dir(10, GPIO_IN); gpio_pull_up(10); gpio_set_irq_enabled_with_callback(10, GPIO_IRQ_EDGE_FALL, true, gpio_wake_handler); sleep_mode(); // 进入 Sleep任意中断唤醒 // 唤醒后代码从这里继续 return 0; }sleep_mode()保持当前系统时钟和外设状态唤醒成本最低。但如果你想在睡眠期间连 XOSC 和 PLL 一起关掉SDK 还提供了sleep_run_from_rosc()它会把系统时钟切到片内 ROSC然后睡睡眠电流更低代价是醒来后时钟源变成了 ROSC需要自己重新配置 XOSC/PLL 恢复频率。dormant 的入口是dormant_run_from_xosc()使用时需要先填一个dormant_source_t结构体说明允许哪些唤醒源#include hardware/structs/scb.h #include hardware/clocks.h #include hardware/pll.h #include hardware/xosc.h dormant_source_t source {0}; source.gpio.mask (1u 10); // 监听 GPIO10 source.gpio.edge GPIO_LOW; // 低电平唤醒 source.gpio.en true; // 使能 GPIO 唤醒 // source.rtc.en true; // 需要外接 32.768kHz 晶振才能可靠使用 dormant_run_from_xosc(source); // 醒来后继续执行但系统时钟可能还悬着习惯性重新初始化时钟 xosc_init(); clocks_init();用 dormant 时有几个关键经验进入 dormant 前要确保所有不用的外设时钟被关闭、GPIO 状态被设置好否则醒来时电流高不能怪芯片。dormant 期间 XOSCPico 板上的 12MHz 晶振必须保持运行它承担唤醒逻辑的时钟。RTC 唤醒在 Pico 板上不可用原因和 MicroPython 一节说过的一样没有 32.768kHz 晶振。除非你自己在 XIN/XOUT 上焊一颗并正确配置 RTC。2.4 软件方案之外用 3V3_EN 或负载开关做真正的软关机如果你追求的是“待机几个月”级别的功耗Sleep 和 Dormant 都不够因为它们还要维持 RAM、GPIO 状态、板载电源芯片电流很难压到个位数微安。真正的断电方案是把 3V3_EN 拉低。树莓派 Pico 的 3V3_EN 引脚直接控制板载 3.3V 稳压器。把它拉低整个 3.3V 域就断电了RP2040 和所有外设都不耗电只有连接到 VBUS 的一些路径还在漏电断电后电流可以做到个位数微安。但注意这个引脚一旦拉低MCU 自己也断电了所以“用 GPIO 拉低 3V3_EN 实现软关机”这个操作是有前提的——唤醒必须依赖外部电路。常见做法用一个 GPIO 控制一颗 NPN/NMOS把 3V3_EN 拉低实现软件关机用外部 RTC 芯片比如 PCF8523、DS3231的定时中断输出脉冲或者用物理按键把 3V3_EN 拉回高电平让系统重新上电。这种方式有点像在给设备装一个“可软件断开的电源总闸”牺牲了 RAM 和快速恢复能力换来了最低的待机功耗。很多工业电池传感器、门磁设备最终都落到这种架构上。3. 隐藏耗电点时钟频率、GPIO、外设和板载 LED 逐个排查3.1 降频和关闭 PLL省电第一步不进入休眠的时候降低系统时钟是立竿见影的省电方式。RP2040 默认 125MHz 或 133MHz 跑着降频到几十甚至十几 MHz电流可能直接砍半。这个操作在 C SDK 里就一行#include hardware/clocks.h bool ok set_sys_clock_khz(12000, false); if (!ok) { // 频率设置失败保持原配置 }在 MicroPython 里用machine.freq()import machine machine.freq(12_000_000) # 降低到 12MHz注意固件支持的具体频率档位降频要注意几点不是所有频率都能随便设MicroPython 固件支持哪些档位要看编译配置设成不支持的值会抛异常或静默失败。USB 需要 48MHz 的时钟。如果你在降频的同时还想用 USB 串口要么先初始化 USB 再降频要么保持一个 USB 可用的频率否则 USB 会失效。调试阶段就别在 USB 连着的情况下测低功耗了。降频后你对外设的时序要求比如 UART 波特率、PWM 频率仍需要满足需要测试确认。如果你用的是 C SDK还可以把 PLL 关掉来进一步省电。PLL 是芯片里把低频晶振倍频到高频的核心模拟电路它本身有一些静态功耗。降频到不需要 PLL 的档位后调用pll_deinit(pll_sys)和pll_deinit(pll_usb)把两个 PLL 关掉。3.2 GPIO 悬空和 ADC 引脚的漏电路径这个坑最隐蔽。休眠函数调用得再漂亮如果 GPIO 引脚一个个都悬空着外部干扰或内部上下拉造成的漏电也会让待机电流涨上去几十甚至几百微安。我在实测中见过一个项目只因为 20 个 GPIO 全部悬空dormant 电流从 0.2mA 涨到了 0.6mA。处理原则很简单进入低功耗前把所有不用的 GPIO 明确设置成“输出低”或“输入下拉”。MicroPython 里可以这样批量处理from machine import Pin idle_pins [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 26, 27, 28] for p in idle_pins: pin Pin(p, Pin.OUT, value0)C SDK 里对应for (int i 0; i 30; i) { if (i ! 使用中的引脚) { gpio_init(i); gpio_set_dir(i, GPIO_OUT); gpio_put(i, 0); } }要特别留意 ADC 引脚ADC0-ADC3 和 ADC_VREF。如果 ADC 外设处于使能状态且输入引脚悬空内部采样电路会形成一个不小的漏电路径。更麻烦的是 ADC_VREF 内部没有默认下拉休眠前建议在软件层面关闭 ADC 外设C SDK 里对adc_hw-cs置 0在硬件层面给 ADC_VREF 接一个几十 kΩ 的下拉电阻或者把所有 ADC 引脚设成数字输出低。如果你用 MicroPython 且发现休眠电流异常但 GPIO 都设置过了优先检查 ADC 相关引脚有没有参与外部电路悬空。3.3 休眠前必须释放的外设舵机、PWM、UART、无线模块很多外设即使“看起来没在执行任务”只要供电还在就在偷偷耗电。最常见的例子是舵机。如果你用树莓派 Pico 控制舵机注意 PWM 信号停止后舵机会失去目标位置并释放扭矩但舵机内部的控制电路仍然消耗电流而且某些数码舵机在失去信号后会反复尝试寻找信号电流反而异常。我现在的做法是舵机供电单独走一个负载开关或 P-MOS 管MCU 用一个 GPIO 控制进入休眠前先停 PWM再延时几十毫秒让舵机稳定然后切断舵机电源最后才进入休眠。其他外设UART如果外部设备还通过 TX/RX 拉高电平GPIO 输入脚可能被外部电平钳住造成漏电。休眠前可以置为输入下拉或输出低。I2C总线上拉电阻接到 3.3V只要上拉电阻存在始终有微安级电流路径。如果追求极致用 GPIO 控制上拉电阻的供电或干脆关掉。SPI FlashPico 板载的 SPI Flash 在休眠时通常会进入 standby但不要让 CS 一直保持有效否则 Flash 会一直处于活跃状态。无线模块如果你用的是 Pico WCYW43439 无线芯片是另一个功耗黑洞需要额外处理它的 sleep / power save 模式。如果用的是外接 LoRa、BLE 模块强烈建议在硬件设计上把模块的 VCC 单独开关因为模块自己的休眠电流往往比单片机还大。4. 实测功耗的方法与一组参考数据4.1 万用表串测的坑与正确姿势不测不知道测了才知道低功耗代码写得对不对。最简单的电流测量方式是把万用表串进电源回路但要小心几个坑万用表有内阻虽然电流档内阻很小但在微安级时读数会偏低或抖动建议用电流分辨率至少 1uA 的表。系统启动瞬间有浪涌电流尤其在板载电容充电时。直接串表你可能因为表上保险丝断开或在量程切换中错失真实读数。自动量程的表在微安和毫安之间切换时会因为切换电阻造成瞬间压降导致系统复位或读数不稳。我的做法是在供电和 Pico 电源入口之间串一个 10Ω 采样电阻用示波器或万用表 mV 档量电阻两端压降再用欧姆定律换算电流。这样既不影响系统稳定性也能看到瞬时波形。如果你手边只有普通万用表测的时候至少要保证电源稳定、USB 断开、系统已经完成启动进入休眠、表笔接触良好。多次读数取中间值。4.2 INA219 连续记录电流曲线低功耗系统是“大部分时间在睡偶尔醒来工作”这种动态电流用万用表很难看全更推荐用 INA219/INA226 这类电流传感器连续记录。INA219 可以接在电源和负载之间通过 I2C 上报电流上位机或另一块 MCU 定期读取。接线要点INA219 的 VIN- 串接在电源正极和 Pico 的 VSYS 或 3V3_OUT 之间取决于你要测哪一级SDA/SCL 接 Pico 的 I2C 引脚采样电阻默认是 0.1Ω适合电池供电设备如果待机电流到微安级可以把采样电阻换大一些比如 1Ω提高小电流分辨率但会带来更大压降需要权衡。用上位机 Python 脚本定时读数据就能画出“运行 30mA、休眠 0.3mA”的电流曲线非常直观。4.3 一组实测数量级与电池寿命估算我最近做的一个土壤传感器节点环境是3.3V 供电断开 USB断开调试器官方 Pico 板实测数据大致如下场景实测电流备注133MHz 空转 while(1)约 24 mA没做任何优化降到 12MHz 空转约 6 mA降频收益明显machine.lightsleep(60000)约 1.8 mA保留外设板载电源静态电流占大头C SDK dormant_run_from_xosc GPIO 全部设置好约 0.21 mA还要继续优化到 uA 级就得断电拉低 3V3_EN 完全断电约 3 uA此时 MCU 无电只有板级漏电拿这个量级算笔账一颗 1000mAh 的锂电池如果 MCU 一直空转理论续航 1000÷24≈41 小时如果改成每 10 秒醒一次醒 50ms 做采集其他时间用 dormant平均电流大约是35mA × 0.05s ÷ 10s 0.21mA × 9.95s ÷ 10s ≈ 0.38mA1000mAh 能撑约 2600 小时也就是 100 天左右。就算加上传感器和无线模块的 2~3mA 后台功耗也比一直空转强一个数量级。这个计算方式可以套到任何项目里把运行时间和休眠时间按比例加权求平均电流再反推电池寿命。需求方问“能用多久”时这种估算比拍脑袋可靠得多。5. 真实项目的低功耗决策休眠模式、电源域、唤醒源怎么选5.1 根据业务场景选择休眠深度没有最好的休眠模式只有最合适的。我的选择标准大概是这样如果要求唤醒后立刻恢复现场、响应延迟毫秒级比如做工业控制器用lightsleep()/sleep_mode()不要轻易上 deepsleep。如果唤醒后重新初始化可以接受且你不依赖太多外设状态用 dormant /deepsleep()功耗能低一个数量级。如果设备要待机几天甚至几个月中间才醒一次直接用断电方案拉低 3V3_EN 或负载开关配合外部 RTC 定时上电。这里有一个很多人没提到的关键点dormant 模式在 RP2040 上其实保留了 RAM但 MicroPython 的 deepsleep 唤醒后相当于重启所以不管底层 RAM 是否保留你都不能把代码逻辑建立在“RAM 还在”的假设上。最稳的做法是把关键状态写入 flash或者只依赖外部 RTC 记住时间。5.2 外设电源域单独控制把耗电外设锁起来低功耗项目做到后面真正决定续航的往往不是 MCU而是外设。传感器的测量电流、无线模块的待机电流、舵机的静态电流每一项都可能比 RP2040 休眠时大得多。所以我现在画板子时强制自己把外设供电分成几路每一路用一个 GPIO 控制的 P-MOS 或负载开关芯片接通传感器电源只在采样前打开几百毫秒采完立刻关无线模块电源只在需要上报时打开平时彻底断电舵机电源独立开关休眠前先释放 PWM 再断电电平转换芯片、运放等辅助电路尽量合并到上述电源域里一起管理。这样休眠时MCU 只需要承担自己的那点微安级别电流外设的“偷电”路径全部被物理切断。功耗优化到了一定阶段你会发现剩下的问题都是外设供电管理而不是 MCU 休眠。5.3 唤醒源选择的实操建议休眠方案定了接下来是“怎么醒”的问题。GPIO 唤醒最简单在 dormant 或 sleep 模式下都可以用。需要注意唤醒电平要保持到唤醒逻辑真正捕获为止。比如用按键唤醒如果不做去抖人按下去那一下的抖动可能造成多次唤醒用传感器报警信号唤醒要确保信号电平能够维持足够的时间否则 MCU 刚醒信号已经消失逻辑上很容易产生误判。如果用外部 RTC 芯片定时唤醒优先选超低功耗型号比如 PCF8523 的典型电流不到 1uADS3231 虽然精度高但要 3uA 左右。让 RTC 芯片的 INT/SQW 引脚输出低电平脉冲接 MCU 的 GPIOMCU 在 dormant 模式下被拉低唤醒或者通过负载开关电路直接给系统重新上电。这种方案在“每天固定时间上报一两次数据”的场景非常好用MCU 完全不需要关心时间戳时间戳由 RTC 维护上电后直接读即可。还有一种做法是用看门狗或内部定时器做周期唤醒但 RP2040 没有独立的低功耗看门狗时钟定时精度和省电效果都受限制不推荐在追求低功耗的场景里依赖它。6. 低功耗调试注意事项汇总我踩过的坑和排查思路6.1 现象与根因对照表现象可能的根因处理方式休眠后电流还有十几 mA板载 LED 没关USB 仍连着无线模块没进入低功耗检查 GPIO25、拔掉 USB、单独管理无线模块dormant 下电流 0.6mA 但期望 0.2mAGPIO 悬空产生漏电休眠前把所有空闲引脚设为输出低或下拉调用 lightsleep 后立刻醒定时器、UART、I2C 中断还在工作休眠前禁用不必要的中断和外设deepsleep 后变量全部丢失RP2040 的 deepsleep 唤醒像重启不要依赖 RAM 内容把关键状态写 flash 或重建唤醒后程序很慢降频后没恢复系统时钟唤醒后重新配置频率USB 连着测功耗偏高USB PHY 和 VBUS 检测电路在工作用电池或独立电源供电测试舵机休眠时还嗡嗡响PWM 停了但舵机供电还没断独立切断舵机电源6.2 一个完整排查流程示例如果你把低功耗代码写好了测出的电流却远远高于预期我建议按下面的顺序排查不要在盲目换函数上浪费时间。第一步确认测量环境断开 USB、断开调试器用稳定的 3.3V 电源供电。只留供电和地两条线其他跳线全部拔掉。先测基础电流再对照正常值。第二步检查基础外设把 GPIO25 拉到低电平关闭不需要的外设或至少确保 ADC 引脚不悬空停掉不需要的 UART、I2C、SPI。第三步逐个检查 GPIO用一个循环把所有未使用引脚设成输出低观察电流变化。如果某一组引脚设置后电流明显下降说明问题就在那里。第四步确认休眠模式是否真的进入了在休眠前放一个标志比如某个 GPIO 拉高表示“准备睡”用示波器观察该引脚的跳变时间和休眠时长确认系统确实睡了那么久而不是秒醒。第五步隔离外设与外接模块把所有外设的供电断开只保留 MCU再测一次。如果电流下降到可接受水平说明外设才是“偷电大户”回来处理电源域。这套流程走下来90% 的低功耗问题都能定位。我给自己项目的检查清单基本就这么写每次改完硬件或者固件重新跑一遍心里特别有底。最后补一个我个人习惯的技巧在 PCB 设计阶段尽量在电源路径上留一个 0Ω 电阻或跳线焊盘方便后续把万用表或 INA219 串进去再留一个 GND 和 VSYS 的测试点实测电流时不用临时找焊点。硬件留好了测量口子低功耗调试会顺畅非常多。
返回列表