传感器采集的功耗困境

在电池供电的传感器节点中,MCU 大部分时间应该处于睡眠状态,只在数据就绪时被唤醒处理。但很多实现中,MCU 要么轮询 ADC,要么用中断频繁唤醒,导致实际睡眠时间远小于预期。DMA 双缓冲加事件驱动,是解决这个问题的经典思路。

DMA 双缓冲的核心价值是:CPU 不参与数据搬运,且两块缓冲区交替使用——一块在被外设计数填充时,另一块正在被 CPU 处理。这避免了单缓冲方案中"等 DMA 传输完才能处理"的空窗期。

STM32L4 系列的 Stop2 模式是个好东西。静态功耗 1.4μA,SRAM 内容保持,DMA 可以继续工作,唤醒延迟 35μs。这意味着传感器采集可以在 CPU 睡眠时进行,只在缓冲区满时才唤醒 CPU。

双缓冲的工作逻辑

双缓冲的关键是"乒乓切换"。DMA 配置两块缓冲区,用 Circular 模式让 DMA 在两块之间循环。每次半传输完成(HT)和传输完成(TC)中断都会触发 CPU 唤醒,处理对应的半块数据。

设计代价是:内存占用翻倍,且需要同步机制防止 DMA 和 CPU 访问同一块缓冲区。简单说,HT 中断处理前半块,TC 中断处理后半块,两块交替使用,天然避免了竞争。

缓冲区布局:
[Buffer A (512B)] [Buffer B (512B)]
       ↑                ↑
   HT 中断处理       TC 中断处理
   (DMA 正在填 B)    (DMA 正在填 A)

实操配置

以下配置基于 STM32L432KC,用 CubeMX 生成 HAL 框架代码,手动补充双缓冲逻辑:

// 实测于 STM32L432KC, STM32Cube HAL 1.17.0
// ADC1 通道 5(PB1),DMA 双缓冲,12 位分辨率
#define BUF_SIZE 512
static uint16_t adc_buf[2][BUF_SIZE];  // 双缓冲

void ADC_DMA_Init(void) {
    // ADC 配置:72MHz/6 = 12MHz,采样时间 47.5 周期
    // 单次转换约 4μs,512 次约 2ms
    hadc1.Init.ContinuousConvMode = ENABLE;
    hadc1.Init.DMAContinuousRequests = ENABLE;

    // DMA 配置:Circular 模式,半字传输
    hdma_adc1.Init.Mode = DMA_NORMAL;  // 用 Normal + 手动重启实现双缓冲切换
    // 注意:HAL 库的 DMA_CIRCULAR 模式下 HT/TC 中断稳定
    // 但实测发现双缓冲切换用 DMA_NORMAL + 手动重启更可靠
    hdma_adc1.Init.Mode = DMA_CIRCULAR;

    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, BUF_SIZE * 2);

    // 注册 HT 和 TC 回调
    HAL_ADC_RegisterCallback(&hadc1, HAL_ADC_CONV_HALF_CB_ID, ADC_HT_Callback);
    HAL_ADC_RegisterCallback(&hadc1, HAL_ADC_CONV_CPLT_CB_ID, ADC_TC_Callback);
}

void ADC_HT_Callback(ADC_HandleTypeDef* hadc) {
    // DMA 正在填 Buffer B,处理 Buffer A
    ProcessData(adc_buf[0], BUF_SIZE);
}

void ADC_TC_Callback(ADC_HandleTypeDef* hadc) {
    // DMA 正在填 Buffer A,处理 Buffer B
    ProcessData(adc_buf[1], BUF_SIZE);
}

进入 Stop2 模式前的配置很关键。必须先挂起所有外设时钟,只保留 ADC 和 DMA 时钟,否则 Stop2 进不去:

void EnterStop2(void) {
    // 关闭不需要的外设时钟
    __HAL_RCC_GPIOA_CLK_SLEEP_DISABLE();
    // 保留 ADC 和 DMA 时钟
    __HAL_RCC_ADC_CLK_SLEEP_ENABLE();
    __HAL_RCC_DMA1_CLK_SLEEP_ENABLE();

    // 进入 Stop2
    HAL_PWREx_ControlLowPowerRunMode(PWR_LOWPOWERRUNMODE_ENABLE);
    HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);

    // 唤醒后恢复时钟
    SystemClock_Config();
}

实测记录

理论预期:MCU 在 Stop2 模式下被 DMA 中断唤醒处理数据,整体平均功耗应接近 Stop2 的 1.4μA 加上短暂的唤醒处理时间。

实测结果(用万用表 μA 档测 VDD 电流):
- 纯 Stop2 模式:1.6μA(手册标称 1.4μA,偏差 0.2μA)
- 双缓冲运行时(512 点采集 + 处理):平均 18μA
- 同样的轮询方案(CPU 不睡眠):平均 2.8mA

偏差分析:Stop2 静态功耗略高于手册值,怀疑是板上 LDO 漏电流导致。双缓冲运行时的 18μA 远高于静态,说明唤醒处理的开销不可忽略——每次唤醒要恢复时钟、处理数据、再进入 Stop2,这个流程约 50μs。

优化思路:增大缓冲区。从 512 点改到 2048 点后,唤醒频率降低 4 倍,平均功耗降到 8μA。代价是数据延迟从 2ms 涨到 8ms,对低频传感器(温度、压力)可以接受。

(这里我一开始想用 DMA 双缓冲的硬件切换功能——GD32 有专门的 MDA M0/M1 寄存器,但 STM32L4 的 HAL 库支持不完整,手动管理反而更可控。)

适用边界

这套方案的前提:
- 传感器采样率低(kHz 级以下)。如果是音频这种高采样率场景,Stop2 的唤醒开销反而比一直跑更划算。
- 数据处理可以批量进行。如果每点都要实时处理,双缓冲没有意义。
- VDD 稳定。Stop2 模式下 VDD 波动会导致 SRAM 数据丢失,电池供电要有稳压。

遗留问题

  • DMA 在 Stop2 模式下能否继续工作?手册写的是"SRAM 保持,DMA 可工作",但实测中如果 DMA 源是 ADC,需要确认 ADC 时钟是否在 Stop2 下保持开启。这部分手册描述模糊,待用逻辑分析仪抓时序验证。
  • 多 ADC 通道扫描时,DMA 双缓冲的 HT/TC 中断会变得复杂。如果采样频率不一致,需要按最慢的通道计算唤醒间隔,否则会丢数据。

关联笔记

关联笔记:STM32 ADC 采样时间计算:从手册到实测 中关于 ADC 采样时间与信号源阻抗的讨论

标签: 编程, 汽车电子

添加新评论