跳转至

说明

概述

离线传感模块提供高频批量传感器数据采集和存储功能。它专为需要精确、带时间戳的数据采集场景而设计,支持可配置的采样参数和多种存储选项。

功能特性

  • ✅ 高频采样(默认:100 Hz,范围:1 - 4000 Hz,限制基于ADXL355传感器的最大ODR)
  • ✅ 可配置采样时长(默认:10秒,范围:0.1 - 3600秒,受 PSRAM 内存限制)
  • ✅ 内存缓冲区存储(启用/禁用)
  • ✅ SD卡存储(启用/禁用)
  • ✅ 采样完成后MQTT报告(启用/禁用)
  • ✅ 基于高精度定时器的采样(ESP定时器)
  • ✅ 带时间戳和参数的自动文件命名
  • ✅ 线程安全操作
  • ✅ 支持延迟和定时启动

架构

基于定时器的采样

模块使用ESP定时器进行精确的周期性采样:

  1. 定时器创建:创建周期定时器,周期 = 1 / 采样频率
  2. 定时器回调:在每个定时器滴答时执行,读取并存储传感器数据
  3. 数据存储:将数据存储在内存缓冲区和/或SD卡
  4. 完成报告:生成报告并可选择通过MQTT发送

数据流

ESP定时器 → 定时器回调 → 传感器读取 → 内存缓冲区 → SD卡存储
                                                          MQTT报告

实现原理

架构概述

离线感知模块采用**基于定时器的批量采集架构**,使用ESP-IDF的高精度定时器系统。设计优先考虑高频数据采集和可靠存储,采用两阶段方法:实时采集和后处理存储。

核心组件

  1. ESP定时器(esp_timer)

    • 高精度硬件定时器,微秒级精度
    • 周期定时器模式:以固定间隔触发回调
    • 定时器周期:period_us = 1,000,000 / 采样频率_hz
    • 定时器回调在定时器上下文中执行(高优先级,类似中断)
  2. 定时器回调函数

    • 在每个定时器滴答时同步执行
    • 执行传感器读取操作(非阻塞SPI)
    • 将数据存储到内存缓冲区(互斥锁保护)
    • 为每个样本记录时间戳
    • 最小化处理时间,避免错过定时器滴答
  3. 内存缓冲区

    • 根据采样参数预分配的数组
    • 从PSRAM(外部RAM)分配,支持高频/长时间采样的大缓冲区
    • 使用FreeRTOS互斥锁进行线程安全访问(SemaphoreHandle_t
    • 存储结构化数据:{timestamp_us, x, y, z, temp}
    • 采样期间基于索引的顺序写入
  4. SD卡存储

    • 后处理:采样完成后写入所有采集的数据
    • 阻塞式文件I/O操作(在调用任务上下文中运行)
    • CSV格式,带标题行
    • 带时间戳和参数的自动文件名生成
  5. 阻塞执行模型

    • offline_sensing_start() 阻塞直到采样时长完成
    • 自动停止机制:当达到预期样本数时,定时器回调自动停止
    • 预期样本数 = (频率 × 时长) + 1(包括t=0的样本)
    • 使用 vTaskDelay() 等待采样时长(带余量)
    • 所有后处理(SD写入、MQTT报告)在采样后进行

编程工具与API

  • ESP-IDF定时器APIesp_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/Ofopen()fprintf()fclose() 用于SD卡操作
  • ESP-IDF MQTT客户端esp_mqtt_client_publish() 用于报告传输
  • FreeRTOS任务管理vTaskDelay() 用于阻塞等待

执行流程

  1. 初始化:根据 频率 × 时长 分配内存缓冲区
  2. 启动
    • 记录开始时间戳
    • 创建并启动周期定时器
    • 执行立即首次采样
  3. 采样阶段(阻塞):
    • 定时器回调以固定间隔触发
    • 通过SPI读取传感器并存储到内存缓冲区(互斥锁保护)
    • 达到预期样本数时自动停止:当 s_total_samples >= s_expected_samples 时,回调设置 s_is_running = false
    • 使用 vTaskDelay() 等待时长(带余量),确保所有样本都已采集
  4. 后处理阶段(采样后):
    • 停止并删除定时器
    • 将内存缓冲区写入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.csvBootXXXXX_FXXXX_DXXXX.csv
  • CSV格式:timestamp_us,x,y,z,temp

文件命名

模块根据采样开始时间、频率和时长自动生成文件名。格式取决于系统时间是否可用:

带系统时间:

20250116120000_F0100_D0010.csv

  • 20250116120000:日期和时间(YYYYMMDDHHMMSS格式)

  • F0100:频率(4位数字,零填充,例如100 Hz)

  • D0010:时长(4位数字,零填充,例如10秒)

无系统时间(启动时间):

Boot12345_F0100_D0010.csv

  • 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
}

使用流程

  1. 初始化传感器:初始化ADXL355传感器
  2. 设置传感器句柄:调用 offline_sensing_set_sensor_handle()
  3. 初始化模块:调用 offline_sensing_init() 并传入配置(如果启用内存,则分配PSRAM缓冲区)
  4. 启动传感:调用 offline_sensing_start()(阻塞直到完成,达到预期样本数时自动停止)
  5. 获取报告:调用 offline_sensing_get_report() 获取结果
  6. 获取数据(可选):调用 offline_sensing_get_memory_data() 检索样本
  7. 清除内存(可选):调用 offline_sensing_clear_memory() 重置缓冲区索引
  8. 检查状态(可选):调用 offline_sensing_is_running() 检查采样是否处于活动状态
  9. 反初始化:调用 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> - 在指定时间启动

这些功能在命令处理器包装任务中实现,不在核心离线传感模块中。