说明¶
Tip
太长不看版:对于需要实时处理的任务,推荐:
- (1) 开启DMA采集数据;
- (2) 数据生产和消费分别采用独立任务来执行,通过缓冲区进行解耦;
- (3) 如果需要窗口重叠处理,选择环形缓冲区架构,否则选择双缓冲架构以提高吞吐量;
- (4) 根据处理需求选择单核或双核处理。
概述¶
实时传感与处理模块提供传感器数据采集和实时计算与特征提取功能。与专注于数据采集和传输的在线和离线传感模块不同,本模块增加了实时处理能力,支持在数据采集过程中进行设备端计算和特征提取。
该模块实现了三种不同的架构,每种架构针对不同的性能需求和使用场景进行了优化。所有三种架构都遵循相同的基本设计模式:数据生产 → 数据缓存 → 数据消费,但在每个阶段的实现细节上有所不同。
架构选择通过编译时宏定义完成,无论底层实现如何,都提供统一的 API 接口。
与其他模块的关系¶
功能对比¶
| 模块 | 主要功能 | 处理能力 | 使用场景 |
|---|---|---|---|
| 在线传感 | 实时数据采集与传输 | 无 | 连续监控、实时数据流 |
| 离线传感 | 高频批量采集与存储 | 无(后处理) | 批量数据采集用于后续分析 |
| 实时传感处理 | 实时采集 + 实时处理 | 实时计算 | 设备端特征提取、边缘AI |
何时使用哪个模块¶
使用在线传感当:
- 需要实时流式传输原始传感器数据
- 数据处理将在远程服务器或云平台完成
- 需要简单、低延迟的数据传输
- 采样频率适中(0.1 - 300 Hz)
使用离线传感当:
- 需要采集高频数据用于后续分析
- 将进行后处理和分析
- 需要长时间数据采集
- 采样频率较高(最高 4000 Hz)
使用实时传感处理当:
- 需要实时计算和特征提取
- 需要设备端处理(边缘AI、实时检测)
- 处理必须跟上数据采集速率
- 需要基于处理数据的即时反馈
技术架构对比¶
在线传感:
- 单任务 + ESP 定时器回调
- 直接数据格式化和传输
- 简单、轻量级实现
- 适用于低到中等频率
离线传感:
- 单任务 + ESP 定时器回调
- 内存缓冲区 + SD 卡存储
- 两阶段方法:采集然后存储
- 适用于高频批量采集
实时传感处理:
- 多任务架构(Producer-Consumer 模式)
- 三种架构选项,具有不同的优化
- 并行数据采集和处理
- 适用于实时计算需求
架构组成部分¶
所有三种实时处理架构都遵循相同的三阶段流水线:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 数据生产 │───▶│ 数据缓存 │───▶│ 数据消费 │
│ (Producer) │ │ (Buffer/Cache) │ │ (Consumer) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
阶段一:数据生产¶
生产者 阶段负责以固定间隔采集传感器数据。所有三种架构都使用相同的基本方法:
- 定时器驱动:ESP 定时器以配置的采样频率触发数据采集
- 任务通知:定时器回调通过
xTaskNotify通知生产者任务(非阻塞) - 传感器读取:生产者任务通过 SPI 读取 ADXL355 加速度计数据(x、y、z)和温度
- 样本准备:将数据打包到
rt_process_sample_t结构中,并添加时间戳
架构之间的差异:
| 架构 | SPI 传输方法 | CPU 使用率 | 说明 |
|---|---|---|---|
| 架构1:Producer-Consumer | 标准 SPI(CPU 驱动) | 中等 | 直接 SPI 读取,CPU 处理传输 |
| 架构2:DMA + 双缓冲 | DMA 辅助 SPI | 低 | SPI 驱动自动使用 DMA 进行传输 |
| 架构3:DMA + 双核 | DMA 辅助 SPI | 最低 | 与架构2相同,但生产者绑定到 Core 0 |
阶段二:数据缓存¶
缓存 阶段将数据生产与消费解耦,允许并行操作。这是三种架构差异最大的地方:
架构1:环形缓冲区¶
- 缓冲区类型:单个环形缓冲区(FIFO)
- 大小:512 样本(可配置)
- 内存:PSRAM 分配
- 同步机制:互斥锁保护访问
- 行为:缓冲区满时 FIFO 覆盖(最旧数据被覆盖)
- 写入策略:生产者写入当前写指针位置,循环推进
- 读取策略:消费者从读指针读取,循环推进,支持任意位置和长度的数据访问
- 溢出处理:缓冲区满时读指针自动推进(覆盖)

