153 lines
5.5 KiB
C++
153 lines
5.5 KiB
C++
/**
|
||
* @file SensorDriver.cpp
|
||
* @brief UART 空气质量传感器驱动实现。
|
||
*
|
||
* 使用 ESP-IDF UART 驱动接收传感器主动推送的 9 字节数据帧,
|
||
* 解析 TVOC / CH₂O / CO₂ 三项空气质量指标。
|
||
*/
|
||
#include "SensorDriver.h"
|
||
|
||
#include <cstring>
|
||
|
||
#include "driver/uart.h"
|
||
#include "esp_log.h"
|
||
|
||
static const char *TAG = "SensorDriver";
|
||
|
||
/// 使用 UART1(UART0 被 ESP32 系统日志占用)
|
||
static constexpr uart_port_t kUartPort = UART_NUM_1;
|
||
|
||
/// UART 接收缓冲区大小(字节),足以容纳多帧数据
|
||
static constexpr int kBufSize = 256;
|
||
|
||
/// 传感器数据引脚定义(与硬件接线对应)
|
||
static constexpr int kRxPin = 0; ///< GPIO0 接传感器 B(TXD)
|
||
static constexpr int kTxPin = 1; ///< GPIO1 接传感器 A(RXD)
|
||
|
||
SensorDriver::SensorDriver() {}
|
||
SensorDriver::~SensorDriver() {}
|
||
|
||
// --------------------------------------------------------------------------
|
||
// 初始化 UART1:9600bps, 8N1
|
||
// --------------------------------------------------------------------------
|
||
void SensorDriver::init() {
|
||
ESP_LOGI(TAG, "初始化 UART1 (RX=GPIO%d, TX=GPIO%d, 9600bps)", kRxPin, kTxPin);
|
||
|
||
uart_config_t uartConfig = {};
|
||
uartConfig.baud_rate = 9600;
|
||
uartConfig.data_bits = UART_DATA_8_BITS;
|
||
uartConfig.parity = UART_PARITY_DISABLE;
|
||
uartConfig.stop_bits = UART_STOP_BITS_1;
|
||
uartConfig.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||
uartConfig.source_clk = UART_SCLK_DEFAULT;
|
||
|
||
// 安装 UART 驱动并配置引脚
|
||
ESP_ERROR_CHECK(uart_driver_install(kUartPort, kBufSize * 2, 0, 0, nullptr, 0));
|
||
ESP_ERROR_CHECK(uart_param_config(kUartPort, &uartConfig));
|
||
ESP_ERROR_CHECK(uart_set_pin(kUartPort, kTxPin, kRxPin,
|
||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||
|
||
ESP_LOGI(TAG, "UART1 初始化完成");
|
||
}
|
||
|
||
// --------------------------------------------------------------------------
|
||
// 高低字节换算为浓度:(high * 256 + low) * 0.001 mg/m³
|
||
// --------------------------------------------------------------------------
|
||
float SensorDriver::toConcentration(uint8_t high, uint8_t low) const {
|
||
return static_cast<float>(high * 256 + low) * 0.001f;
|
||
}
|
||
|
||
// --------------------------------------------------------------------------
|
||
// 从 UART 缓冲区读取一帧数据并解析
|
||
//
|
||
// 读取策略:
|
||
// 1. 先清空 UART 接收缓冲区中的旧数据
|
||
// 2. 等待传感器推送新的一帧数据(短轮询,避免长时间阻塞)
|
||
// 3. 读取实际可用的字节数(而非请求固定大小导致阻塞)
|
||
// 4. 从后向前搜索最新的完整帧 → 校验和 → 解析浓度
|
||
// --------------------------------------------------------------------------
|
||
SensorReading SensorDriver::read(int timeoutMs) {
|
||
// 默认返回全 NAN(表示读取失败)
|
||
SensorReading result = { NAN, NAN, NAN };
|
||
|
||
// 清空 UART 缓冲区中的旧数据,确保后续读到的是最新帧
|
||
uart_flush_input(kUartPort);
|
||
|
||
// 等待传感器新数据到达(轮询方式,每 100ms 检查一次)
|
||
// 传感器通常每 1~2 秒推送一帧,所以最多等 timeoutMs 即可
|
||
int waitedMs = 0;
|
||
size_t available = 0;
|
||
while (waitedMs < timeoutMs) {
|
||
uart_get_buffered_data_len(kUartPort, &available);
|
||
if (available >= static_cast<size_t>(kFrameSize)) {
|
||
// 缓冲区中已有至少一帧数据,可以读取
|
||
break;
|
||
}
|
||
vTaskDelay(pdMS_TO_TICKS(100)); // 短暂休眠,释放 CPU
|
||
waitedMs += 100;
|
||
}
|
||
|
||
if (available < static_cast<size_t>(kFrameSize)) {
|
||
ESP_LOGW(TAG, "UART 等待 %dms 超时,未收到足够数据(仅 %d 字节)",
|
||
timeoutMs, static_cast<int>(available));
|
||
return result;
|
||
}
|
||
|
||
// 只读取实际可用的字节数(不超过缓冲区上限),避免阻塞
|
||
int toRead = (available > static_cast<size_t>(kBufSize))
|
||
? kBufSize
|
||
: static_cast<int>(available);
|
||
|
||
uint8_t buf[kBufSize];
|
||
int len = uart_read_bytes(kUartPort, buf, toRead, pdMS_TO_TICKS(100));
|
||
|
||
if (len <= 0) {
|
||
ESP_LOGW(TAG, "UART 读取返回 %d,无有效数据", len);
|
||
return result;
|
||
}
|
||
|
||
ESP_LOGI(TAG, "UART 收到 %d 字节", len);
|
||
|
||
// 在缓冲区中从后向前搜索最后一个完整帧(取最新数据)
|
||
int frameStart = -1;
|
||
for (int i = len - kFrameSize; i >= 0; i--) {
|
||
if (buf[i] == kHeaderB1 && buf[i + 1] == kHeaderB2) {
|
||
frameStart = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (frameStart < 0) {
|
||
ESP_LOGW(TAG, "未找到有效帧头 (0x2C 0xE4)");
|
||
return result;
|
||
}
|
||
|
||
// 提取 9 字节帧
|
||
uint8_t *frame = &buf[frameStart];
|
||
|
||
// 校验和验证:uint8(B1 + B2 + ... + B8) == B9
|
||
uint8_t checksum = 0;
|
||
for (int i = 0; i < kFrameSize - 1; i++) {
|
||
checksum += frame[i];
|
||
}
|
||
|
||
if (checksum != frame[kFrameSize - 1]) {
|
||
ESP_LOGW(TAG, "校验和失败: 计算值=0x%02X, 接收值=0x%02X",
|
||
checksum, frame[kFrameSize - 1]);
|
||
return result;
|
||
}
|
||
|
||
// 解析各项浓度值
|
||
// TVOC 和 CH₂O 用 ×0.001 换算为 mg/m³
|
||
result.tvoc = toConcentration(frame[2], frame[3]);
|
||
result.ch2o = toConcentration(frame[4], frame[5]);
|
||
// CO₂ 直接用原始值(高×256+低)作为 ppm,不需要 ×0.001
|
||
result.co2 = static_cast<float>(frame[6] * 256 + frame[7]);
|
||
|
||
ESP_LOGI(TAG, "解析成功: TVOC=%.3f mg/m³, CH2O=%.3f mg/m³, CO2=%.0f ppm",
|
||
result.tvoc, result.ch2o, result.co2);
|
||
|
||
return result;
|
||
}
|
||
|