代码¶
Warning
以下代码应基于发布代码中的代码,可能已更新。
online_sensing.h¶
/**
* @file online_sensing.h
* @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
* @brief 在线传感模块 - 连续传感器数据采集和传输
* @version 1.0
* @date 2025-12-17
* @copyright Copyright (c) 2025
*
* @details
* 该模块提供在线传感器数据采集,可配置:
* - 采样频率(默认:1.0 Hz)
* - MQTT传输(启用/禁用)
* - 串口输出(启用/禁用)
*
* 功能特性:
* - 使用ESP定时器进行固定频率数据采集
* - 自动数据格式化(紧凑CSV格式)
* - 可配置输出通道(MQTT、串口、LCD)
* - 线程安全操作
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "node_acc_adxl355.h"
#include "tiny_measurement_config.h"
#ifdef __cplusplus
extern "C"
{
#endif
/* ============================================================================
* CONFIGURATION STRUCTURE
* ============================================================================ */
/**
* @brief 在线传感配置
*/
typedef struct
{
float sampling_frequency_hz; ///< 采样频率(Hz,默认:1.0)
bool enable_mqtt; ///< 启用MQTT传输(默认:true)
bool enable_serial; ///< 启用串口输出(默认:true)
#ifdef TINY_MEASUREMENT_ENABLE_LCD
bool enable_lcd; ///< 启用LCD显示输出(默认:false,仅在定义TINY_MEASUREMENT_ENABLE_LCD时可用)
#endif
const char *mqtt_topic; ///< MQTT发布主题(默认:NULL使用MQTT_PUBLISH_TOPIC)
} online_sensing_config_t;
/* ============================================================================
* DEFAULT CONFIGURATION
* ============================================================================ */
/**
* @brief 默认配置值(来自tiny_measurement_config.h宏)
*/
#define ONLINE_SENSING_DEFAULT_FREQ_HZ TINY_MEASUREMENT_ONLINE_SENSING_DEFAULT_FREQ_HZ
#define ONLINE_SENSING_DEFAULT_ENABLE_MQTT TINY_MEASUREMENT_ONLINE_SENSING_DEFAULT_ENABLE_MQTT
#define ONLINE_SENSING_DEFAULT_ENABLE_SERIAL TINY_MEASUREMENT_ONLINE_SENSING_DEFAULT_ENABLE_SERIAL
#ifdef TINY_MEASUREMENT_ENABLE_LCD
#define ONLINE_SENSING_DEFAULT_ENABLE_LCD TINY_MEASUREMENT_ONLINE_SENSING_DEFAULT_ENABLE_LCD
#else
#define ONLINE_SENSING_DEFAULT_ENABLE_LCD false
#endif
/* ============================================================================
* FUNCTION DECLARATIONS
* ============================================================================ */
/**
* @brief 初始化在线传感模块
* @param config 配置结构(NULL使用默认配置)
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_init(const online_sensing_config_t *config);
/**
* @brief 启动在线传感任务
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_start(void);
/**
* @brief 停止在线传感任务
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_stop(void);
/**
* @brief 反初始化在线传感模块
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_deinit(void);
/**
* @brief 运行时更新采样频率
* @param frequency_hz 新的采样频率(Hz)
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_set_frequency(float frequency_hz);
/**
* @brief 启用或禁用MQTT传输
* @param enable true启用,false禁用
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_set_mqtt_enable(bool enable);
/**
* @brief 启用或禁用串口输出
* @param enable true启用,false禁用
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_set_serial_enable(bool enable);
#ifdef TINY_MEASUREMENT_ENABLE_LCD
/**
* @brief 启用或禁用LCD显示
* @param enable true启用,false禁用
* @return 成功返回ESP_OK,失败返回错误码
* @note 仅在定义TINY_MEASUREMENT_ENABLE_LCD时可用
*/
esp_err_t online_sensing_set_lcd_enable(bool enable);
#endif
/**
* @brief 获取当前配置
* @param config 输出参数,当前配置
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_get_config(online_sensing_config_t *config);
/**
* @brief 检查在线传感是否正在运行
* @param is_running 输出参数:true表示正在运行,false表示未运行
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_is_running(bool *is_running);
/**
* @brief 设置ADXL355传感器句柄(必须在启动前调用)
* @param handle ADXL355句柄指针
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_set_sensor_handle(adxl355_handle_t *handle);
#ifdef __cplusplus
}
#endif
online_sensing.c¶
/**
* @file online_sensing.c
* @author SHUAIWEN CUI (SHUAIWEN001@e.ntu.edu.sg)
* @brief 在线传感模块实现
* @version 1.0
* @date 2025-12-17
* @copyright Copyright (c) 2025
*
*/
#include "online_sensing.h"
#include "node_mqtt.h"
#ifdef TINY_MEASUREMENT_ENABLE_LCD
#include "node_lcd.h"
#endif
#include "esp_log.h"
#include "esp_timer.h"
#include <string.h>
#include <stdio.h>
/* ============================================================================
* PRIVATE DEFINITIONS
* ============================================================================ */
static const char *TAG = "OnlineSensing";
/* ============================================================================
* PRIVATE VARIABLES
* ============================================================================ */
static online_sensing_config_t s_config = {
.sampling_frequency_hz = ONLINE_SENSING_DEFAULT_FREQ_HZ,
.enable_mqtt = ONLINE_SENSING_DEFAULT_ENABLE_MQTT,
.enable_serial = ONLINE_SENSING_DEFAULT_ENABLE_SERIAL,
.mqtt_topic = NULL
};
static esp_timer_handle_t s_sampling_timer = NULL;
static bool s_is_running = false;
static adxl355_handle_t *s_adxl355_handle = NULL;
/* ============================================================================
* PRIVATE FUNCTION DECLARATIONS
* ============================================================================ */
static void sampling_timer_callback(void *arg);
/* ============================================================================
* PRIVATE FUNCTION IMPLEMENTATIONS
* ============================================================================ */
/**
* @brief 定时器回调函数,用于周期性采样
* @param arg 定时器参数(未使用)
*/
static void sampling_timer_callback(void *arg)
{
if (!s_is_running || s_adxl355_handle == NULL)
{
return;
}
adxl355_accelerations_t accel;
float temperature;
char mqtt_pub_buff[128];
// 读取传感器数据
esp_err_t accel_ret = adxl355_read_accelerations(s_adxl355_handle, &accel);
esp_err_t temp_ret = adxl355_read_temperature(s_adxl355_handle, &temperature);
if (accel_ret == ESP_OK && temp_ret == ESP_OK)
{
// MQTT传输 - 紧凑格式:"x,y,z,t",20位精度(6位小数)
// 注意:esp_mqtt_client_publish是非阻塞的,但如果缓冲区满可能会失败
if (s_config.enable_mqtt && s_is_mqtt_connected)
{
snprintf(mqtt_pub_buff, sizeof(mqtt_pub_buff), "%.6f,%.6f,%.6f,%.2f",
accel.x, accel.y, accel.z, temperature);
const char *topic = s_config.mqtt_topic ? s_config.mqtt_topic : MQTT_PUBLISH_TOPIC;
int mqtt_ret = esp_mqtt_client_publish(s_mqtt_client, topic,
mqtt_pub_buff, strlen(mqtt_pub_buff), 1, 0);
if (mqtt_ret < 0)
{
// MQTT发布失败(可能是缓冲区满),但不阻塞采样
ESP_LOGW(TAG, "MQTT发布失败(缓冲区可能已满): %d", mqtt_ret);
}
}
// 串口输出 - 最小格式,20位精度(6位小数)
// 注意:如果串口缓冲区满,printf可能会阻塞,但通常足够快
if (s_config.enable_serial)
{
printf("%.6f,%.6f,%.6f,%.2f\n", accel.x, accel.y, accel.z, temperature);
fflush(stdout); // 确保立即刷新输出
}
#ifdef TINY_MEASUREMENT_ENABLE_LCD
// LCD显示 - 每行一个轴,最后一行显示温度
// 使用6位小数以支持完整的20位精度
if (s_config.enable_lcd)
{
char lcd_str[48];
// 第1行:X轴
snprintf(lcd_str, sizeof(lcd_str), "X:%.6f", accel.x);
lcd_show_string(0, 0, lcd_self.width, 16, 16, lcd_str, BLACK);
// 第2行:Y轴
snprintf(lcd_str, sizeof(lcd_str), "Y:%.6f", accel.y);
lcd_show_string(0, 16, lcd_self.width, 16, 16, lcd_str, BLACK);
// 第3行:Z轴
snprintf(lcd_str, sizeof(lcd_str), "Z:%.6f", accel.z);
lcd_show_string(0, 32, lcd_self.width, 16, 16, lcd_str, BLACK);
// 第4行:温度
snprintf(lcd_str, sizeof(lcd_str), "T:%.2fC", temperature);
lcd_show_string(0, 48, lcd_self.width, 16, 16, lcd_str, BLUE);
}
#endif
}
else
{
if (accel_ret != ESP_OK)
{
ESP_LOGE(TAG, "读取加速度失败: %s", esp_err_to_name(accel_ret));
}
if (temp_ret != ESP_OK)
{
ESP_LOGE(TAG, "读取温度失败: %s", esp_err_to_name(temp_ret));
}
}
}
/* ============================================================================
* PUBLIC FUNCTION IMPLEMENTATIONS
* ============================================================================ */
esp_err_t online_sensing_init(const online_sensing_config_t *config)
{
if (s_is_running)
{
ESP_LOGW(TAG, "在线传感已在运行,请先停止");
return ESP_ERR_INVALID_STATE;
}
// 应用配置
if (config != NULL)
{
memcpy(&s_config, config, sizeof(online_sensing_config_t));
}
else
{
// 使用默认配置
s_config.sampling_frequency_hz = ONLINE_SENSING_DEFAULT_FREQ_HZ;
s_config.enable_mqtt = ONLINE_SENSING_DEFAULT_ENABLE_MQTT;
s_config.enable_serial = ONLINE_SENSING_DEFAULT_ENABLE_SERIAL;
#ifdef TINY_MEASUREMENT_ENABLE_LCD
s_config.enable_lcd = ONLINE_SENSING_DEFAULT_ENABLE_LCD;
#endif
s_config.mqtt_topic = NULL;
}
// 验证配置
// 注意:代码接受高达300 Hz,但实际硬件稳定运行上限约为200 Hz
// 此限制主要是由于MQTT传输的约束 - 在更高频率下,MQTT发布无法跟上
// 数据生成速率,导致缓冲区溢出和数据丢失
if (s_config.sampling_frequency_hz <= 0.0f || s_config.sampling_frequency_hz > 300.0f)
{
ESP_LOGE(TAG, "无效的采样频率: %.2f Hz (有效范围: 0.1 - 300 Hz)",
s_config.sampling_frequency_hz);
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "在线传感已初始化:");
ESP_LOGI(TAG, " - 采样频率: %.2f Hz", s_config.sampling_frequency_hz);
ESP_LOGI(TAG, " - MQTT启用: %s", s_config.enable_mqtt ? "是" : "否");
ESP_LOGI(TAG, " - 串口输出启用: %s", s_config.enable_serial ? "是" : "否");
#ifdef TINY_MEASUREMENT_ENABLE_LCD
ESP_LOGI(TAG, " - LCD显示启用: %s", s_config.enable_lcd ? "是" : "否");
#endif
if (s_config.mqtt_topic)
{
ESP_LOGI(TAG, " - MQTT主题: %s", s_config.mqtt_topic);
}
return ESP_OK;
}
esp_err_t online_sensing_start(void)
{
if (s_is_running)
{
ESP_LOGW(TAG, "在线传感已在运行");
return ESP_ERR_INVALID_STATE;
}
// 获取ADXL355句柄(假设在main中已初始化)
// 这应该在启动前由应用程序设置
if (s_adxl355_handle == NULL)
{
ESP_LOGE(TAG, "ADXL355句柄未设置。请先调用 online_sensing_set_sensor_handle()");
return ESP_ERR_INVALID_STATE;
}
// 计算定时器周期(微秒)
// 注意:实际稳定运行上限约为200 Hz(5000微秒周期)
// 此限制主要是由于MQTT传输的约束 - 在更高频率下,MQTT发布无法跟上数据生成速率
uint64_t period_us = (uint64_t)(1000000.0f / s_config.sampling_frequency_hz);
// 验证周期(代码层面检查:最小100微秒 = 最大300 Hz)
// 但实际硬件稳定运行上限约为200 Hz,主要受MQTT传输带宽限制
if (period_us < 100)
{
ESP_LOGE(TAG, "采样频率过高: %.2f Hz (最大: 300 Hz)", s_config.sampling_frequency_hz);
return ESP_ERR_INVALID_ARG;
}
// 创建定时器
esp_timer_create_args_t timer_args = {
.callback = sampling_timer_callback,
.arg = NULL,
.name = "online_sampling_timer"
};
esp_err_t ret = esp_timer_create(&timer_args, &s_sampling_timer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "创建定时器失败: %s", esp_err_to_name(ret));
return ret;
}
// 在启动定时器前设置运行标志(以便回调可以执行)
s_is_running = true;
#ifdef TINY_MEASUREMENT_ENABLE_LCD
// 在启动在线传感前清除LCD屏幕
if (s_config.enable_lcd)
{
lcd_clear(WHITE);
}
#endif
// 在启动周期定时器前立即执行第一次采样(t=0)
sampling_timer_callback(NULL);
// 启动周期定时器(第一次周期采样将在t=period_us时执行)
ret = esp_timer_start_periodic(s_sampling_timer, period_us);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "启动定时器失败: %s", esp_err_to_name(ret));
esp_timer_delete(s_sampling_timer);
s_sampling_timer = NULL;
s_is_running = false;
return ret;
}
ESP_LOGI(TAG, "在线传感已启动 (频率: %.2f Hz, 周期: %llu us)",
s_config.sampling_frequency_hz, period_us);
return ESP_OK;
}
esp_err_t online_sensing_stop(void)
{
if (!s_is_running)
{
ESP_LOGW(TAG, "在线传感未运行");
return ESP_ERR_INVALID_STATE;
}
s_is_running = false;
// 停止并删除定时器
if (s_sampling_timer != NULL)
{
esp_err_t ret = esp_timer_stop(s_sampling_timer);
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE)
{
ESP_LOGE(TAG, "停止定时器失败: %s", esp_err_to_name(ret));
}
ret = esp_timer_delete(s_sampling_timer);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "删除定时器失败: %s", esp_err_to_name(ret));
}
s_sampling_timer = NULL;
}
ESP_LOGI(TAG, "在线传感已停止");
return ESP_OK;
}
esp_err_t online_sensing_deinit(void)
{
if (s_is_running)
{
online_sensing_stop();
}
memset(&s_config, 0, sizeof(online_sensing_config_t));
s_adxl355_handle = NULL;
ESP_LOGI(TAG, "在线传感已反初始化");
return ESP_OK;
}
esp_err_t online_sensing_set_frequency(float frequency_hz)
{
if (frequency_hz <= 0.0f || frequency_hz > 300.0f)
{
ESP_LOGE(TAG, "无效的采样频率: %.2f Hz (有效范围: 0.1 - 300 Hz)", frequency_hz);
return ESP_ERR_INVALID_ARG;
}
// 如果定时器正在运行,需要用新频率重启它
bool was_running = s_is_running;
if (was_running)
{
online_sensing_stop();
}
s_config.sampling_frequency_hz = frequency_hz;
ESP_LOGI(TAG, "采样频率已更新为 %.2f Hz", frequency_hz);
// 如果之前正在运行,则重启
if (was_running)
{
return online_sensing_start();
}
return ESP_OK;
}
esp_err_t online_sensing_set_mqtt_enable(bool enable)
{
s_config.enable_mqtt = enable;
ESP_LOGI(TAG, "MQTT传输 %s", enable ? "已启用" : "已禁用");
return ESP_OK;
}
esp_err_t online_sensing_set_serial_enable(bool enable)
{
s_config.enable_serial = enable;
ESP_LOGI(TAG, "串口输出 %s", enable ? "已启用" : "已禁用");
return ESP_OK;
}
#ifdef TINY_MEASUREMENT_ENABLE_LCD
esp_err_t online_sensing_set_lcd_enable(bool enable)
{
s_config.enable_lcd = enable;
ESP_LOGI(TAG, "LCD显示 %s", enable ? "已启用" : "已禁用");
return ESP_OK;
}
#endif
esp_err_t online_sensing_get_config(online_sensing_config_t *config)
{
if (config == NULL)
{
return ESP_ERR_INVALID_ARG;
}
memcpy(config, &s_config, sizeof(online_sensing_config_t));
return ESP_OK;
}
esp_err_t online_sensing_is_running(bool *is_running)
{
if (is_running == NULL)
{
return ESP_ERR_INVALID_ARG;
}
*is_running = s_is_running;
return ESP_OK;
}
/* ============================================================================
* ADDITIONAL HELPER FUNCTION
* ============================================================================ */
/**
* @brief 设置ADXL355传感器句柄(必须在启动前调用)
* @param handle ADXL355句柄指针
* @return 成功返回ESP_OK,失败返回错误码
*/
esp_err_t online_sensing_set_sensor_handle(adxl355_handle_t *handle)
{
if (handle == NULL)
{
return ESP_ERR_INVALID_ARG;
}
s_adxl355_handle = handle;
ESP_LOGI(TAG, "ADXL355传感器句柄已设置");
return ESP_OK;
}
关键实现¶
定时器回调¶
在线传感的核心是定时器回调函数 sampling_timer_callback(),它以固定间隔执行:
- 传感器读取:通过SPI读取加速度和温度数据(非阻塞)
- 数据格式化:使用6位小数格式化数据,支持完整的20位精度
- 输出通道:根据配置发布到MQTT、串口和可选的LCD
- 错误处理:记录错误但继续采样以维持时序
定时器管理¶
模块使用ESP-IDF的高精度定时器(esp_timer)进行周期性采样:
- 定时器创建:创建周期定时器,周期 =
1,000,000 / 采样频率_hz微秒 - 立即首次采样:在启动周期定时器前立即执行回调(t=0)
- 状态管理:使用
s_is_running标志防止停止后回调执行
配置验证¶
- 频率范围:验证 0.1 - 300 Hz(代码层面限制)
- 实测限制:稳定运行约200 Hz(受MQTT传输带宽限制)
- 默认值:当config为NULL时使用
tiny_measurement_config.h中的宏
运行时更新¶
- 频率更新:如果正在运行,停止并重启定时器以使用新频率
- 通道控制:可以在运行时启用/禁用MQTT、串口和LCD输出
- 非阻塞:MQTT发布是非阻塞的,但如果缓冲区满可能会失败
使用示例¶
#include "tiny_measurement.h"
#include "node_acc_adxl355.h"
// 初始化传感器
adxl355_handle_t adxl355_handle;
adxl355_init(&adxl355_handle, ADXL355_RANGE_2G, ADXL355_ODR_1000);
// 设置传感器句柄
online_sensing_set_sensor_handle(&adxl355_handle);
// 使用默认配置初始化
online_sensing_init(NULL);
// 启动在线传感
online_sensing_start();
// 运行时配置更新
online_sensing_set_frequency(50.0f); // 改为50 Hz
online_sensing_set_mqtt_enable(false); // 禁用MQTT
online_sensing_set_serial_enable(false); // 禁用串口输出
// 完成后停止
online_sensing_stop();
// 清理
online_sensing_deinit();