跳转至

说明

概述

架构3实现了 DMA + 双核分工模式,其中数据采集和处理分配给不同的 CPU 核心。该架构通过利用双核 ESP32 平台的两个核心,提供最高性能,实现采集和处理之间的真正并行。

架构原理

双核分工

该架构在两个 CPU 核心之间分配工作:

  • Core 0:Producer 任务(使用 DMA 进行数据采集)
  • Core 1:Consumer 任务(数据处理)
  • 双缓冲:用于并行操作的乒乓缓冲区机制
  • 核心绑定:任务显式绑定到特定核心
  • 真正并行:采集和处理在不同核心上同时运行

关键设计决策

  1. 核心分配:Producer 在 Core 0,Consumer 在 Core 1
  2. 任务绑定:使用 xTaskCreatePinnedToCore() 确保任务在特定核心上运行
  3. 双缓冲:与架构2相同的乒乓机制
  4. DMA 支持:SPI 传输自动使用 DMA
  5. 核心隔离:每个核心处理其专用任务,无干扰

实现细节

基于定时器的采样

  • ESP 定时器:创建周期定时器,周期 = 1,000,000 / sampling_frequency_hz 微秒
  • 定时器回调:在定时器上下文中执行,通过 xTaskNotify 通知 Producer 任务(Core 0)
  • 立即首次采样:在启动周期定时器之前执行一次采样

Producer 任务(Core 0)

优先级:10(高优先级,用于及时采集)

核心分配:通过 xTaskCreatePinnedToCore() 绑定到 Core 0

功能

  1. 仅在 Core 0 上运行
  2. 通过 xTaskNotifyWait 等待定时器通知
  3. 读取传感器数据(DMA 自动处理 SPI 传输)
  4. 准备带时间戳的样本结构
  5. 在互斥锁保护下写入活动缓冲区(A 或 B)
  6. 当缓冲区满时:
  7. 标记缓冲区为就绪
  8. 切换到另一个缓冲区
  9. 通过信号量通知 Consumer(Core 1)
  10. 重置写索引
  11. 更新采集统计

核心隔离:Producer 任务仅在 Core 0 上运行,确保采集的专用 CPU 时间

Consumer 任务(Core 1)

优先级:10(高优先级,用于处理)

核心分配:通过 xTaskCreatePinnedToCore() 绑定到 Core 1

功能

  1. 仅在 Core 1 上运行
  2. 等待缓冲区就绪信号量(A 或 B)
  3. 获取就绪缓冲区的互斥锁
  4. 处理缓冲区中的所有样本
  5. 标记缓冲区为已处理并重置就绪标志
  6. 更新处理统计
  7. 输出结果(串口、MQTT、LCD)

核心隔离:Consumer 任务仅在 Core 1 上运行,确保处理的专用 CPU 时间

双缓冲机制

缓冲区大小:每个缓冲区 256 样本(可通过 RT_DMA_DC_BUFFER_SIZE 配置)

内存:两个缓冲区都从 PSRAM 分配

同步

  • 每个缓冲区的互斥锁(保护跨核心访问)
  • 每个缓冲区的二进制信号量(信号表示就绪)
  • 用于缓冲区就绪状态的易失性标志

操作流程

  1. Producer(Core 0)填充缓冲区 A
  2. 当缓冲区 A 满时,Producer 切换到缓冲区 B 并通知 Consumer(Core 1)
  3. Consumer(Core 1)处理缓冲区 A,同时 Producer(Core 0)填充缓冲区 B
  4. 当缓冲区 B 满时,Producer 切换到缓冲区 A 并通知 Consumer
  5. Consumer 处理缓冲区 B,同时 Producer 填充缓冲区 A
  6. 循环重复,实现真正并行

双缓冲

图:双缓冲(乒乓缓冲)机制示意图,采用双核分工 - 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 任务(处理)
  • 同步:两个互斥锁和两个二进制信号量(跨核心)

使用注意事项

  1. 双核要求:此架构需要双核 ESP32 平台
  2. 初始化顺序:必须在 arch_dma_dc_set_sensor_handle() 之前调用 arch_dma_dc_init()
  3. 传感器句柄:启动前必须设置
  4. 缓冲区监控:监控统计中的 overwrite_count 以检测 Consumer 是否落后
  5. 核心验证:任务在启动时记录其核心 ID 用于验证
  6. 处理延迟:处理延迟取决于缓冲区填充时间(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());

这允许验证任务是否在正确的核心上运行。