From 0eb428aaa5bb99699d4750f09dae4f944e917034 Mon Sep 17 00:00:00 2001 From: Wang Beihong Date: Tue, 21 Apr 2026 22:46:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20HX711=20=E7=94=B5=E5=AD=90?= =?UTF-8?q?=E7=A7=A4=E6=94=AF=E6=8C=81=EF=BC=8C=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E9=A1=B9=E5=B9=B6=E9=9B=86=E6=88=90=E5=88=B0=E4=B8=BB?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dependencies.lock | 13 +++- main/CMakeLists.txt | 2 +- main/idf_component.yml | 1 + main/main.cpp | 144 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 155 insertions(+), 5 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index 4637342..50c2beb 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -80,10 +80,21 @@ dependencies: registry_url: https://components.espressif.com type: service version: 9.5.0 + supcik/hx711: + component_hash: 38de09d1a896b3eda3cbb8c259979deed77fa0c517516c2b344dc55ae04f4fb6 + dependencies: + - name: idf + require: private + version: '>=5.3' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.1.3 direct_dependencies: - espressif/aht30 - espressif/esp_lvgl_port - idf -manifest_hash: 15dd4fd3d660b11406a1dde8c0ec0b63f12e3cd4064ff9026e70648e3f7bc8a3 +- supcik/hx711 +manifest_hash: 09736a2502ce314a6733743671e919311d43e602eb74410732a63f10ed3aa60a target: esp32s3 version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ae791fd..27f7810 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 JW01 human_door fire_sensor) + 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 human_door fire_sensor hx711) diff --git a/main/idf_component.yml b/main/idf_component.yml index 694ab0f..09bb203 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -16,3 +16,4 @@ dependencies: # public: true espressif/esp_lvgl_port: ^2.7.2 espressif/aht30: ^1.0.0 + supcik/hx711: ^1.1.3 diff --git a/main/main.cpp b/main/main.cpp index 2641153..e9937e1 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "wifi-connect.h" #include "esp_lvgl_port.h" #include "lvgl_st7789_use.h" @@ -12,6 +13,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" +#include "esp_task_wdt.h" #include "sntp_time.h" #include "esp_mac.h" #include "esp_system.h" @@ -24,10 +26,26 @@ #include "JW01.h" #include "human_door.h" #include "fire_sensor.h" +#include "hx711.hpp" #define TAG "MAIN" #define CO2_SPOILAGE_THRESHOLD_PPM 1500.0f #define FIRE_DANGER_THRESHOLD_PERCENT 35.0f +static const gpio_num_t kClockPin = GPIO_NUM_13; +static const gpio_num_t kDataPin = GPIO_NUM_14; + +#define HX711_TARE_SAMPLES 24 +#define HX711_READ_TIMEOUT_MS 350 +#define HX711_REFRESH_MS 120 +#define HX711_COUNTS_PER_GRAM 430.0f +#define HX711_ZERO_DEADBAND_G 2.0f +#define HX711_FILTER_ALPHA 0.15f +#define HX711_STABLE_BAND_G 0.30f +#define HX711_UNLOCK_DELTA_G 2.00f +#define HX711_UPDATE_MIN_STEP_G 0.05f +#define HX711_STABLE_SAMPLES 15 +#define TWDT_NORMAL_TIMEOUT_MS 5000 +#define TWDT_UI_INIT_TIMEOUT_MS 120000 typedef struct { @@ -39,6 +57,7 @@ typedef struct float tvoc; float hcho; float co2; + float ice_weight; float fire_percent; bool fire_danger; bool human_present; @@ -49,6 +68,20 @@ static env_data_t s_env_data; static SemaphoreHandle_t s_env_data_lock = NULL; static volatile bool s_ui_ready = false; +static void reconfigure_twdt(uint32_t timeout_ms, uint32_t idle_core_mask) +{ + const esp_task_wdt_config_t twdt_cfg = { + .timeout_ms = timeout_ms, + .idle_core_mask = idle_core_mask, + .trigger_panic = true, + }; + + esp_err_t ret = esp_task_wdt_reconfigure(&twdt_cfg); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "TWDT reconfigure failed: %s", esp_err_to_name(ret)); + } +} + static bool wait_for_wifi_connected(TickType_t timeout_ticks) { const TickType_t start_ticks = xTaskGetTickCount(); @@ -99,11 +132,14 @@ static void ui_init_task(void *arg) { (void)arg; + ESP_LOGI(TAG, "UI init start"); lvgl_port_lock(100 / portTICK_PERIOD_MS); ui_init(); lvgl_port_unlock(); + ESP_LOGI(TAG, "UI init done"); s_ui_ready = true; + reconfigure_twdt(TWDT_NORMAL_TIMEOUT_MS, (1U << portNUM_PROCESSORS) - 1U); vTaskDelete(NULL); } @@ -144,6 +180,102 @@ static void sntp_task(void *arg) vTaskDelete(NULL); } +static void hx711_task(void *arg) +{ + (void)arg; + + HX711 hx711(kClockPin, kDataPin, HX711::Mode::kChannelA128); + int64_t tare_sum = 0; + int tare_ok_count = 0; + + // 上电空载自动去皮:当前重量作为 0g 基准 + for (int i = 0; i < HX711_TARE_SAMPLES; ++i) { + int32_t raw = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS)); + if (raw != HX711::kUndefined) { + tare_sum += raw; + tare_ok_count++; + } + vTaskDelay(pdMS_TO_TICKS(40)); + } + + int32_t tare_offset = 0; + if (tare_ok_count > 0) { + tare_offset = (int32_t)(tare_sum / tare_ok_count); + ESP_LOGI(TAG, "HX711 tare done: raw0=%ld, samples=%d", (long)tare_offset, tare_ok_count); + } else { + ESP_LOGW(TAG, "HX711 tare failed, use 0 as offset"); + } + + float filtered_weight_g = 0.0f; + float display_weight_g = 0.0f; + bool display_initialized = false; + bool display_locked = false; + uint32_t stable_count = 0; + uint32_t err_cnt = 0; + for (;;) { + int32_t value = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS)); + if (value != HX711::kUndefined) { + float weight_g = ((float)(value - tare_offset)) / HX711_COUNTS_PER_GRAM; + if (fabsf(weight_g) < HX711_ZERO_DEADBAND_G) { + weight_g = 0.0f; + } + if (weight_g < 0.0f) { + weight_g = 0.0f; + } + + // 一阶低通,减小抖动 + filtered_weight_g = filtered_weight_g * (1.0f - HX711_FILTER_ALPHA) + weight_g * HX711_FILTER_ALPHA; + float rounded_weight_g = roundf(filtered_weight_g * 100.0f) / 100.0f; + + if (!display_initialized) { + display_weight_g = rounded_weight_g; + display_initialized = true; + } + + float diff_from_display = fabsf(rounded_weight_g - display_weight_g); + + // 稳定后锁定显示,重量明显变化时再解锁并继续更新 + if (display_locked) { + if (diff_from_display >= HX711_UNLOCK_DELTA_G) { + display_locked = false; + stable_count = 0; + } + } + + if (!display_locked) { + if (diff_from_display <= HX711_STABLE_BAND_G) { + if (stable_count < HX711_STABLE_SAMPLES) { + stable_count++; + } + if (stable_count >= HX711_STABLE_SAMPLES) { + display_locked = true; + } + } else { + stable_count = 0; + } + + if (diff_from_display >= HX711_UPDATE_MIN_STEP_G) { + display_weight_g = rounded_weight_g; + } + } + + set_var_ice_weight(display_weight_g); + + if (s_env_data_lock) { + xSemaphoreTake(s_env_data_lock, portMAX_DELAY); + s_env_data.ice_weight = display_weight_g; + xSemaphoreGive(s_env_data_lock); + } + err_cnt = 0; + } else { + if ((++err_cnt % 20) == 0) { + ESP_LOGW(TAG, "HX711 read timeout, check DOUT/SCK wiring and power"); + } + } + vTaskDelay(pdMS_TO_TICKS(HX711_REFRESH_MS)); + } +} + extern "C" void app_main(void) { vTaskDelay(pdMS_TO_TICKS(100)); @@ -156,6 +288,9 @@ extern "C" void app_main(void) // 2. 初始化显示屏和 LVGL start_lvgl_demo(); + // UI 初始化期间,临时放宽 TWDT(仅监控 CPU0 空闲任务) + reconfigure_twdt(TWDT_UI_INIT_TIMEOUT_MS, 1U << 0); + // 3. 在 CPU1 上执行 UI 初始化,避免 app_main 长时间占用 CPU0 xTaskCreatePinnedToCore(ui_init_task, "ui_init_task", 8192, NULL, 8, NULL, 1); set_var_food_status("良好"); @@ -172,12 +307,15 @@ extern "C" void app_main(void) // 4. 独立 SNTP 对时任务 xTaskCreate(sntp_task, "sntp_task", 4096, NULL, 4, NULL); + // HX711 电子秤:GPIO13(SCK) / GPIO14(DOUT) + xTaskCreate(hx711_task, "hx711_task", 4096, NULL, 6, NULL); + // 初始化继电器 (独立配置每个通道) const relay_config_t relay_cfg[RELAY_CTRL_ID_MAX] = { - {.pin = GPIO_NUM_9, .active_high = false}, - {.pin = GPIO_NUM_10, .active_high = false}, - {.pin = GPIO_NUM_11, .active_high = true}, {.pin = GPIO_NUM_12, .active_high = true}, + {.pin = GPIO_NUM_11, .active_high = true}, + {.pin = GPIO_NUM_10, .active_high = true}, + {.pin = GPIO_NUM_9, .active_high = true}, }; ESP_ERROR_CHECK(relay_ctrl_init(relay_cfg));