说明¶
概述¶
架构3实现了 DMA + 双核分工模式,其中数据采集和处理分配给不同的 CPU 核心。该架构通过利用双核 ESP32 平台的两个核心,提供最高性能,实现采集和处理之间的真正并行。
架构原理¶
双核分工¶
该架构在两个 CPU 核心之间分配工作:
- Core 0:Producer 任务(使用 DMA 进行数据采集)
- Core 1:Consumer 任务(数据处理)
- 双缓冲:用于并行操作的乒乓缓冲区机制
- 核心绑定:任务显式绑定到特定核心
- 真正并行:采集和处理在不同核心上同时运行
关键设计决策¶
- 核心分配:Producer 在 Core 0,Consumer 在 Core 1
- 任务绑定:使用
xTaskCreatePinnedToCore()确保任务在特定核心上运行 - 双缓冲:与架构2相同的乒乓机制
- DMA 支持:SPI 传输自动使用 DMA
- 核心隔离:每个核心处理其专用任务,无干扰
实现细节¶
基于定时器的采样¶
- ESP 定时器:创建周期定时器,周期 =
1,000,000 / sampling_frequency_hz微秒 - 定时器回调:在定时器上下文中执行,通过
xTaskNotify通知 Producer 任务(Core 0) - 立即首次采样:在启动周期定时器之前执行一次采样
Producer 任务(Core 0)¶
优先级:10(高优先级,用于及时采集)
核心分配:通过 xTaskCreatePinnedToCore() 绑定到 Core 0
功能:
- 仅在 Core 0 上运行
- 通过
xTaskNotifyWait等待定时器通知 - 读取传感器数据(DMA 自动处理 SPI 传输)
- 准备带时间戳的样本结构
- 在互斥锁保护下写入活动缓冲区(A 或 B)
- 当缓冲区满时:
- 标记缓冲区为就绪
- 切换到另一个缓冲区
- 通过信号量通知 Consumer(Core 1)
- 重置写索引
- 更新采集统计
核心隔离:Producer 任务仅在 Core 0 上运行,确保采集的专用 CPU 时间
Consumer 任务(Core 1)¶
优先级:10(高优先级,用于处理)
核心分配:通过 xTaskCreatePinnedToCore() 绑定到 Core 1
功能:
- 仅在 Core 1 上运行
- 等待缓冲区就绪信号量(A 或 B)
- 获取就绪缓冲区的互斥锁
- 处理缓冲区中的所有样本
- 标记缓冲区为已处理并重置就绪标志
- 更新处理统计
- 输出结果(串口、MQTT、LCD)
核心隔离:Consumer 任务仅在 Core 1 上运行,确保处理的专用 CPU 时间
双缓冲机制¶
缓冲区大小:每个缓冲区 256 样本(可通过 RT_DMA_DC_BUFFER_SIZE 配置)
内存:两个缓冲区都从 PSRAM 分配
同步:
- 每个缓冲区的互斥锁(保护跨核心访问)
- 每个缓冲区的二进制信号量(信号表示就绪)
- 用于缓冲区就绪状态的易失性标志
操作流程:
- Producer(Core 0)填充缓冲区 A
- 当缓冲区 A 满时,Producer 切换到缓冲区 B 并通知 Consumer(Core 1)
- Consumer(Core 1)处理缓冲区 A,同时 Producer(Core 0)填充缓冲区 B
- 当缓冲区 B 满时,Producer 切换到缓冲区 A 并通知 Consumer
- Consumer 处理缓冲区 B,同时 Producer 填充缓冲区 A
- 循环重复,实现真正并行

