4、低功耗外设设计:传感器选型与功耗、低功耗通信接口(SPI/I2C/UART)、DMA与事件驱动、外设休眠管理

各位工程师朋友,咱们接着聊。上一章我们把MCU内核的功耗控制讲透了,但说实话,一个嵌入式系统里真正吃电的,往往是那些挂在总线上的外设。传感器、通信接口、外部存储器……它们才是真正的“电老虎”。这一章,我就结合自己这些年踩过的坑,聊聊怎么从外设层面把功耗压到最低。

4.1 传感器选型与功耗评估

选传感器,第一眼看什么?精度?接口?还是价格?我个人习惯,第一眼看它的工作电流和休眠电流。你想想看,一个传感器如果待机时还吃几十微安,那你的电池再大也扛不住。

核心原则: 传感器的功耗 = 工作电流 × 工作时间 + 休眠电流 × 休眠时间。别只看峰值,要看平均。

我在项目中遇到过一件事。有一次做一款便携式环境监测仪,选了一款很流行的温湿度传感器,精度高、响应快,但它的休眠电流是 5μA。当时没在意,结果整机待机电流直接飙到 20μA,电池续航少了整整三天。后来换了一款休眠电流只有 0.1μA 的传感器,问题才解决。

选型时,我建议你重点关注这几个参数:

  • 工作电流:测量时的瞬时电流,通常几 mA 到几十 mA
  • 休眠电流:待机时的漏电流,越低越好,最好 < 1μA
  • 启动时间:从休眠到稳定输出的时间,这决定了你每次测量前要等多久
  • 采样周期:能支持的最低采样频率,别让传感器比你还勤快
传感器类型 工作电流 休眠电流 典型启动时间
温湿度(SHT30) 1.5 mA 0.2 μA 15 ms
加速度计(LIS3DH) 2 μA(低功耗模式) 0.5 μA 1 ms
气压计(BMP280) 2.7 mA 0.1 μA 5 ms
红外热电堆(MLX90614) 1.5 mA 2 μA 250 ms

我的小技巧: 如果传感器支持“单次测量模式”,尽量用这个模式。每次测量完自动休眠,比你自己手动切休眠更省心,也更省电。

4.2 低功耗通信接口:SPI / I2C / UART

通信接口是外设和MCU之间的“桥梁”,但这座桥如果设计不好,功耗会高得吓人。我分别说说三种常用接口的低功耗用法。

4.2.1 I2C:最省线的接口,但要注意上拉电阻

I2C 是低功耗场景的常客。它只用两根线,而且支持多设备挂载。但有个坑——上拉电阻。我曾经在一个项目里用了 4.7kΩ 的上拉,结果总线空闲时,每根线上一直有几百微安的电流。后来换成 10kΩ,电流直接减半。

为什么?因为 I2C 总线空闲时是高电平,电流从上拉电阻流到地。电阻越大,电流越小。但也不能太大,否则信号上升沿会变慢。一般 4.7kΩ ~ 10kΩ 是常用范围,具体看总线电容和速率。

注意: 如果总线上挂的从设备支持“时钟延展”,记得把主机的时钟频率调低一点。我见过有人用 400kHz 跑 I2C,结果从设备跟不上,一直拉低时钟线,功耗直接翻倍。

4.2.2 SPI:速度快,但功耗也高

SPI 是全双工、高速接口,但它的功耗比 I2C 高不少。原因很简单:SPI 有 4 根线,而且通常需要片选信号。每根线在翻转时都会产生动态功耗。

低功耗场景下,我建议:

  • 能不传就不传:只在需要数据时才拉低片选,传完立刻拉高
  • 降低时钟频率:别一上来就开 20MHz,够用就行。比如传感器每秒只传 100 字节,1MHz 就绰绰有余
  • 关闭未使用的 SPI 外设:很多 MCU 的 SPI 模块在空闲时也会吃电,记得在寄存器里关掉

4.2.3 UART:简单但容易忽略的功耗点

UART 看起来简单,但它的功耗问题往往出在空闲状态。UART 空闲时,TX 引脚通常是高电平。如果外部电路设计不当,这个高电平可能会通过上拉或下拉电阻产生漏电流。

我记得有一次调试一个蓝牙模块,发现待机电流比规格书多了 50μA。查了半天,原来是 UART 的 RX 引脚悬空了,导致电平不确定,内部上拉电阻一直在耗电。后来加了一个 100kΩ 的下拉电阻,问题解决。

避坑指南: 我曾经在量产前才发现,UART 的波特率设置太高,导致接收中断频繁触发,MCU 根本睡不踏实。后来把波特率从 115200 降到 9600,整机功耗降了 30%。你想想看,中断每 100μs 来一次,CPU 哪有机会睡?

