树莓派 Pico 低功耗实战:从 light sleep 到 dormant 的功耗压榨指南
2026/9/10 4:31:08 网站建设 项目流程

低功耗这三个字,放在树莓派 Pico 上,很多人的第一反应往往是——“一个开发板而已,省那点电有什么意义?”这种想法我太熟悉了,直到我自己做了一个电池供电的田间环境采集节点,在户外跑了快两个月,才明白低功耗软件控制这件事的含金量到底在哪里。树莓派 Pico 用的 RP2040 芯片本身并不是以低功耗见长的 MCU,但正因为这样,它的功耗优化空间反而更值得挖:同一个项目,如果只是“能跑”,整机休眠电流可能在 20mA 以上;如果把 MicroPython 或 C SDK 提供的电源管理 API 吃透,配合合理的外设处理和供电方案,可以把待机电流压到 1mA 以内,甚至在 dormant 模式下做到 0.2mA 左右的水平。这个差距,决定了设备是“一周一充”还是“半年不管”。

这篇文章我会直接切入正题,分两条线讲:一条是 MicroPython 路线,适合快速验证和中小规模项目;另一条是 C SDK 路线,适合把潜伏电流往极致压。重点不是贴一堆 API 文档,而是说清楚每个 API 背后到底做了什么、为什么这么做、在真实板子上会踩哪些坑。包括 GPIO 唤醒、定时唤醒、外设关闭、供电方案、电流测量方法、常见故障排查,都会给到可直接抄作业的参考实现。

1. 低功耗软件控制到底在控制什么

1.1 搞清楚“低功耗”的对象:RP2040 的功耗模型

很多人把低功耗设计理解成“程序里调用一个睡眠函数就完事了”,这是最常见的误区。真实情况是,睡眠函数只解决 MCU 核心的功耗问题,整个板子的功耗还包括外设、电源转换电路、LED、电平转换芯片、USB 接口等一大堆东西。所以在写代码之前,先要清楚 RP2040 本身到底有哪几种功耗状态。

RP2040 的电源域可以粗略分为四个层级:

  • 运行模式(Run):CPU 全速或降频运行,所有外设可工作,电流通常在 20mA 以上(板载 LED、USB、外设另算)。
  • Sleep 模式:CPU 时钟停止,但外设时钟和系统时钟仍保持,RAM 内容不丢失,唤醒速度最快。MicroPython 里对应machine.lightsleep(),C SDK 里对应sleep_run()sleep_goto_sleep()
  • Dormant 模式:几乎所有时钟都停掉,包括系统 PLL 和大多数外设时钟,只保留少数唤醒源(GPIO 边沿、RTC 等)。这是 RP2040 真正意义上的深度休眠,MicroPython 里对应machine.deepsleep(),C SDK 里对应sleep_goto_dormant_until_edge()sleep_goto_dormant_until_rtc()
  • 外部断电:把板子的供电直接切掉,功耗为零,但 RAM 内容丢失,唤醒后是全冷启动。

这里关键要理解的是:睡眠不是“程序暂停”,而是“时钟树裁剪”。睡眠层级越深,被关掉的时钟就越多,唤醒后重新启动这些时钟的时间就越长。所以低功耗设计本质上是在“功耗”和“响应速度”之间做权衡,不是一味追求最深的睡眠模式。

我在实际项目里的经验是,如果业务逻辑每次唤醒只需要做“读传感器、存数据、发消息”这几件事,对唤醒延迟不敏感,那就优先考虑 dormant 级别的深度睡眠;如果业务逻辑需要快速响应外部事件,比如按键、中断信号,那就选择 sleep 模式,同时把不需要的时钟主动关掉。

1.2 为什么选择软件控制为主,少谈硬件改动