图:双缓冲(乒乓缓冲)机制示意图,采用双核分工 - Producer 在 Core 0,Consumer 在 Core 1
核心绑定¶
任务使用 xTaskCreatePinnedToCore() 显式绑定到核心:
// Producer 在 Core 0
xTaskCreatePinnedToCore(
producer_task,
"rt_dma_dc_producer",
RT_DMA_DC_PRODUCER_STACK_SIZE,
NULL,
RT_DMA_DC_PRODUCER_PRIORITY,
&s_producer_task_handle,
RT_DMA_DC_PRODUCER_CORE // Core 0
);
// Consumer 在 Core 1
xTaskCreatePinnedToCore(
consumer_task,
"rt_dma_dc_consumer",
RT_DMA_DC_CONSUMER_STACK_SIZE,
NULL,
RT_DMA_DC_CONSUMER_PRIORITY,
&s_consumer_task_handle,
RT_DMA_DC_CONSUMER_CORE // Core 1
);
DMA 支持¶
- 自动 DMA:ESP-IDF SPI 驱动自动使用 DMA 进行传输
- CPU 减少:DMA 处理数据传输,释放 Core 0 用于其他任务
- 非阻塞:使用 DMA 的 SPI 操作是非阻塞的
数据流¶
ESP 定时器 → 定时器回调 → xTaskNotify → Producer 任务(Core 0)
↓
读取传感器(SPI + DMA)
↓
活动缓冲区(A 或 B)
↓
缓冲区满 → 切换缓冲区
↓
信号量信号 → Consumer 任务(Core 1)
↓
处理缓冲区(Core 1)
↓
┌───────────────────┴───────────────────┐
↓ ↓ ↓
串口 MQTT LCD
配置¶
默认配置¶
#define RT_DMA_DC_BUFFER_SIZE 256
#define RT_DMA_DC_PRODUCER_PRIORITY 10
#define RT_DMA_DC_CONSUMER_PRIORITY 10
#define RT_DMA_DC_PRODUCER_STACK_SIZE 4096
#define RT_DMA_DC_CONSUMER_STACK_SIZE 8192
#define RT_DMA_DC_PRODUCER_CORE 0
#define RT_DMA_DC_CONSUMER_CORE 1
配置参数¶
- 采样频率:0.1 - 10000 Hz(初始化时验证)
- 缓冲区大小:每个缓冲区 256 样本(编译时固定)
- 总内存:256 × 2 × 32 字节 = 16 KB,来自 PSRAM
- Producer 核心:Core 0(固定)
- Consumer 核心:Core 1(固定)
功能特性¶
优势¶
- 最高性能:利用两个 CPU 核心实现真正并行
- 核心隔离:每个任务有专用 CPU 核心
- 低 CPU 使用率:DMA 处理传输,Core 0 有更多空闲时间
- 最高吞吐量:计算密集型处理的最佳性能
- 无核心竞争:Producer 和 Consumer 从不竞争同一核心
限制¶
- 需要双核:仅在双核 ESP32 平台上工作
- 内存使用较高:需要两个缓冲区(双倍内存)
- 固定核心分配:核心分配在编译时固定
- 缓冲区覆盖风险:如果 Consumer 无法跟上,缓冲区可能被覆盖
性能特征¶
适用频率范围¶
- 有效范围:0.1 - 10000 Hz(初始化时验证)
- 推荐:最高性能需求(通常 1 kHz - 10 kHz)
- 最大:最高 10 kHz(验证限制)
- 最小:0.1 Hz(实际限制)
资源使用¶
- 内存:两个缓冲区(256 样本 × 2 × 32 字节 = 16 KB,来自 PSRAM)
- CPU Core 0:Producer 任务(采集)
- CPU Core 1:Consumer 任务(处理)
- 同步:两个互斥锁和两个二进制信号量(跨核心)
使用注意事项¶
- 双核要求:此架构需要双核 ESP32 平台
- 初始化顺序:必须在
arch_dma_dc_set_sensor_handle()之前调用arch_dma_dc_init() - 传感器句柄:启动前必须设置
- 缓冲区监控:监控统计中的
overwrite_count以检测 Consumer 是否落后 - 核心验证:任务在启动时记录其核心 ID 用于验证
- 处理延迟:处理延迟取决于缓冲区填充时间(buffer_size / sampling_frequency)
错误处理¶
- 互斥锁超时:如果无法在 10ms 内获取互斥锁,Producer 丢弃样本
- 缓冲区未初始化:如果缓冲区未就绪,丢弃样本
- 传感器读取失败:记录日志但不停止处理
- 任务创建失败:返回错误,清理资源
线程安全¶
- 跨核心同步:互斥锁和信号量跨核心工作
- 任务隔离:Producer(Core 0)和 Consumer(Core 1)正确隔离
- 信号量通知:二进制信号量提供安全的跨核心通信
- 统计:在互斥锁保护的部分内原子更新
核心分配验证¶
任务在启动时验证其核心分配:
ESP_LOGI(TAG, "Producer task started on Core %d (DMA + Dual Core mode)",
xPortGetCoreID());
ESP_LOGI(TAG, "Consumer task started on Core %d (DMA + Dual Core mode)",
xPortGetCoreID());
这允许验证任务是否在正确的核心上运行。