4、DMA与外设接口:SPI、I2C、UART、ADC等外设如何与DMA配合实现高效数据采集
TinyML 设备的数据采集链路中,CPU 往往是最宝贵的资源——它既要运行推理模型,又要处理中断、维护协议状态机、搬运数据。传统的中断驱动采集方式在高速外设(如 1Msps 的 ADC、8MHz 的 SPI 传感器)面前会迅速耗尽 CPU 预算,导致推理帧率下降或数据丢失。DMA(Direct Memory Access)的核心价值在于:它允许外设与内存之间直接交换数据,仅在传输完成或错误时通知 CPU。本章将深入剖析 SPI、I2C、UART、ADC 四类典型外设与 DMA 的配合机制,并给出工程化的配置判断方法与实战案例。
4.1 核心原理:DMA 如何消除 CPU 搬运瓶颈
DMA 的本质是外设与内存之间的“直通车道”。以 STM32 系列为例,DMA 控制器包含多个通道,每个通道可绑定特定外设的请求信号。当外设的数据寄存器就绪(如 SPI RX 非空、ADC 转换完成),硬件自动将数据搬运至内存地址,整个过程不需要 CPU 执行加载/存储指令。传输完成后,DMA 置位传输完成标志,并可触发中断通知 CPU。
理解 DMA 的关键在于三个维度:传输粒度(字节/半字/字)、传输模式(单次/循环/突发)和缓冲策略(单缓冲/双缓冲/环形)。在 TinyML 场景中,双缓冲是最常用的策略:DMA 填充缓冲区 A 时,CPU 处理缓冲区 B 的数据(如特征提取或推理),当 DMA 完成 A 的填充后自动切换至 B,同时触发中断让 CPU 处理 A。这种“乒乓”机制将采集与计算完全流水线化。
4.2 各外设的 DMA 配合机制与判断方法
4.2.1 SPI + DMA:高速传感器数据流
SPI 是 TinyML 中最常见的高速接口,典型设备包括 9 轴 IMU(如 BMI270)、气压计(BMP390)和外部 Flash。SPI 的 DMA 配合有两种模式:接收专用(只读传感器数据)和全双工(写寄存器+读数据)。
配置判断流程如下:
- 确认外设支持突发传输:查看数据手册中的最大 SPI 时钟频率(通常 10MHz-80MHz),计算每帧数据量(如 IMU 的 6 轴×2 字节=12 字节)。
- 选择 DMA 通道:参考 MCU 的 DMA 请求映射表,确保 SPIx_RX 和 SPIx_TX 绑定到不同通道(避免冲突)。
- 设置数据宽度:SPI 数据寄存器通常为 8 位或 16 位,DMA 宽度必须与外设寄存器一致,否则会错位。
- 启用 SPI 的 DMA 请求:在 SPI 控制寄存器中设置 RXDMAEN 和 TXDMAEN 位。
// STM32 HAL 示例:SPI2 接收 12 字节 IMU 数据到双缓冲
__ALIGN_BEGIN static uint8_t dma_buf[2][12] __ALIGN_END;
HAL_SPI_Receive_DMA(&hspi2, dma_buf[0], 12);
// 在 DMA 传输完成中断中切换缓冲
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi == &hspi2) {
active_buf ^= 1;
HAL_SPI_Receive_DMA(&hspi2, dma_buf[active_buf], 12);
// 此时 dma_buf[active_buf^1] 包含完整帧,可交给推理任务
}
}
4.2.2 I2C + DMA:低速但需注意总线状态
I2C 的 DMA 配合比 SPI 复杂,因为 I2C 协议包含 ACK/NACK、重复起始条件等状态机。多数 I2C 传感器(如温湿度 SHT40、光照 OPT3001)的采样率较低(1-100Hz),数据量小,DMA 收益有限。但在需要连续读取大块数据(如 8KB EEPROM)或需要保持总线时钟稳定时,DMA 仍有价值。
关键判断点:I2C 的 DMA 传输必须与硬件状态机严格同步。在 STM32 上,需要监听 I2C 的 TXIS/RXNE 事件,并通过 DMA 请求信号触发。常见误区是直接使用 HAL_I2C_Master_Receive_DMA() 而不检查总线错误标志,导致 DMA 卡死。
4.2.3 UART + DMA:日志与外部处理器通信
UART 的 DMA 主要用于两类场景:高波特率日志输出(如 921600bps 打印调试信息)和与外部协处理器通信(如树莓派发送特征向量)。UART 的 DMA 接收需要特别处理不定长数据——通常使用空闲中断(IDLE)来检测帧结束。
推荐配置:
- DMA 接收使用循环模式,配合空闲中断:当总线空闲时,CPU 读取 DMA 当前计数,计算接收长度。
- DMA 发送使用普通模式,发送完成后自动关闭,避免循环发送相同数据。
- 数据宽度固定为 8 位,因为 UART 数据寄存器是字节宽。
// 循环 DMA 接收 + 空闲中断实现不定长帧
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
// 在 UART 空闲中断中:
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) {
uint16_t len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx);
process_frame(rx_buf, len);
// 重新启动接收(循环模式自动继续)
}
4.2.4 ADC + DMA:连续采样与波形捕获
ADC 是 TinyML 中音频、振动、电流检测的核心接口。ADC 的 DMA 配合几乎是强制性的——如果使用中断方式读取 16kHz 采样率的音频,CPU 将每 62.5 微秒被中断一次,无法运行任何推理。ADC 的 DMA 配置要点:
- 扫描模式与 DMA 循环:多通道扫描时,DMA 自动将各通道结果按顺序存入缓冲区。
- 过采样与数据对齐:12 位 ADC 右对齐时,DMA 传输半字(16 位),注意缓冲区类型必须为 uint16_t。
- 双缓冲与半传输中断:对于音频流,使用半传输中断(HT)和传输完成中断(TC)将缓冲区分为两半,CPU 处理一半时 DMA 填充另一半。
| 外设 | 典型速率 | DMA 推荐模式 | 缓冲策略 | 关键注意点 |
|---|---|---|---|---|
| SPI | 10-80MHz | 普通/循环 | 双缓冲 | 数据宽度匹配,CS 控制 |
| I2C | 100-400kHz | 普通(仅大块读) | 单缓冲 | 错误中断必须启用 |
| UART | 115200-921600 | 循环接收+空闲中断 | 环形 | 帧边界检测 |
| ADC | 1k-1Msps | 循环 | 双缓冲/环形 | 半传输中断用于帧同步 |
4.3 案例拆解:多传感器 TinyML 数据采集系统
以一个真实的可穿戴活动识别设备为例,该设备需要同时采集 6 轴 IMU(SPI,100Hz)、麦克风(ADC,16kHz)和环境温度(I2C,1Hz),并在 Cortex-M4 上运行轻量级 CNN 进行跌倒检测。
初始目标与约束:CPU 主频 80MHz,推理需要 30ms 时间预算,数据采集不能超过 10% CPU 占用。初始方案使用中断采集,实测 CPU 占用高达 42%,导致推理帧率从 20FPS 降至 8FPS。
关键参数分析:
- IMU:SPI 时钟 10MHz,每帧 12 字节,100Hz → 每秒 1200 字节,中断频率 100Hz。
- 麦克风:ADC 16kHz,每次采样 2 字节 → 每秒 32000 字节,中断频率 16kHz(这是主要瓶颈)。
- 温度:I2C 1Hz,每次 2 字节,可忽略。
DMA 改造方案:
- IMU 使用 SPI1-RX DMA 双缓冲,每 10ms 切换一次缓冲,CPU 仅处理 100Hz 的完成中断。
- ADC 使用 DMA 循环模式,缓冲区 512 字节(256 个采样点),半传输中断每 8ms 触发一次,CPU 处理 256 个样本的预处理。
- 温度传感器保持中断方式(1Hz 无压力)。
实施结果:CPU 采集占用降至 6.5%,推理帧率恢复至 20FPS,且音频数据无丢失。DMA 配置耗时约 2 小时,主要时间花在调试 SPI 的 DMA 数据宽度匹配上——最初设置为字节传输,导致 12 位 ADC 数据错位。
4.4 常见误区与总结
误区一:所有外设都适合 DMA。对于 I2C 的低速传感器(<10Hz),DMA 初始化开销反而增加延迟。正确做法是评估中断频率和单次数据量,只有当 CPU 中断负载超过 20% 或数据量超过 16 字节/次时才考虑 DMA。
误区二:忽略 DMA 通道优先级。当多个外设同时请求 DMA 时,优先级低的通道可能被饿死。在音频+IMU 场景中,应将 ADC 设为高优先级(音频数据连续性强),SPI 设为中优先级。
误区三:缓冲区大小设置不当。DMA 缓冲区过小会导致频繁中断,过大则浪费 SRAM。对于 TinyML,建议缓冲区大小满足:缓冲区时长 = 推理周期 / 2。例如推理周期 50ms,则缓冲区应容纳 25ms 的数据。
误区四:忘记处理 DMA 错误。DMA 传输错误(如总线错误、对齐错误)会静默停止。务必在 DMA 错误中断中设置标志位,并在主循环中检查。
总结而言,DMA 与外设的配合是 TinyML 系统设计的分水岭。掌握 SPI 的双缓冲、UART 的循环接收、ADC 的半传输中断,以及 I2C 的谨慎使用,能够将 CPU 从数据搬运中解放出来,专注于推理计算。在实际项目中,建议先绘制数据流图,标注每个外设的速率、数据量和中断频率,再决定 DMA 通道分配和缓冲策略。这种系统化的方法能显著减少调试时间,并确保采集链路在极端负载下依然稳定。