低功耗项目的常见土办法是“硬件改板子”:去掉 LDO、拆掉电源 LED、外接低功耗 DC-DC 模块。这些做法当然有效,但不是每个项目都有改板的成本和时间。而且软件层面的优化往往被低估——在同一个硬件板子上,仅仅通过合理调用电源管理 API,把休眠电流从十几毫安降到一两毫安是完全可能的。

软件控制的核心思路可以总结成一句话:把不需要的东西全部停掉,在需要的时候以最快速度恢复。这里的“不需要”包括:用不到的定时器、ADC、PWM、I2C、SPI、UART 外设,以及板载的电源 LED 和 USB 连接器状态。MicroPython 和 C SDK 都提供了对应的 API 来管理这些资源,差别只是控制的粒度。

2. 读懂 API:Pico 的电源管理接口体系

2.1 MicroPython 下的核心 API:machine 模块

MicroPython 是树莓派 Pico 最常用的开发方式,对新手特别友好。它把电源管理接口封装在machine模块里,核心就三个函数:

  • machine.freq():设置 CPU 主频,可传参数比如machine.freq(50000000)把频率降到 50MHz。降频能显著降低运行电流,适合对算力要求不高的采集任务。
  • machine.lightsleep(ms):进入浅睡,可设置唤醒时间(毫秒),也可以不传参,由 GPIO 中断唤醒。RAM 保留,唤醒后代码从调用点继续执行。
  • machine.deepsleep(ms):进入深度休眠,RAM 保留,唤醒后代码行为与lightsleep类似(在 MicroPython 实现中,RP2040 的 deepsleep 只是 dormant 模式的封装)。如果传了时间参数,会用内置的定时器唤醒。

这三个函数看起来简单,但使用上有几个关键区别必须要知道。

第一,lightsleep会保留外设的时钟,所以唤醒后 UART、I2C 这些外设还能继续工作;deepsleep会停掉大部分外设时钟,唤醒后可能需要重新初始化外设。第二,lightsleep期间电流大约在 1~5mA 之间(取决于引脚状态和外设),deepsleep期间电流可以到 0.2~0.4mA,但唤醒时间更长。第三,deepsleep之后,MicroPython 解释器不是从头执行的,而是从调用deepsleep那一行的下一行继续,这一点和很多人的直觉不一样,写代码时要小心。

另一个要留意的是machine.Pin的中断唤醒配置。无论是lightsleep还是deepsleep,只要在进入睡眠前给某个引脚配置了Pin.IRQ_RISINGPin.IRQ_FALLING中断,睡眠中该引脚出现对应边沿时就会唤醒。这是实现“事件驱动”低功耗的关键手段。

2.2 C SDK 下的低功耗接口与映射关系

如果要用 C 开发,RP2040 的官方 C SDK 也提供了一套电源管理 API,集中在pico/stdlib.hsleep.hhardware/rtc.h里。常用的有:

  • sleep_run():把 CPU 切到低功耗运行状态,适合不需要深度休眠的场景。
  • sleep_goto_sleep():进入 sleep 模式,需要先配置唤醒源(sleep_source_enable)。
  • sleep_goto_dormant_until_edge():进入 dormant 模式,由 GPIO 边沿唤醒,需要指定引脚和边沿类型。
  • sleep_goto_dormant_until_rtc():进入 dormant 模式,由内部 RTC 在指定时间唤醒。
  • rosc_clocks_系列函数:手动管理时钟源,比如停掉 PLL、切换到内部振荡器。

C SDK 的粒度比 MicroPython 细很多。比如你可以只停掉某个外设的时钟,而不是整个芯片进入睡眠。但这种粒度也是双刃剑:配置错误会导致系统卡死、无法唤醒,所以一般建议按照官方示例来,不要随意混用。

MicroPython 的deepsleep在底层就是对 C SDK 的sleep_goto_dormant_until_edgesleep_goto_dormant_until_rtc的封装。理解这层映射关系后,很多“我明明调了 deepsleep 为什么电流还是很高”的问题就容易排查了——很可能是某个外设没有被正确关闭,或者 GPIO 引脚处于浮动状态导致漏电。

