说明¶
概述¶
离线传感模块提供高频批量传感器数据采集和存储功能。它专为需要精确、带时间戳的数据采集场景而设计,支持可配置的采样参数和多种存储选项。
功能特性¶
- ✅ 高频采样(默认:100 Hz,范围:1 - 4000 Hz,限制基于ADXL355传感器的最大ODR)
- ✅ 可配置采样时长(默认:10秒,范围:0.1 - 3600秒,受 PSRAM 内存限制)
- ✅ 内存缓冲区存储(启用/禁用)
- ✅ SD卡存储(启用/禁用)
- ✅ 采样完成后MQTT报告(启用/禁用)
- ✅ 基于高精度定时器的采样(ESP定时器)
- ✅ 带时间戳和参数的自动文件命名
- ✅ 线程安全操作
- ✅ 支持延迟和定时启动
架构¶
基于定时器的采样¶
模块使用ESP定时器进行精确的周期性采样:
- 定时器创建:创建周期定时器,周期 = 1 / 采样频率
- 定时器回调:在每个定时器滴答时执行,读取并存储传感器数据
- 数据存储:将数据存储在内存缓冲区和/或SD卡
- 完成报告:生成报告并可选择通过MQTT发送
数据流¶
实现原理¶
架构概述¶
离线感知模块采用**基于定时器的批量采集架构**,使用ESP-IDF的高精度定时器系统。设计优先考虑高频数据采集和可靠存储,采用两阶段方法:实时采集和后处理存储。
核心组件¶
-
ESP定时器(esp_timer)
- 高精度硬件定时器,微秒级精度
- 周期定时器模式:以固定间隔触发回调
- 定时器周期:
period_us = 1,000,000 / 采样频率_hz - 定时器回调在定时器上下文中执行(高优先级,类似中断)
-
定时器回调函数
- 在每个定时器滴答时同步执行
- 执行传感器读取操作(非阻塞SPI)
- 将数据存储到内存缓冲区(互斥锁保护)
- 为每个样本记录时间戳
- 最小化处理时间,避免错过定时器滴答
-
内存缓冲区
- 根据采样参数预分配的数组
- 从PSRAM(外部RAM)分配,支持高频/长时间采样的大缓冲区
- 使用FreeRTOS互斥锁进行线程安全访问(
SemaphoreHandle_t) - 存储结构化数据:
{timestamp_us, x, y, z, temp} - 采样期间基于索引的顺序写入
-
SD卡存储
- 后处理:采样完成后写入所有采集的数据
- 阻塞式文件I/O操作(在调用任务上下文中运行)
- CSV格式,带标题行
- 带时间戳和参数的自动文件名生成
-
阻塞执行模型
offline_sensing_start()阻塞直到采样时长完成- 自动停止机制:当达到预期样本数时,定时器回调自动停止
- 预期样本数 =
(频率 × 时长) + 1(包括t=0的样本) - 使用
vTaskDelay()等待采样时长(带余量) - 所有后处理(SD写入、MQTT报告)在采样后进行
编程工具与API¶
- ESP-IDF定时器API:
esp_timer_create()、esp_timer_start_periodic()、esp_timer_get_time() - ESP-IDF堆管理:
heap_caps_malloc()、heap_caps_free()使用MALLOC_CAP_SPIRAM进行PSRAM分配 - FreeRTOS同步:
xSemaphoreCreateMutex()用于线程安全的缓冲区访问 - 标准C文件I/O:
fopen()、fprintf()、fclose()用于SD卡操作 - ESP-IDF MQTT客户端:
esp_mqtt_client_publish()用于报告传输 - FreeRTOS任务管理:
vTaskDelay()用于阻塞等待
执行流程¶
- 初始化:根据
频率 × 时长分配内存缓冲区 - 启动:
- 记录开始时间戳
- 创建并启动周期定时器
- 执行立即首次采样
- 采样阶段(阻塞):
- 定时器回调以固定间隔触发
- 通过SPI读取传感器并存储到内存缓冲区(互斥锁保护)
- 达到预期样本数时自动停止:当
s_total_samples >= s_expected_samples时,回调设置s_is_running = false - 使用
vTaskDelay()等待时长(带余量),确保所有样本都已采集
- 后处理阶段(采样后):
- 停止并删除定时器
- 将内存缓冲区写入SD卡(如果启用)
- 生成带统计信息的报告
- 发送MQTT报告(如果启用)
- 从阻塞函数返回
关键设计决策¶
- 两阶段设计:将实时采集(定时器回调)与存储(后处理)分离
- PSRAM分配:使用外部PSRAM作为内存缓冲区,支持大缓冲区(通常有8MB可用)
- 内存缓冲区优先:先将所有数据存储在RAM中,然后批量写入SD卡
- 自动停止机制:定时器回调在达到预期样本数时自动停止采样
- 阻塞模型:简化状态管理,确保数据完整性
- 互斥锁保护:防止定时器回调和主任务之间的竞争条件
- 立即首次采样:确保精确的样本计数(N秒内N个样本,频率N Hz,包括t=0样本)
配置¶
默认配置¶
默认值在 tiny_measurement_config.h 中定义:
#define TINY_MEASUREMENT_OFFLINE_SENSING_DEFAULT_FREQ_HZ 100.0f
#define TINY_MEASUREMENT_OFFLINE_SENSING_DEFAULT_DURATION_SEC 10.0f
#define TINY_MEASUREMENT_OFFLINE_SENSING_DEFAULT_ENABLE_MEMORY true
#define TINY_MEASUREMENT_OFFLINE_SENSING_DEFAULT_ENABLE_SD true
#define TINY_MEASUREMENT_OFFLINE_SENSING_DEFAULT_ENABLE_MQTT_REPORT true
配置结构¶
typedef struct
{
float sampling_frequency_hz; // 采样频率(Hz)
float sampling_duration_sec; // 采样时长(秒)
bool enable_memory; // 启用内存缓冲区存储
bool enable_sd; // 启用SD卡存储
bool enable_mqtt_report; // 采样完成后启用MQTT报告
const char *sd_file_path; // SD卡文件路径(NULL自动生成)
const char *mqtt_report_topic; // MQTT报告主题(NULL使用默认)
} offline_sensing_config_t;
数据存储¶
内存缓冲区¶
- 在采样期间将样本存储在**PSRAM(外部RAM)**中
- 使用
heap_caps_malloc()和MALLOC_CAP_SPIRAM从PSRAM自动分配 - 使用互斥锁进行线程安全访问(
xSemaphoreTake/xSemaphoreGive) - 采样完成后可以通过
offline_sensing_get_memory_data()检索 - 可以通过
offline_sensing_clear_memory()清除 - 缓冲区大小 =
(频率 × 时长) + 100个样本(带余量)
SD卡存储¶
- 将数据保存为CSV文件
- 带时间戳和参数的自动文件名生成
- 格式:
YYYYMMDDHHMMSS_FXXXX_DXXXX.csv或BootXXXXX_FXXXX_DXXXX.csv - CSV格式:
timestamp_us,x,y,z,temp
文件命名¶
模块根据采样开始时间、频率和时长自动生成文件名。格式取决于系统时间是否可用:
带系统时间:
-
20250116120000:日期和时间(YYYYMMDDHHMMSS格式) -
F0100:频率(4位数字,零填充,例如100 Hz) -
D0010:时长(4位数字,零填充,例如10秒)
无系统时间(启动时间):
-
Boot12345:启动时间(秒,自启动以来的最后5位数字) -
F0100:频率(4位数字,零填充) -
D0010:时长(4位数字,零填充)
注意:系统会自动检测系统时间是否未设置(1970-01-01),并回退到启动时间格式。
数据格式¶
CSV格式¶
timestamp_us,x,y,z,temp
1234567890,0.012345,-0.045678,0.987654,25.50
1234567891,0.012346,-0.045679,0.987655,25.51
...
timestamp_us:自启动以来的时间戳(微秒)x, y, z:加速度值(g,6位小数)temp:温度(°C,2位小数)
MQTT报告格式¶
{
"samples": 1000,
"freq_hz": 100.00,
"duration_sec": 10.00,
"memory_ok": true,
"sd_ok": true,
"sd_path": "/sdcard/20250116120000_F0100_D0010.csv",
"start_us": 1234567890,
"end_us": 1334567890
}
使用流程¶
- 初始化传感器:初始化ADXL355传感器
- 设置传感器句柄:调用
offline_sensing_set_sensor_handle() - 初始化模块:调用
offline_sensing_init()并传入配置(如果启用内存,则分配PSRAM缓冲区) - 启动传感:调用
offline_sensing_start()(阻塞直到完成,达到预期样本数时自动停止) - 获取报告:调用
offline_sensing_get_report()获取结果 - 获取数据(可选):调用
offline_sensing_get_memory_data()检索样本 - 清除内存(可选):调用
offline_sensing_clear_memory()重置缓冲区索引 - 检查状态(可选):调用
offline_sensing_is_running()检查采样是否处于活动状态 - 反初始化:调用
offline_sensing_deinit()清理资源(释放PSRAM缓冲区)
性能考虑¶
采样频率限制¶
- 最小值:1 Hz
- 最大值:4000 Hz - 此限制基于ADXL355传感器的最大输出数据率(ODR)
- 推荐值:大多数应用使用 10 - 1000 Hz
传感器相关限制
4000 Hz的最大采样频率由**ADXL355传感器的硬件能力**决定,该传感器支持的最大输出数据率为4000 Hz。如果使用具有更高采样能力的其他传感器模块,离线感知模块的架构理论上支持更高的采样频率。ESP定时器系统和内存缓冲区设计可以处理超过4000 Hz的频率,但实际限制将取决于传感器的最大ODR(输出数据率)。
内存需求¶
内存缓冲区大小 = (采样频率 × 采样时长 + 100) × sizeof(offline_sensing_sample_t)
- 缓冲区从**PSRAM(外部RAM)**分配,而非内部RAM
- PSRAM通常提供8MB外部内存
- 示例:100 Hz × 10 秒 + 100 余量 = 1100 样本 × 32 字节 = 35.2 KB
- 高频示例:4000 Hz × 10 秒 + 100 = 40100 样本 × 32 字节 = 1.28 MB
PSRAM 内存限制
单次采样时长受可用 PSRAM 内存大小限制。
最大时长取决于采样频率,受 PSRAM 内存容量限制:
- 在 4000 Hz 下:理论上限约为 87 秒(实际测试中最长使用过 80 秒)
- 在 100 Hz 下:理论上限约为 3480 秒(约 58 分钟)
此限制是由于在写入 SD 卡之前,需要在内存缓冲区中存储所有样本所需的 PSRAM 内存容量。
计算示例: - 最大样本数 ≈ 8MB / 32 字节每样本 ≈ 262,144 样本 - 在 4000 Hz 下:最大时长 ≈ 262,144 / 4000 ≈ 65.5 秒(不含余量)→ 含余量约 87 秒 - 在 100 Hz 下:最大时长 ≈ 262,144 / 100 ≈ 2,621 秒(不含余量)→ 含余量约 3480 秒(约 58 分钟)
对于高频下的长时间采样,建议: - 使用较低的采样频率(例如:100 Hz 可达约 58 分钟,而 4000 Hz 仅约 87 秒) - 减少余量(不推荐) - 实现采样期间流式写入 SD 卡(未来增强功能)
资源使用¶
- CPU:定时器回调以采样频率执行
- 内存:根据采样参数从PSRAM分配缓冲区(支持高频/长时间采样的大缓冲区)
- 存储:SD卡写入速度可能在极高频率时限制最大频率
- 传感器:主要限制因素是传感器的最大ODR(输出数据率)。对于ADXL355,这是4000 Hz。如果使用更快的传感器,模块架构本身理论上可以支持更高的频率。
- 阻塞:
offline_sensing_start()阻塞直到采样完成
错误处理¶
常见错误¶
ESP_ERR_INVALID_ARG:无效频率或时长(超出范围)ESP_ERR_INVALID_STATE:已在运行或未初始化ESP_ERR_NO_MEM:内存分配失败(PSRAM不足)ESP_FAIL:SD卡写入失败
错误恢复¶
- 在启动前检查传感器句柄是否已设置
- 在初始化前验证频率和时长范围
- 确保SD卡已挂载且可写
- 在启动高频或长时间采样前检查可用PSRAM
- 在 4000 Hz 下,最大时长约为 87 秒(测试中最长使用过 80 秒)
- 在 100 Hz 下,最大时长约为 3480 秒(约 58 分钟)
- 内存需求:
(频率 × 时长 + 100) × 32 字节 - 确保在项目配置中启用了PSRAM
线程安全¶
- 定时器回调在定时器上下文中执行(高优先级)
- 内存缓冲区访问由互斥锁保护
- 状态检查防止并发操作
offline_sensing_start()是阻塞的(在调用任务上下文中运行)
集成注意事项¶
SD卡集成¶
- 需要SD卡已挂载
- 使用
MOUNT_POINT常量作为文件路径 - 文件操作使用标准C文件I/O
- 错误会被记录但不会停止采样
MQTT集成¶
- 需要MQTT客户端已连接
- 发布前检查
s_is_mqtt_connected - 报告在采样完成后发送
- 如果
mqtt_report_topic为NULL,使用默认主题/offline_sensing/report
传感器集成¶
- 需要ADXL355传感器句柄
- 必须在设置句柄前初始化
- 传感器读取操作是非阻塞的
- 传感器ODR限制:4000 Hz的最大值受ADXL355的最大ODR限制。模块架构是传感器无关的,理论上可以使用具有更高ODR能力的传感器支持更高的频率
延迟和定时启动¶
模块通过命令处理器支持延迟和定时启动:
- 延迟启动:
DL=<秒数>- 在指定延迟后启动 - 定时启动:
TIME=<YYMMDDHHMMSS>- 在指定时间启动
这些功能在命令处理器包装任务中实现,不在核心离线传感模块中。