图:环形缓冲区操作,显示生产者写入和消费者读取,以及 FIFO 覆盖行为
环形缓冲区的核心优势:
环形缓冲区是**实时 DSP 应用的理想选择**,特别适合需要**窗口重叠处理**的场景:
- 灵活的窗口访问:消费者可以从缓冲区任意位置读取任意长度的数据窗口,不受缓冲区边界限制
- 窗口重叠支持:可以轻松实现滑动窗口、重叠窗口等 DSP 算法(如 FFT、STFT、滤波器组)
- 连续数据流:数据在环形缓冲区中连续存储,支持跨边界读取,无需数据复制或重组
- 低延迟处理:可以处理最新的 N 个样本,或从历史数据中提取任意时间窗口
- 内存效率:单个缓冲区,内存占用最小
典型应用场景: - FFT/STFT 分析:需要滑动窗口,每次处理 256 个样本,但窗口每次只滑动 128 个样本(50% 重叠) - 滤波器组:需要多个重叠的时间窗口进行频域分析 - 特征提取:需要从连续数据流中提取不同时间尺度的特征 - 实时频谱分析:需要连续、重叠的频谱计算
双缓冲的局限性: 双缓冲架构在处理窗口重叠时存在明显限制: - 固定窗口边界:只能处理整个缓冲区(512 个样本),无法灵活选择窗口位置和大小 - 窗口重叠困难:要实现重叠窗口,需要额外的数据复制和重组,增加延迟和内存开销 - 不连续的数据访问:数据分布在两个独立缓冲区中,跨缓冲区访问需要复杂的数据管理 - 不适合滑动窗口:每次处理必须等待整个缓冲区填满,无法实现灵活的滑动窗口处理
架构2:DMA + 双缓冲(乒乓)¶
- 缓冲区类型:两个独立缓冲区(A 和 B),乒乓配置
- 大小:每个缓冲区 512 样本(可配置)
- 内存:两个缓冲区都从 PSRAM 分配
- 同步机制:每个缓冲区互斥锁 + 缓冲区就绪信号量
- 行为:生产者填充一个缓冲区,同时消费者处理另一个缓冲区
- 写入策略:生产者写入活动缓冲区(A 或 B),满时切换
- 读取策略:消费者等待缓冲区就绪信号量,处理整个缓冲区
- 溢出处理:即使前一个缓冲区未完全处理,生产者也会切换缓冲区(检测到覆盖)

图:双缓冲(乒乓)操作,显示并行采集和处理
架构3:DMA + 双核 + 双缓冲¶
- 缓冲区类型:两个独立缓冲区(A 和 B),乒乓配置
- 大小:每个缓冲区 256 样本(可配置,双核效率优化,更小)
- 内存:两个缓冲区都从 PSRAM 分配
- 同步机制:每个缓冲区互斥锁 + 缓冲区就绪信号量
- 行为:与架构2相同,但具有核心绑定
- 写入策略:生产者(Core 0)写入活动缓冲区,满时切换
- 读取策略:消费者(Core 1)等待缓冲区就绪信号量,处理整个缓冲区
- 溢出处理:与架构2相同
- 核心分配:生产者运行在 Core 0,消费者运行在 Core 1(真正的并行)