2.3 外设电源管理:什么在偷偷耗电

进入睡眠模式不等于外设自动关闭。这是一个我反复强调的点:睡眠只是停了 CPU 的时钟,外设如果不主动关闭,依然会耗电

常见的“偷电大户”按影响从大到小排序:

外设/组件未关闭时的影响关闭方式
板载电源 LED2~5mA 甚至更高拆电阻或用 GPIO 控制 LED 供电
ADC 模块0.5~1mA调用machine.ADC(0).deinit()或设置供电关断
PWM 输出未接负载引脚电平不确定,导致漏电设置为输入模式或固定输出低电平
I2C/SPI 上拉电阻数百 uA关闭外设后把 SCL/SDA 引脚设为输入
UART 空闲电平引脚电平未定义配置为确定的 GPIO 状态
DC-DC/LDO 静态功耗1~5mA(取决于方案)硬件选低静态电流的 LDO

MicroPython 下,关闭外设的常用方式是把引脚重新初始化。举个例子,如果一个引脚之前用作 PWM 输出,进入睡眠前把它重新配置成输入模式,并且不启用内部上拉,就能避免这个引脚带来的不确定漏电。

C SDK 下的做法更直接,进入休眠前调用adc_set_temperature_enable(false)i2c_deinit()spi_deinit()等函数即可。

3. 从 API 到实战:一个电池供电温度采集节点的完整实现

3.1 整体方案与硬件接线

这部分我拿自己做过的项目来举例:一个电池供电的户外温度采集节点,每 30 分钟起床一次,读温度传感器的值,通过 Semtech 射频模块发出去,然后继续睡觉。

硬件清单:

  • 树莓派 Pico(第一代,RP2040)
  • DS18B20 温度传感器(单总线,一条 GPIO 即可)
  • 一个 3.7V 锂电池 + 低静态电流 LDO(我用的 TPS7A0230,静态电流约 1uA)
  • 一块小太阳能板做浮充(可选)
  • 万用表或者 INA219 模块测电流

接线很简单:传感器数据线接 GP15,VCC 和 GND 分别接 LDO 输出和地。GP15 需要接一个 4.7k 上拉电阻到 3.3V,这是 DS18B20 单总线协议的要求。

在写代码之前,有件事很重要:把板载 LED 的限流电阻去掉。Pico 的原理图里 LED 是通过一个电阻接在 GP25 上的,直接接 3.3V 和地。如果不拆这个电阻,即使你不点亮 LED,因为 LED 负极已经接地、正极通过电阻接到 3.3V,它不会亮,但如果你初始化了 GP25 为输出高,就会白耗电;更常见的情况是如果 GP25 悬空,状态不确定,也可能产生漏电。稳妥的办法是把那个电阻焊下来,或者找一块没有板载 LED 的兼容板。

3.2 代码实现一:定时唤醒的 lightsleep 版本

先给一个最基础的版本:用machine.lightsleep实现定时唤醒。

import machine import utime import ubinascii import onewire import ds18x20 # 定义传感器引脚 SENSOR_PIN = machine.Pin(15) SENSOR = ds18x20.DS18X20(onewire.OneWire(SENSOR_PIN)) # 读取温度 def read_temp(): roms = SENSOR.scan() if not roms: return None SENSOR.convert_temp() utime.sleep_ms(750) return SENSOR.read_temp(roms[0]) # 发送数据(这里用伪代码替代) def send_data(temp): # 在这一步把数据通过无线模块发出去 # 注意:发送时功耗很高,尽量压缩发送时间 print("send:", temp) # 主循环 while True: # 唤醒后先读取传感器 temp = read_temp() if temp is not None: send_data(temp) # 进入浅睡,30分钟后唤醒 machine.lightsleep(30 * 60 * 1000)

这段代码能跑,但电流表现一般。为什么?因为lightsleep保留的时钟和外设太多,实际测量可能是 1~3mA。对于应用场景要求不高的项目够了,但如果要把功耗往死里压,就不合适。

