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 的休眠,却忘了外设也需要“睡觉”。
外设休眠管理,说白了就是三件事:
- 知道谁在耗电:列出所有外设,查清楚它们的工作电流和休眠电流
- 知道什么时候该睡:根据系统状态,决定哪些外设可以休眠
- 知道怎么叫醒:设计好唤醒机制,别让外设睡死过去
我一般会做一个“外设功耗状态机”:
| 系统状态 | 传感器 | 通信接口 | 显示模块 | 存储模块 |
|---|---|---|---|---|
| 运行 | 工作 | 工作 | 工作 | 工作 |
| 待机 | 休眠 | 休眠 | 关闭 | 休眠 |
| 深度休眠 | 关闭电源 | 关闭电源 | 关闭电源 | 关闭电源 |
注意: 有些外设的“休眠”和“关闭电源”是两回事。休眠时外设还挂着电源,只是内部时钟停了。关闭电源则需要用 MOSFET 或负载开关彻底切断供电。后者功耗更低,但唤醒时间更长。你要根据应用场景权衡。
我曾经在一个分拣机项目里,用了一个小技巧:给传感器单独加一个 MOSFET 开关。平时传感器完全断电,只有需要测量时才上电。测量完立刻断电。这样传感器的待机电流直接降到 0。代价是每次测量前要等传感器启动稳定,大概 50ms。但对于分拣机这种每秒测一次的场景,完全够用。
总结一下: 外设低功耗设计,核心就四个字——按需供电。需要的时候才给电,不需要的时候彻底断电或深度休眠。配合 DMA 和事件驱动,让 CPU 也尽量少干活。这样一套组合拳下来,系统功耗能降 50% 以上。
好了,这一章就到这里。下一章咱们聊聊电源路径管理和电池选型,那又是另一番天地了。