4.3 DMA 与事件驱动:让 CPU 歇着

CPU 是系统里最耗电的模块之一。如果能让它少干活,功耗自然就下来了。DMA 和事件驱动就是干这个的。

4.3.1 DMA:数据搬运工,不费脑

DMA 可以在不经过 CPU 的情况下,直接把数据从外设搬到内存,或者从内存搬到外设。CPU 只需要在传输开始和结束时被唤醒一次。

举个例子,你要从 SPI 接口的 ADC 读取 1000 个采样点。如果用 CPU 轮询,每读一个字节就要进一次中断,1000 次中断下来,CPU 基本没时间睡觉。但用 DMA,你只需要配置好源地址、目的地址和传输长度,然后就可以让 CPU 去睡了。DMA 传完后再发一个中断唤醒 CPU。

// 配置 SPI DMA 传输(以 STM32 为例)
// 假设 SPI1 的 RX 数据要搬到 buffer[1000]
DMA_InitTypeDef DMA_InitStruct;

DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStruct.DMA_BufferSize = 1000;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStruct.DMA_Priority = DMA_Priority_Low;

DMA_Init(DMA1_Channel1, &DMA_InitStruct);
DMA_Cmd(DMA1_Channel1, ENABLE);

// 启动 SPI 接收,CPU 可以进入休眠
SPI_DMACmd(SPI1, SPI_DMAReq_Rx, ENABLE);
// 然后执行 WFI 指令让 CPU 休眠
__WFI();

关键点: DMA 传输完成后会触发中断。在这个中断里,你可以处理数据,然后再次进入休眠。这样 CPU 的活跃时间被压缩到极致。

4.3.2 事件驱动:别让 CPU 去“查岗”

事件驱动说白了就是:有事你叫我,没事别烦我。和轮询相比,事件驱动能让 CPU 的休眠时间延长几个数量级。

我见过很多新手工程师,喜欢在主循环里轮询传感器状态:

while(1) {
    if(传感器数据就绪) {
        读取数据();
    }
    // 其他任务
    delay(10ms);
}

这种写法,CPU 每 10ms 就要醒一次,哪怕传感器根本没新数据。正确的做法是用外部中断或 GPIO 唤醒:

// 传感器数据就绪引脚连接到 MCU 的外部中断引脚
// 配置为上升沿触发
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);

// 主循环中直接休眠
while(1) {
    __WFI();  // 等待中断唤醒
    // 被唤醒后,在中断服务函数中设置标志位
    // 主循环检查标志位并处理数据
    if(data_ready) {
        读取数据();
        data_ready = 0;
    }
}

我的经验: 事件驱动配合 DMA 使用,效果最好。比如 ADC 用 DMA 连续采集,每采集完一个缓冲区,DMA 中断唤醒 CPU 一次。这样 CPU 的唤醒频率从每采样一次一次,降为每缓冲区一次。缓冲区越大,功耗越低。

4.4 外设休眠管理:谁该睡,谁不该睡

最后,咱们聊聊外设的休眠管理。很多工程师只关注 MCU 的休眠,却忘了外设也需要“睡觉”。

外设休眠管理,说白了就是三件事:

  1. 知道谁在耗电:列出所有外设,查清楚它们的工作电流和休眠电流
  2. 知道什么时候该睡:根据系统状态,决定哪些外设可以休眠
  3. 知道怎么叫醒:设计好唤醒机制,别让外设睡死过去

我一般会做一个“外设功耗状态机”:

系统状态 传感器 通信接口 显示模块 存储模块
运行 工作 工作 工作 工作
待机 休眠 休眠 关闭 休眠
深度休眠 关闭电源 关闭电源 关闭电源 关闭电源

注意: 有些外设的“休眠”和“关闭电源”是两回事。休眠时外设还挂着电源,只是内部时钟停了。关闭电源则需要用 MOSFET 或负载开关彻底切断供电。后者功耗更低,但唤醒时间更长。你要根据应用场景权衡。

我曾经在一个分拣机项目里,用了一个小技巧:给传感器单独加一个 MOSFET 开关。平时传感器完全断电,只有需要测量时才上电。测量完立刻断电。这样传感器的待机电流直接降到 0。代价是每次测量前要等传感器启动稳定,大概 50ms。但对于分拣机这种每秒测一次的场景,完全够用。

总结一下: 外设低功耗设计,核心就四个字——按需供电。需要的时候才给电,不需要的时候彻底断电或深度休眠。配合 DMA 和事件驱动,让 CPU 也尽量少干活。这样一套组合拳下来,系统功耗能降 50% 以上。

好了,这一章就到这里。下一章咱们聊聊电源路径管理和电池选型,那又是另一番天地了。