另外有个坑:machine.lightsleep(30000)在 MicroPython 里是毫秒单位,注意别写成秒。我曾经在这上面翻过车,唤醒间隔直接短了 1000 倍。

3.3 代码实现二:deepsleep + GPIO 唤醒的事件驱动版本

如果业务是“有人按键或者传感器触发时才工作”,那用事件驱动更合适。下面这段是事件驱动 + 深度睡眠的典型写法:

import machine import utime # 唤醒引脚:GP0,配置为上拉输入,下降沿触发 WAKE_PIN = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP) def wake_handler(pin): # 中断回调里只设置标志,不在中断里做耗时操作 global wake_flag wake_flag = True wake_flag = False WAKE_PIN.irq(trigger=machine.Pin.IRQ_FALLING, handler=wake_handler) # 关闭不用的外设 adc_sample = machine.ADC(0) adc_sample.deinit() # 把其他 GPIO 全部设为输入,避免悬空漏电 for i in range(1, 30): if i == 15 or i == 0: # 跳过传感器和唤醒引脚 continue try: p = machine.Pin(i, machine.Pin.IN) p.irq(handler=None) except Exception: pass # 主流程(首次上电时) while True: if wake_flag: wake_flag = False # 读取传感器、处理业务 # ... else: # 进入深度睡眠,等待 GPIO 边沿唤醒 machine.deepsleep()

这段代码的关键点在于:

  1. 中断回调只置标志,不放进业务逻辑,避免中断上下文里执行耗时操作导致不可预期的行为。
  2. 睡眠前把所有 GPIO 都设置为输入模式,清掉中断,避免悬空引脚漏电。这个操作很基础,但对电流影响很大。
  3. deepsleep()不传时间参数,就变成“永久睡眠”,只有 GPIO 边沿或复位能唤醒。

3.4 实测电流数据与优化前后对比

我自己在实际项目里测过几组数据,给大家参考:

模式实测电流说明
默认运行(CPU 125MHz)25~28mA不加任何外设
降频到 50MHz 运行17~20mAmachine.freq(50000000)
light sleep(未优化外设)2~5mA外设时钟还在跑
light sleep(外设全关)1~2mA主要剩余是 LDO 静态电流
deepsleep(外设全关 + LDO 低静态电流)0.3~0.8mA目标状态
理论 dormant 最低约 0.18mA需要 C SDK 精细控制

这里特别提醒:测量电流时要把万用表串进电源回路里,不是浮空接在某个引脚上。很多人接法不对,测出来数据完全没参考价值。更稳的办法是用 INA219 这种电流传感器模块,挂在主控的 I2C 上,代码里定期读数,这样还能记录睡眠和唤醒两个状态各自的电流。

4. C SDK 路线:把休眠压到更低的几种写法

4.1 sleep_run 与 sleep_goto_sleep 简单休眠

如果项目对实时性要求高、需要频繁唤醒处理数据,用 C SDK 的sleep_goto_sleep会更合适。

#include "pico/stdlib.h" #include "hardware/clocks.h" #include "pico/sleep.h" int main() { // 初始化必要外设 stdio_init_all(); // 设置唤醒源:GPIO0 上升沿 sleep_source_enable(DORMANT_SOURCE_GPIO0); gpio_set_irq_enabled_with_callback(0, GPIO_IRQ_EDGE_RISE, true, &gpio_callback); while (true) { // 处理业务 do_something(); // 进入浅睡 sleep_goto_sleep(); } }

这个模式唤醒很快,因为大部分时钟没停。但电流相对高,适合“频繁唤醒 + 业务实时性强”的场景。如果你想要更低的睡眠电流,就得切到 dormant 模式。

4.2 dormant 模式与时钟停振注意事项

dormant 模式是 RP2040 能提供的真正深度休眠,它会停止大部分时钟源,包括系统主时钟。唤醒后再恢复时钟。

