为什么电池设备必须谈功耗
一颗纽扣电池的容量通常只有 200 到 300 毫安时,如果设备在工作模式下吃电 20 毫安,理论上十几个小时就会耗尽。但真实的电池类产品——智能门锁、温湿度传感器、可穿戴设备——往往要求一颗电池撑半年甚至一年。这中间的差距不是靠选更大的电池解决的,而是靠让芯片 99% 的时间都在睡觉。
低功耗设计的核心思路是:让 CPU 尽量少醒、醒了尽量快干完活再睡回去。平均电流决定电池寿命,而不是工作电流。
很多新手做低功耗的第一步就走错了:他们盯着 datasheet 里的 Run 模式电流,觉得 10 毫安已经很优秀。实际上一个合理的低功耗方案,平均电流往往在 10 微安以下——比 Run 模式小一千倍。真正决定寿命的不是干活时耗多少电,而是闲着的时候耗多少电。
三种睡眠模式:Stop、Standby 与 Shutdown
以常见的 Cortex-M 芯片为例,低功耗模式大致分三档:Sleep 只是停 CPU,外设照样跑;Stop 把内核时钟停掉但保留 SRAM,唤醒很快;Standby 几乎切断所有电源,只有备份域和唤醒引脚活着,电流可以低到 1 微安以下,但唤醒后相当于复位。
/* 进入 Stop 模式:CPU 停,但 SRAM 内容保留,中断可唤醒 */
static void enter_stop(void)
{
HAL_SuspendTick(); /* 停掉 SysTick,避免它立刻唤醒 */
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
/* 被唤醒后从这里继续执行,需要重新配置时钟 */
SystemClock_Config();
HAL_ResumeTick();
}
/* Standby:最省电,唤醒后等同于复位 */
static void enter_standby(void)
{
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
HAL_PWR_EnterSTANDBYMode();
}
选哪个模式有个简单的判断:唤醒后需要保留现场(变量、栈)就用 Stop;唤醒后从头启动也无所谓就用 Standby。Standby 电流最低,但它把 RAM 里的状态全丢了,下次启动要重新初始化——对传感器采集这种“醒了就干活”的场景反而最合适。
外设时钟门控:不用就关掉
很多开发者不知道的是,芯片内部每个外设都有独立的时钟使能位。UART、SPI、ADC 这些模块即使你不去用它,只要时钟开着,就会白白消耗几百微安。低功耗设计的一条铁律是:哪个外设不用,就在进入低功耗前把它的时钟关掉。
/* 进入 Stop 前关掉所有暂时不用的外设时钟 */
static void periph_clock_gate(void)
{
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_USART1_CLK_DISABLE();
__HAL_RCC_SPI1_CLK_DISABLE();
__HAL_RCC_ADC1_CLK_DISABLE();
/* 只保留要用来唤醒的引脚对应的 GPIO 时钟 */
__HAL_RCC_GPIOC_CLK_ENABLE(); /* PC13 接了按键唤醒 */
}
/* 唤醒后再按需重新打开 */
static void periph_clock_ungate(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
MX_SPI1_Init();
MX_ADC1_Init();
}
- 引脚设为模拟输入未使用的 GPIO 设为模拟输入可避免施密特触发器耗电,比悬空更省电。
- 调试口别忘了关SWD 在默认状态下会持续耗电,量产固件里要在初始化末尾关闭 JTAG/SWD。
- LSE 低速晶振按需开用 RTC 唤醒才开 LSE,不用就停掉,几十微安也是肉。
功耗测量:用电流表抓准每一微安
不测量就谈不上优化。低功耗测量最大的坑是:普通万用表的电流档分辨率通常是毫安级,根本测不到微安级的待机电流。你需要一台支持微安甚至纳安档的精密电流表,或者用一个大功率采样电阻加示波器来看电流曲线。
/* 用 RTC 周期性唤醒,测量平均电流的典型代码结构 */
static void rtc_wakeup_setup(void)
{
RTC_HandleTypeDef hrtc = {0};
hrtc.Instance = RTC;
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 6000, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
/* 每 6 秒醒一次:采集传感器、上报、再睡回去 */
}
void RTC_IRQHandler(void)
{
HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
if (collect_and_report() == true) {
/* 上报成功,再进 Stop */
}
enter_stop();
}
测量时重点看三个值:睡眠电流(目标几微安)、唤醒工作电流(看峰值和持续时间)、以及两者加权后的平均电流。一颗 220mAh 的纽扣电池,按平均 10 微安算,理论寿命是 22000 小时,大约两年半——扣掉自放电和保守系数,做到一年以上是合理目标。
软件策略:批量唤醒与事件驱动
软件层面对功耗的影响往往比硬件还大。最反直觉的一点是:频繁短暂唤醒比偶尔长时唤醒更费电。因为每次唤醒都要重新配置时钟、预热外设,这段固定开销如果摊薄到一次长任务里,平均下来反而更省。
- 传感器数据不要采一个发一个,攒到一定数量或时间窗口再批量上传。
- 用中断代替轮询:任何“等待某引脚变化”的代码都不该写成 while 循环空转。
- 无线模块(蓝牙、LoRa)是耗电大户,连接和发送的瞬间能冲到几十毫安,发送窗口要尽量短。
- 打印调试信息在量产固件里必须关闭或改为异步缓冲,printf 阻塞串口时 CPU 一直在跑。
/* 批量上报:采集 10 次再发一次 */
static sample_t buf[10];
static uint8_t idx = 0;
static void on_sample_ready(sample_t s)
{
buf[idx++] = s;
if (idx >= 10) {
radio_tx_batch(buf, idx); /* 一次把 10 条发完 */
idx = 0;
}
}
电源域与 LDO、DCDC 选型
芯片再省电,如果电源方案选错了也是白搭。LDO 静态电流小但效率低,电池电压高时白白发热变成热量;DCDC 效率高但静态电流大,轻载时反而不如 LDO。实际产品里常用的方案是:负载大时用 DCDC,休眠时切到 LDO 维持。
| 方案 | 静态电流 | 效率 | 适用场景 |
|---|---|---|---|
| LDO | 几微安 | 压差×负载电流 | 休眠为主、负载轻的设备 |
| DCDC | 几十微安 | 85%~95% | 持续工作、负载较重 |
| DCDC+LDO 自动切换 | 休眠时微安级 | 兼顾 | 电池产品主流方案 |
另一个常被忽略的点是漏电:PCB 上的助焊剂残留、湿度、脏污都可能产生微安级漏电。低功耗板子清洗后一定要做绝缘阻抗测试,否则你在板子上测到的“莫名其妙的几微安”可能根本不是芯片耗的。
常见坑与优化清单
最后列一份实战中反复踩过的坑,按检查顺序排列。
- 先确认你测的电流是真待机电流:示波器看波形,别信万用表的平均值。
- 发现待机电流比 datasheet 高一个数量级,先查外设时钟有没有关、引脚有没有设成模拟输入。
- 休眠电流偏大时,用手逐个切断电源域——断一块看电流变化,定位到具体芯片。
- 无线模块不要只看发射电流,平均功耗里待机连接电流占大头,要缩短连接间隔。
- 量产前做高温老化测试:高温下漏电和 RTC 电流都会明显上升,常温测好不代表高温也行。
- 电池选型要留余量:自放电、低温容量衰减都要算进平均电流模型里。
低功耗是一门“抠门”的学问:每一微安都要找到它的去处。把测量、时钟门控、批量上报这三件事做扎实,电池寿命自然会上去。
建议的动手顺序是:先在开发板上把 Stop 模式跑通,用电流表看到微安级电流;再加上 RTC 周期唤醒,测平均电流;最后把外设时钟门控和批量上报加上,对比优化前后的数据。每一步都留好测量记录,比凭感觉调参靠谱得多。