Files
xInfo/main/SensorDriver.cpp
T
2026-08-31 15:05:13 +08:00

153 lines
5.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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;
}