// 进入 dormant 模式,等待 GPIO 上升沿 sleep_goto_dormant_until_edge(0, GPIO_IRQ_EDGE_RISE);

代码只有一行,但背后有几个坑:

  1. dormant 模式唤醒后,USB 等依赖 PLL 的外设可能失效。如果你用 USB 串口调试,唤醒后大概率丢了连接,需要重新初始化 USB 配置。
  2. 时钟恢复需要时间。唤醒瞬间系统时钟是从慢速启动的,如果马上执行高速 SPI 操作,可能出错。稳妥做法是唤醒后调用clocks_init()之类重新配置时钟。
  3. 不要在 dormant 模式下依赖定时器——除非用 RTC。没有外部 RTC 模块的话,RP2040 内部没有真正的掉电保持 RTC,dormant 模式的定时唤醒只能靠板上的 RTC 外设(需要跑在低功耗时钟源上)。

4.3 复位与 RTC 唤醒组合方案

MicroPython 的deepsleep其实还有一个隐藏特性:某些场景下唤醒后会自动复位重新执行脚本。如果你用 C SDK,可以自己实现这种“RTC 定时唤醒 + 冷启动”的组合。

方案是:在休眠前把断电/重启后的恢复数据存到 Flash 或 RAM retention 区域,设置 RTC 唤醒时间,然后进入 dormant。唤醒后从main()重新跑,但通过检查启动原因判断是“首次上电”还是“RTC 唤醒”,从而跳过初始化步骤直接进业务逻辑。

这种方案的优点是代码路径清晰、鲁棒性高;缺点是每次唤醒都是冷启动,中间状态要自己管理。对于“每分钟采集一次、每次采集 2 秒”的应用来说,冷启动多出来的时间完全可以接受。

5. 注意事项与避坑实录

5.1 测量工具与测量方法

低功耗优化没有电流数据支撑就是瞎忙活。工具选择上,优先推荐:

  • UT61E 等带 uA 档的万用表:测睡眠电流够用,但测瞬态电流(比如 RF 发射瞬间)不靠谱。
  • INA219 / INA226 模块:能通过 I2C 连续采样,记录电流曲线,适合观察“睡眠—唤醒—业务—再睡眠”的完整周期。
  • 示波器 + 电流探头:如果预算够,看瞬态电流最直观。

测量接线有个很常见的错误:把万用表接在地和 GND 之间,或者接在某个 GPIO 上。正确的是把万用表串接到电源正极和板子 VIN 之间。测完运行电流后再测睡眠电流,两个状态都要有记录。

5.2 USB 接口、板载 LED、ADC 的“隐形漏电”

板载 LED、USB 连接器、ADC 这三个是最常见的漏电源头。

Pico 的板载 LED 正极通过电阻接在 GP25 上,负极接地。很多代码第一步就是Pin(25, Pin.OUT),如果忘了设成低电平,GP25 输出高就会点亮 LED,这可是几毫安的电流。更隐蔽的是 MicroPython 的 ADC——你只要调用过一次machine.ADC(),没有 deinit,它就可能一直在采样,白白耗电。所以睡眠前务必逐个 deinit。

USB 连接器在电池供电项目里基本是废的,但它接在板子上,如果不把板子的 VBUS 检测脚处理掉,可能产生持续的漏电。最省事的方式是直接用剪刀或者烙铁把 USB 座子拆掉,或者用不带 USB 的兼容板。

5.3 引脚悬空与上拉/下拉配置

悬空的 GPIO 引脚在睡眠中是“薛定谔电平”,可能是有规律的漏电。睡觉前把所有不用引脚统一配置成输入模式,不要启用内部上拉/下拉,是最省电的做法。因为内部上拉/下拉电阻本身也是一个分流路径,每个引脚约几十 kΩ 到上百 kΩ,累积起来在电池供电场景下不可忽视。