图:双核分工的双缓冲(Core 0:生产者,Core 1:消费者)
阶段三:数据消费¶
消费者 阶段处理缓冲数据并执行实时计算。所有三种架构使用相同的处理逻辑,但在访问缓冲区的方式上有所不同:
处理示例:加速度检测¶
出于演示目的,模块实现了**加速度检测**作为数据消费示例:
检测条件:
|x| > 0.5g OR |y| > 0.5g OR |z| < 0.5g
输出:
- LCD 可视反馈:满足条件时显示红色,否则显示白色
- 颜色保持:0.3 秒保持持续时间
- 串口输出:可选的处理数据输出
- MQTT 输出:可选的处理结果发布
架构特定的消费细节¶
| 架构 | 缓冲区访问 | 处理策略 | 样本数量 |
|---|---|---|---|
| 架构1:Producer-Consumer | 互斥锁保护的环形缓冲区读取 | 灵活处理:最后 N 个样本、任意窗口、支持重叠窗口 | 每个周期最多 10 个样本(可配置),支持任意窗口大小和位置 |
| 架构2:DMA + 双缓冲 | 信号量等待缓冲区就绪,然后处理整个缓冲区 | 固定处理整个缓冲区,不支持窗口重叠 | 每个周期 512 个样本(固定) |
| 架构3:DMA + 双核 | 信号量等待缓冲区就绪,然后处理整个缓冲区 | 固定处理整个缓冲区,不支持窗口重叠 | 每个周期 256 个样本(固定) |
通用处理流程:
- 等待数据可用(互斥锁/信号量)
- 从缓冲区读取样本
- 对每个样本执行加速度检测
- 根据检测结果更新 LCD 颜色
- 输出结果(串口、MQTT(如果启用))
- 更新处理统计信息
架构对比¶
下表总结了三种架构在所有三个阶段的关键差异:
| 特性 | 架构1:Producer-Consumer | 架构2:DMA + 双缓冲 | 架构3:DMA + 双核 |
|---|---|---|---|
| 数据生产 | |||
| SPI 传输 | DMA 辅助 SPI | DMA 辅助 SPI | DMA 辅助 SPI |
| 数据缓存 | |||
| 缓冲区类型 | 环形缓冲区 | 双缓冲(乒乓) | 双缓冲(乒乓) |
| 缓冲区大小 | 512 样本 | 512 样本 × 2 | 256 样本 × 2 |
| 同步机制 | 互斥锁 | 互斥锁 + 信号量 | 互斥锁 + 信号量 |
| 数据消费 | |||
| 处理策略 | 最后 N 个样本(10) | 整个缓冲区(512) | 整个缓冲区(256) |
| 核心分配 | 自动(FreeRTOS) | 自动(FreeRTOS) | Core 0:生产者,Core 1:消费者 |
| 整体性能 | |||
| 频率范围 | 0.1 - 1000 Hz | 0.1 - 10000 Hz | 0.1 - 10000 Hz |
| 适用频率 | 100 Hz - 1 kHz(推荐) | 1 kHz - 10 kHz(推荐) | 最高性能(推荐) |
| 实现复杂度 | 低 | 中等 | 高 |
| 内存使用 | 环形缓冲区(512 样本) | 两个缓冲区(每个 512 样本) | 两个缓冲区(每个 256 样本) |
| 优势 | 简单、解耦、支持窗口重叠、灵活的数据访问 | 高吞吐量、并行处理 | 最高性能、真正并行 |
| 限制 | 缓冲区可能成为瓶颈 | 内存使用较高、不支持窗口重叠、固定窗口大小 | 需要双核支持、不支持窗口重叠、固定窗口大小 |
架构选择指南¶
Tip
数据生产和消费可以分别采用独立任务来执行,数据采集建议开启DMA辅助SPI采集数据,数据消费可以根据实际需求选择单核或双核处理。至于缓冲区处理,如果需要实时处理且数据之间有重叠,建议采用环形缓冲区;如果数据之间没有重叠,且倾向批次处理可以选择双缓冲区以提高吞吐量。
选择架构1(Producer-Consumer)当:
- 需要窗口重叠处理(如 FFT、STFT、滤波器组等实时 DSP 算法)
- 需要灵活的数据窗口访问(滑动窗口、任意位置和长度的数据提取)
- 采样频率适中(0.1 - 1000 Hz,推荐:100 Hz - 1 kHz)
- 偏好简单实现
- 内存资源有限
- 需要简单、易于理解的模式
- 处理可以处理小批次(10 个样本)或任意大小的窗口
选择架构2(DMA + 双缓冲)当:
- 不需要窗口重叠处理(处理算法不需要滑动窗口或重叠窗口)
- 采样频率较高(0.1 - 10000 Hz,推荐:1 kHz - 10 kHz)
- 需要高吞吐量
- 应最小化 CPU 使用率
- 并行采集和处理是有益的
- 处理受益于更大的批次(512 个样本),且可以接受固定窗口大小
- 注意:如果您的 DSP 算法需要窗口重叠(如 FFT 重叠、STFT),请选择架构1
选择架构3(DMA + 双核)当:
- 不需要窗口重叠处理(处理算法不需要滑动窗口或重叠窗口)
- 需要最高性能
- 拥有双核 ESP32 平台
- 处理工作负载计算密集
- 需要在采集和处理之间实现真正的并行
- 处理受益于专用核心分配,且可以接受固定窗口大小
- 注意:如果您的 DSP 算法需要窗口重叠(如 FFT 重叠、STFT),请选择架构1
统一 API¶
所有三种架构共享相同的统一 API 接口,使得在实现之间切换而无需更改应用程序代码变得容易。架构选择通过编译时 RT_PROCESS_ARCH_TYPE 宏完成。
编译时架构选择¶
// 默认:Producer-Consumer
#include "real-time-process-arch.h"
// 或显式选择架构:
#define RT_PROCESS_ARCH_TYPE RT_ARCH_PRODUCER_CONSUMER
#include "real-time-process-arch.h"
// 或选择 DMA + 双缓冲:
#define RT_PROCESS_ARCH_TYPE RT_ARCH_DMA_DOUBLE_BUFFER
#include "real-time-process-arch.h"
// 或选择 DMA + 双核:
#define RT_PROCESS_ARCH_TYPE RT_ARCH_DMA_DUAL_CORE
#include "real-time-process-arch.h"
API 函数¶
所有架构提供以下统一接口:
rt_process_init()- 初始化架构rt_process_set_sensor_handle()- 设置 ADXL355 传感器句柄rt_process_start()- 启动实时处理rt_process_stop()- 停止实时处理rt_process_deinit()- 反初始化架构rt_process_get_status()- 获取当前状态rt_process_get_stats()- 获取性能统计rt_process_set_frequency()- 更新采样频率rt_process_is_running()- 检查处理是否正在运行rt_process_set_accel_detection()- 启用/禁用加速度检测
配置¶
配置结构¶
typedef struct
{
float sampling_frequency_hz; // 采样频率(Hz,默认:100.0)
uint32_t queue_size; // Producer-Consumer 的队列大小(默认:50)
bool enable_mqtt; // 启用 MQTT 输出(默认:false)
bool enable_serial; // 启用串口输出(默认:true)
bool enable_accel_detection; // 启用加速度检测和 LCD 反馈(默认:false)
const char *mqtt_topic; // MQTT 主题(默认:NULL 使用默认主题)
} rt_process_config_t;
默认配置¶
- 采样频率:100.0 Hz
- 队列大小(架构1):50 样本
- 缓冲区大小(架构⅔):512 样本(架构2),256 样本(架构3)
- MQTT 输出:禁用
- 串口输出:启用
- 加速度检测:禁用
性能统计¶
该模块收集全面的性能统计信息用于监控和优化:
typedef struct
{
uint32_t total_samples; // 采集的样本总数
uint32_t processed_samples; // 已处理的样本数
uint32_t dropped_samples; // 丢弃的样本数(队列/缓冲区满)
float avg_acquisition_time_us; // 每个样本的平均采集时间
float avg_process_time_us; // 每个样本的平均处理时间
float cpu_usage_core0; // Core 0 的 CPU 使用率(%)
float cpu_usage_core1; // Core 1 的 CPU 使用率(%)(用于双核架构)
uint64_t last_sample_time_us; // 最后一个样本的时间戳
} rt_process_stats_t;
使用流程¶
- 初始化:使用配置调用
rt_process_init()(或使用 NULL 使用默认值) - 设置传感器句柄:使用 ADXL355 句柄调用
rt_process_set_sensor_handle() - 启动处理:调用
rt_process_start()开始实时采集和处理 - 监控(可选):使用
rt_process_get_status()和rt_process_get_stats()监控性能 - 停止处理:调用
rt_process_stop()停止采集和处理 - 反初始化:调用
rt_process_deinit()清理资源
功能特性¶
实时处理¶
- 从传感器数据中实时特征提取
- 可配置的处理算法
- 低延迟处理管道
多路输出通道¶
- MQTT:将处理结果发布到 MQTT 代理
- 串口:通过串口输出处理数据
- LCD:加速度检测的可视反馈(可选)
加速度检测¶
启用后,模块可以检测特定的加速度条件并通过 LCD 提供可视反馈:
- 条件:
|x| > 0.5g OR |y| > 0.5g OR |z| < 0.5g - LCD 在满足条件时显示红色,否则显示白色
- 颜色保持:0.3 秒保持持续时间
线程安全¶
- 所有架构使用适当的同步机制
- Producer 和 Consumer 任务正确隔离
- 缓冲区/队列访问由互斥锁或信号量保护
错误处理¶
常见错误代码:
ESP_ERR_INVALID_ARG:无效的配置参数ESP_ERR_INVALID_STATE:当前状态下不允许的操作ESP_ERR_NO_MEM:内存分配失败ESP_FAIL:一般失败
集成注意事项¶
传感器集成¶
- 需要 ADXL355 传感器句柄(使用前必须初始化)
- 必须在启动处理前设置传感器句柄
- 与现有 ADXL355 驱动接口兼容
MQTT 集成¶
- 需要 MQTT 客户端已连接
- 如果
mqtt_topic为 NULL,使用默认主题 - 非阻塞发布操作
LCD 集成¶
- 可选的 LCD 支持用于加速度检测反馈
- 需要 LCD 驱动已初始化
- 使用颜色显示进行可视反馈
文档¶
- 统一接口代码:统一接口的完整代码
- 架构1:Producer-Consumer:详细文档
- 架构2:DMA + 双缓冲:详细文档
- 架构3:DMA + 双核:详细文档