说明¶
概述¶
架构2实现了 DMA + 双缓冲(乒乓缓冲区)模式,用于高频实时传感器数据采集和处理。该架构针对高采样频率(0.1 - 10000 Hz,推荐:1 kHz - 10 kHz)进行了优化,通过 DMA 辅助的 SPI 传输提供低 CPU 使用率。
架构原理¶
DMA + 双缓冲模式¶
该架构使用乒乓(双缓冲)机制:
- 缓冲区 A 和 B:两个独立的缓冲区,在采集和处理之间交替
- Producer 任务:填充一个缓冲区,同时 Consumer 处理另一个
- Consumer 任务:处理已填充的缓冲区,同时 Producer 填充另一个
- 并行操作:采集和处理可以在时间上重叠
- DMA 支持:SPI 传输自动使用 DMA,减少 CPU 负载
关键设计决策¶
- 双缓冲(乒乓):两个缓冲区允许并行采集和处理
- 缓冲区切换:当一个缓冲区满时,Producer 切换到另一个缓冲区
- 信号量通知:二进制信号量通知 Consumer 缓冲区已就绪
- 互斥锁保护:每个缓冲区有自己的互斥锁用于线程安全访问
- DMA 自动:ESP-IDF SPI 驱动自动使用 DMA 进行传输
实现细节¶
基于定时器的采样¶
- ESP 定时器:创建周期定时器,周期 =
1,000,000 / sampling_frequency_hz微秒 - 定时器回调:在定时器上下文中执行,通过
xTaskNotify通知 Producer 任务 - 立即首次采样:在启动周期定时器之前执行一次采样
Producer 任务¶
优先级:10(高优先级,用于及时采集)
功能:
- 通过
xTaskNotifyWait等待定时器通知 - 读取传感器数据(DMA 自动处理 SPI 传输)
- 准备带时间戳的样本结构
- 在互斥锁保护下写入活动缓冲区(A 或 B)
- 当缓冲区满时:
- 标记缓冲区为就绪
- 切换到另一个缓冲区
- 通过信号量通知 Consumer
- 重置写索引
- 更新采集统计
缓冲区管理:
- 跟踪活动缓冲区(0 = A,1 = B)
- 顺序写入活动缓冲区
- 缓冲区满时切换缓冲区
- 如果 Consumer 未及时处理,检测覆盖
Consumer 任务¶
优先级:8(高优先级,用于处理)
功能:
- 等待缓冲区就绪信号量(A 或 B)
- 获取就绪缓冲区的互斥锁
- 处理缓冲区中的所有样本
- 标记缓冲区为已处理并重置就绪标志
- 更新处理统计
- 输出结果(串口、MQTT、LCD)
处理逻辑:
- 处理整个缓冲区(所有样本)
- 执行特征提取、检测等
- 处理带 LCD 反馈的加速度检测
- 如果启用,通过串口输出第一个样本
双缓冲机制¶
缓冲区大小:每个缓冲区 512 样本(可通过 RT_DMA_DB_BUFFER_SIZE 配置)
内存:两个缓冲区都从 PSRAM 分配
同步:
- 每个缓冲区的互斥锁(保护写/读访问)
- 每个缓冲区的二进制信号量(信号表示就绪)
- 用于缓冲区就绪状态的易失性标志
操作流程:
- Producer 填充缓冲区 A
- 当缓冲区 A 满时,Producer 切换到缓冲区 B 并通知 Consumer
- Consumer 处理缓冲区 A,同时 Producer 填充缓冲区 B
- 当缓冲区 B 满时,Producer 切换到缓冲区 A 并通知 Consumer
- Consumer 处理缓冲区 B,同时 Producer 填充缓冲区 A
- 循环重复

图:双缓冲(乒乓缓冲)机制示意图,显示并行采集和处理
DMA 支持¶
- 自动 DMA:ESP-IDF SPI 驱动自动使用 DMA 进行传输
- CPU 减少:DMA 处理数据传输,释放 CPU 用于其他任务
- 非阻塞:使用 DMA 的 SPI 操作是非阻塞的
数据流¶
ESP 定时器 → 定时器回调 → xTaskNotify → Producer 任务(Core 0/1)
↓
读取传感器(SPI + DMA)
↓
活动缓冲区(A 或 B)
↓
缓冲区满 → 切换缓冲区
↓
信号量信号 → Consumer 任务
↓
处理缓冲区
↓
┌───────────────────┴───────────────────┐
↓ ↓ ↓
串口 MQTT LCD
配置¶
默认配置¶
#define RT_DMA_DB_BUFFER_SIZE 512
#define RT_DMA_DB_PRODUCER_PRIORITY 10
#define RT_DMA_DB_CONSUMER_PRIORITY 8
#define RT_DMA_DB_PRODUCER_STACK_SIZE 4096
#define RT_DMA_DB_CONSUMER_STACK_SIZE 8192
配置参数¶
- 采样频率:0.1 - 10000 Hz(初始化时验证)
- 缓冲区大小:每个缓冲区 512 样本(编译时固定)
- 总内存:512 × 2 × 32 字节 = 32 KB,来自 PSRAM
功能特性¶
优势¶
- 低 CPU 使用率:DMA 自动处理 SPI 传输
- 高吞吐量:并行采集和处理
- 适用于高频:针对 1 kHz - 10 kHz 范围优化
- 真正并行:Producer 和 Consumer 可以同时运行
- 无队列开销:直接缓冲区访问,无队列管理
限制¶
- 内存使用较高:需要两个大缓冲区(双倍内存)
- 固定缓冲区大小:缓冲区大小在编译时固定
- 缓冲区覆盖风险:如果 Consumer 无法跟上,缓冲区可能被覆盖
性能特征¶
适用频率范围¶
- 有效范围:0.1 - 10000 Hz(初始化时验证)
- 推荐:1 kHz - 10 kHz
- 最大:最高 10 kHz(验证限制)
- 最小:0.1 Hz(实际限制)
资源使用¶
- 内存:两个缓冲区(512 样本 × 2 × 32 字节 = 32 KB,来自 PSRAM)
- CPU:低使用率(DMA 处理传输)
- 同步:两个互斥锁和两个二进制信号量
使用注意事项¶
- 初始化顺序:必须在
arch_dma_db_set_sensor_handle()之前调用arch_dma_db_init() - 传感器句柄:启动前必须设置
- 缓冲区监控:监控统计中的
overwrite_count以检测 Consumer 是否落后 - 处理延迟:处理延迟取决于缓冲区填充时间(buffer_size / sampling_frequency)
- DMA 配置:确保在 ESP-IDF 配置中启用了 SPI DMA
错误处理¶
- 互斥锁超时:如果无法在 10ms 内获取互斥锁,Producer 丢弃样本
- 缓冲区未初始化:如果缓冲区未就绪,丢弃样本
- 传感器读取失败:记录日志但不停止处理
- 任务创建失败:返回错误,清理资源
线程安全¶
- 互斥锁保护:每个缓冲区有自己的互斥锁用于线程安全访问
- 任务隔离:Producer 和 Consumer 任务正确隔离
- 信号量通知:二进制信号量提供安全的任务间通信
- 统计:在互斥锁保护的部分内原子更新