唤醒按键这类外部输入,建议在硬件上接一个 100kΩ 左右的外部上拉电阻到 VCC,然后配置 GPIO 为输入模式,用下降沿触发。这样睡眠时引脚电平有确定值且无内部电阻漏电,外部上拉带来的额外功耗也微乎其微。

5.4 供电方案:LDO 与 DC-DC 的选择

低功耗设备供电,LDO 和 DC-DC 的选择直接决定底噪和转换效率。

  • LDO(低压差线性稳压器):优点是纹波小、静态电流低、外围简单;缺点是电池电压越高,转换效率越低。比如 3.7V 锂电池经 LDO 降到 3.3V,效率大概 89%,不算差。选型时看静态地电流(quiescent current),常见低静态 LDO 有 TPS7A02、MCP1700 等,静态电流在 1~2uA 量级。
  • DC-DC 开关电源:效率高,但静态电流通常比 LDO 高(轻载时可能 10~50uA),而且纹波大,对 ADC 采样精度有影响,PCB 布局也更讲究。

小电流应用(整体方案平均电流小于 5mA)优先选低静态 LDO,这是我在多个项目里的实用结论。如果你需要“待机几个月”这种极端场景,DC-DC 的低效率区间反而可能让平均功耗劣化。

6. 常见问题速查与排查思路

6.1 问题速查表

现象可能原因排查方式
睡眠后电流依然有 10mA+板载 LED 被点亮、USB 座子未处理、GPIO 悬空逐项关掉外设,看电流变化
醒来后 UART 没有输出light sleep 保留外设时钟,但 C SDK dormant 唤醒后未重新初始化唤醒后重新调用stdio_init_all()
GPIO 中断唤不醒中断触发方式不对,或引脚配置成内部上拉了检查sleep_source_enable和边沿设置
deepsleep 后程序从头执行MicroPython 行为可能需要硬复位检查代码里是否用了machine.DEEPSLEEP_RESET
测量到的待机电流不稳定万用表接触不良、电池电压波动用 INA219 连续记录曲线,看波形
唤醒后传感器读不到数据传感器供电未受控,睡眠中传感器一直在运行用 GPIO 控制传感器电源,工作后才上电
外设时钟没关闭导致电流高某些外设初始化后没有 deinit睡眠前把所有外设对象 deinit 或引脚重新设成输入

6.2 一个典型的排查案例

我遇到过最典型的一个问题:deepsleep 后电流怎么测都是 2.5mA,怎么也降不下来。排查过程供大家参考。

先怀疑 LDO 静态电流过大,把 Pico 拔下来只测电源板,电流是 3uA,排除。又怀疑传感器,把传感器拔掉,电流没变。最后怀疑 MicroPython 环境,写了个最小测试程序:只配置唤醒引脚、关闭所有外设、进入 deepsleep,结果还是 2.5mA。

最后发现是万用表测法的问题。我最初把万用表串在电池负极和系统 GND 之间,但 Pico 上电后 GND 和电池负极之间有一个电源管理芯片的旁路,导致表笔中间形成了寄生回路。把表挪到电池正极和主板 VIN 之间,读数是 0.4mA。数据一差距,思路就通了。

排查这类问题的通用方法:

  1. 最小化测试:把代码砍到只剩“进入睡眠”一个功能,确认底数。
  2. 模块化排除:外设逐个启用,每启用一个就测一次电流,定位差异来源。
  3. 检查测量方式:确认万用表接法正确,排除测量误差再谈优化。

最后分享一个我觉得特别实用的习惯:在做低功耗项目时,把“测量设备”也纳入整个系统的设计里。比如在 PCB 上留一个专门的电流测试焊盘,或者预留 INA219 的 I2C 接口,这样后续调试不用每次拿镊子戳触点,省去大量无效时间和情绪损耗。软件控制的 API 固然重要,但从硬件上把测量和调试路径设计好,才是长期项目能持续优化的基础。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询