From cdc35d323a048a8486f2e589375d27f6a22a3956 Mon Sep 17 00:00:00 2001 From: Wang Beihong Date: Tue, 21 Apr 2026 01:47:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20JW01=20=E4=BC=A0=E6=84=9F?= =?UTF-8?q?=E5=99=A8=E6=94=AF=E6=8C=81=EF=BC=8C=E5=8C=85=E5=90=AB=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=92=8C=E6=95=B0=E6=8D=AE=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=88=A4=E6=96=ADCO2=E5=80=BC?= =?UTF-8?q?=E6=9D=A5=E5=88=A4=E6=96=AD=E5=AE=9E=E7=89=A9=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E3=80=821000=E4=B8=BA=E5=88=9D=E5=A7=8B=E9=98=88=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/JW01/CMakeLists.txt | 3 + components/JW01/JW01.c | 184 +++++++++++++++++++++++++++++++++ components/JW01/include/JW01.h | 40 +++++++ main/CMakeLists.txt | 2 +- main/main.cpp | 53 ++++++++-- 5 files changed, 274 insertions(+), 8 deletions(-) create mode 100644 components/JW01/CMakeLists.txt create mode 100644 components/JW01/JW01.c create mode 100644 components/JW01/include/JW01.h diff --git a/components/JW01/CMakeLists.txt b/components/JW01/CMakeLists.txt new file mode 100644 index 0000000..bdde9f1 --- /dev/null +++ b/components/JW01/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "JW01.c" + INCLUDE_DIRS "include" + REQUIRES esp_driver_uart) diff --git a/components/JW01/JW01.c b/components/JW01/JW01.c new file mode 100644 index 0000000..7ae99cd --- /dev/null +++ b/components/JW01/JW01.c @@ -0,0 +1,184 @@ +#include "JW01.h" + +#include +#include +#include + +#include "driver/uart.h" +#include "esp_check.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +static const char *TAG = "JW01"; +static bool s_inited = false; +static uint32_t s_parse_fail_count = 0; + +static esp_err_t jw01_parse_binary_frames(const uint8_t *buf, int len, jw01_data_t *out) +{ + bool found = false; + float last_co2 = 0.0f; + + for (int i = 0; i + 5 < len; ++i) { + if (buf[i] != 0x2C) { + continue; + } + + uint8_t cs = (uint8_t)(buf[i] + buf[i + 1] + buf[i + 2] + buf[i + 3] + buf[i + 4]); + if (cs != buf[i + 5]) { + continue; + } + + uint16_t value = ((uint16_t)buf[i + 1] << 8) | buf[i + 2]; + last_co2 = (float)value; + found = true; + i += 5; + } + + if (!found) { + return ESP_ERR_INVALID_RESPONSE; + } + + out->co2 = last_co2; + out->co2_valid = true; + return ESP_OK; +} + +static void str_to_upper_inplace(char *s) +{ + while (*s != '\0') { + *s = (char)toupper((unsigned char)*s); + ++s; + } +} + +static bool extract_float_by_key(const char *line, const char *key, float *out) +{ + char up_line[160]; + char up_key[24]; + + strncpy(up_line, line, sizeof(up_line) - 1); + up_line[sizeof(up_line) - 1] = '\0'; + strncpy(up_key, key, sizeof(up_key) - 1); + up_key[sizeof(up_key) - 1] = '\0'; + + str_to_upper_inplace(up_line); + str_to_upper_inplace(up_key); + + char *pos = strstr(up_line, up_key); + if (pos == NULL) { + return false; + } + + while (*pos != '\0' && *pos != ':' && *pos != '=') { + ++pos; + } + if (*pos == '\0') { + return false; + } + ++pos; + + while (*pos != '\0' && (isspace((unsigned char)*pos) || *pos == ',')) { + ++pos; + } + + char *endptr = NULL; + float value = strtof(pos, &endptr); + if (endptr == pos) { + return false; + } + + *out = value; + return true; +} + +static esp_err_t jw01_parse_line(const char *line, jw01_data_t *out) +{ + jw01_data_t data = {0}; + + data.tvoc_valid = extract_float_by_key(line, "TVOC", &data.tvoc); + if (!data.tvoc_valid) { + data.tvoc_valid = extract_float_by_key(line, "VOC", &data.tvoc); + } + + data.hcho_valid = extract_float_by_key(line, "HCHO", &data.hcho); + if (!data.hcho_valid) { + data.hcho_valid = extract_float_by_key(line, "CH2O", &data.hcho); + } + + data.co2_valid = extract_float_by_key(line, "CO2", &data.co2); + + if (!data.tvoc_valid && !data.hcho_valid && !data.co2_valid) { + return ESP_ERR_INVALID_RESPONSE; + } + + *out = data; + return ESP_OK; +} + +esp_err_t jw01_init(void) +{ + if (s_inited) { + return ESP_OK; + } + + const uart_config_t uart_cfg = { + .baud_rate = JW01_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .source_clk = UART_SCLK_DEFAULT, + }; + + ESP_RETURN_ON_ERROR(uart_driver_install((uart_port_t)JW01_UART_PORT, 1024, 0, 0, NULL, 0), + TAG, "uart driver install failed"); + ESP_RETURN_ON_ERROR(uart_param_config((uart_port_t)JW01_UART_PORT, &uart_cfg), + TAG, "uart param config failed"); + ESP_RETURN_ON_ERROR(uart_set_pin((uart_port_t)JW01_UART_PORT, + JW01_UART_TX_GPIO, + JW01_UART_RX_GPIO, + UART_PIN_NO_CHANGE, + UART_PIN_NO_CHANGE), + TAG, "uart set pin failed"); + ESP_RETURN_ON_ERROR(uart_flush_input((uart_port_t)JW01_UART_PORT), TAG, "uart flush failed"); + + s_inited = true; + ESP_LOGI(TAG, "JW01 init done on UART%d TX=%d RX=%d baud=%d", + JW01_UART_PORT, JW01_UART_TX_GPIO, JW01_UART_RX_GPIO, JW01_UART_BAUDRATE); + return ESP_OK; +} + +esp_err_t jw01_read(jw01_data_t *out_data, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(out_data != NULL, ESP_ERR_INVALID_ARG, TAG, "out_data is null"); + ESP_RETURN_ON_FALSE(s_inited, ESP_ERR_INVALID_STATE, TAG, "jw01 not init"); + + char line[160] = {0}; + int n = uart_read_bytes((uart_port_t)JW01_UART_PORT, + (uint8_t *)line, + sizeof(line) - 1, + pdMS_TO_TICKS(timeout_ms)); + if (n <= 0) { + return ESP_ERR_TIMEOUT; + } + line[n] = '\0'; + jw01_data_t data = {0}; + esp_err_t ret = jw01_parse_binary_frames((const uint8_t *)line, n, &data); + if (ret != ESP_OK) { + ret = jw01_parse_line(line, &data); + } + + if (ret == ESP_OK) { + *out_data = data; + } + + if (ret != ESP_OK) { + s_parse_fail_count++; + if (s_parse_fail_count <= 5 || (s_parse_fail_count % 20) == 0) { + ESP_LOGW(TAG, "parse failed (%s), rx_len=%d", esp_err_to_name(ret), n); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, (const uint8_t *)line, n, ESP_LOG_WARN); + } + } + + return ret; +} diff --git a/components/JW01/include/JW01.h b/components/JW01/include/JW01.h new file mode 100644 index 0000000..d74a614 --- /dev/null +++ b/components/JW01/include/JW01.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define JW01_UART_PORT 0 +#define JW01_UART_TX_GPIO 43 +#define JW01_UART_RX_GPIO 44 +#define JW01_UART_BAUDRATE 9600 + +typedef struct { + float tvoc; + float hcho; + float co2; + bool tvoc_valid; + bool hcho_valid; + bool co2_valid; +} jw01_data_t; + +/** + * @brief 初始化 JW01 UART 传感器(UART0, TX=43, RX=44) + */ +esp_err_t jw01_init(void); + +/** + * @brief 读取一帧 JW01 数据 + * + * @param out_data 输出数据 + * @param timeout_ms 超时时间(ms) + */ +esp_err_t jw01_read(jw01_data_t *out_data, int timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 0ea6506..888bcff 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "main.cpp" INCLUDE_DIRS "." - REQUIRES nvs_flash esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2) + REQUIRES nvs_flash esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2 JW01) diff --git a/main/main.cpp b/main/main.cpp index 55fb85b..c3b60f6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -20,8 +20,10 @@ #include "bh1750_use.h" #include "aht30.h" #include "MQ-2.h" +#include "JW01.h" #define TAG "MAIN" +#define CO2_SPOILAGE_THRESHOLD_PPM 1000.0f typedef struct { @@ -30,6 +32,9 @@ typedef struct float temp; float humidity; float gas_percent; + float tvoc; + float hcho; + float co2; } env_data_t; static env_data_t s_env_data; @@ -92,6 +97,7 @@ extern "C" void app_main(void) lvgl_port_lock(100 / portTICK_PERIOD_MS); ui_init(); lvgl_port_unlock(); + set_var_food_status("良好"); // 7. 创建 UI 任务 xTaskCreate(ui_task, "ui_task", 8192, NULL, 5, NULL); @@ -114,32 +120,61 @@ extern "C" void app_main(void) // MQ-2 使用 ADC(GPIO8) ESP_ERROR_CHECK(mq2_init()); + // JW01 使用 UART0(GPIO43/44) + ESP_ERROR_CHECK(jw01_init()); + // 6. 创建传感器读取任务 xTaskCreate([](void *arg) { aht30_handle_t aht30 = (aht30_handle_t)arg; + uint32_t log_cnt = 0; for (;;) { float lux = 0.0f, temp = 0.0f, hum = 0.0f, gas_percent = 0.0f; + jw01_data_t jw01{}; + esp_err_t bh_ret = ESP_FAIL; + esp_err_t aht_ret = ESP_FAIL; + esp_err_t mq2_ret = ESP_FAIL; + esp_err_t jw_ret = ESP_FAIL; // 读取 BH1750 - if (bh1750_user_read(&lux) == ESP_OK) { + bh_ret = bh1750_user_read(&lux); + if (bh_ret == ESP_OK) { set_var_light_val(lux); } // 读取 AHT30 - if (aht30_get_temperature_humidity_value(aht30, &temp, &hum) == ESP_OK) { + aht_ret = aht30_get_temperature_humidity_value(aht30, &temp, &hum); + if (aht_ret == ESP_OK) { set_var_temp(temp); set_var_humity_val(hum); } // 读取 MQ-2,更新空气质量变量 - if (mq2_read_percent(&gas_percent) == ESP_OK) { + mq2_ret = mq2_read_percent(&gas_percent); + if (mq2_ret == ESP_OK) { set_var_air_quity(gas_percent); + } + + // 读取 JW01(TVOC/HCHO/CO2) + jw_ret = jw01_read(&jw01, 200); + if (jw_ret == ESP_OK) { + if (jw01.co2_valid) { + if (jw01.co2 >= CO2_SPOILAGE_THRESHOLD_PPM) { + set_var_food_status("变质"); + } else { + set_var_food_status("良好"); + } - if (s_env_data_lock) { - xSemaphoreTake(s_env_data_lock, portMAX_DELAY); - s_env_data.gas_percent = gas_percent; - xSemaphoreGive(s_env_data_lock); } } + + // 每 5 次打印一次综合状态,避免日志刷屏 + if ((log_cnt++ % 10) == 0) { + ESP_LOGI(TAG, + "SENS bh=%s lux=%.1f | aht=%s t=%.1f h=%.1f | mq2=%s gas=%.1f | jw01=%s co2_valid=%d co2=%.1f", + esp_err_to_name(bh_ret), lux, + esp_err_to_name(aht_ret), temp, hum, + esp_err_to_name(mq2_ret), gas_percent, + esp_err_to_name(jw_ret), jw01.co2_valid ? 1 : 0, jw01.co2); + } // 数据存入共享结构体 @@ -148,6 +183,10 @@ extern "C" void app_main(void) s_env_data.lux = lux; s_env_data.temp = temp; s_env_data.humidity = hum; + s_env_data.gas_percent = gas_percent; + if (jw01.tvoc_valid) s_env_data.tvoc = jw01.tvoc; + if (jw01.hcho_valid) s_env_data.hcho = jw01.hcho; + if (jw01.co2_valid) s_env_data.co2 = jw01.co2; xSemaphoreGive(s_env_data_lock); } vTaskDelay(pdMS_TO_TICKS(1000));