--- disqus: ahb0222 GA : G-VF9ZT413CG --- > [color=#40f1ef][name=LHB阿好伯, 2025/09/18][:earth_africa:](https://www.facebook.com/LHB0222/) [TOC] 晚上在等影像辨識模型訓練過程 來做些有趣的小專案 剛好宿舍瓦斯桶已經用了兩三個月都還沒用完 我就很好奇大概剩多少 雖然從小用到大隨手一抬也能知道還有剩多或剩少但要是能有知道準確數值那就更好了 由此尋找到一款體重計模組 # HX711 接線圖 ![image](https://hackmd.io/_uploads/S1pho9Kseg.png) 為了裝置省電我查看了HX711的說明可以看到耗電其實很低 工作時也只有<1.5mA ESP32 c3的GPIO完全是可以負擔的 ![image](https://hackmd.io/_uploads/HJq-nqtoge.png) 後續我就改成使用GPIO進行HX711電源開關 # HX711測試 :::spoiler 測試程式碼 ```cpp= /* * Seeed Studio XIAO ESP32C3 與 HX711 的測試代碼 * - 功能:同時輸出原始 ADC 讀數和校準後的重量 * * 接線: * HX711 VCC -> XIAO D8 (或 3V3,建議使用 3V3 以獲得更佳穩定性) * HX711 GND -> XIAO GND * HX711 DT -> XIAO D10 * HX711 SCK -> XIAO D9 */ #include "HX711.h" // 定義 HX711 的資料和時脈腳位 const int LOADCELL_DOUT_PIN = 10; // DT const int LOADCELL_SCK_PIN = 9; // SCK // 定義用來供電的 GPIO 腳位 (若使用 3V3 接 VCC,此行可忽略) const int POWER_PIN = 8; // D8 // 建立 HX711 物件 HX711 scale; // 校準係數 (Calibration Factor) // 這是一個初始的佔位數值。我們將透過下面的步驟計算出您自己的精確值。 float calibration_factor = 220.5; // <<<<<<<<<<< 等一下我們會來計算並替換這個值 void setup() { Serial.begin(115200); Serial.println("HX711 校準程式"); // 若使用 D8 供電,請保留以下兩行 pinMode(POWER_PIN, OUTPUT); digitalWrite(POWER_PIN, HIGH); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); Serial.println("正在穩定感測器..."); Serial.println("準備歸零,請確保秤上沒有任何東西..."); scale.set_scale(); scale.tare(); // 歸零 Serial.println("歸零完成。"); Serial.println("========================================"); } void loop() { if (scale.is_ready()) { // 1. 讀取原始的 ADC 讀數 (取 10 次平均值以增加穩定性) long raw_value = scale.read_average(10); Serial.print("原始讀數 (Raw Value): "); Serial.print(raw_value); // 2. 應用校準係數,獲取重量 (單位:公克) scale.set_scale(calibration_factor); float weight_grams = scale.get_units(10); Serial.print(" | 校準後重量: "); Serial.print(weight_grams, 2); Serial.print(" g"); // 將公克轉換為公斤 float weight_kg = weight_grams / 1000.0; Serial.print(" ("); Serial.print(weight_kg, 3); Serial.println(" kg)"); } else { Serial.println("HX711 未就緒!"); } delay(1000); } ``` ::: :::spoiler 休眠一小時程式碼 ```cpp= #include <WiFi.h> #include <PubSubClient.h> #include "HX711.h" #include "esp_sleep.h" #include "esp_task_wdt.h" // ================== 1. 請在此修改您的設定 ================== // -- Wi-Fi 設定 (主要和備用) -- const char* ssid1 = "xxxxx"; // 主要 Wi-Fi 名稱 const char* password1 = "xxxxx"; // 主要 Wi-Fi 密碼 const char* ssid2 = "xxxxx"; // 備用 Wi-Fi 名稱 const char* password2 = "xxxxx"; // 備用 Wi-Fi 密碼 // -- MQTT Broker 設定 -- const char* mqtt_server = "xxxxx"; // MQTT Broker 地址 const int mqtt_port = 1883; // MQTT Port (通常是 1883) const char* mqtt_topic = "xxxxx"; // 您想發送數據到的主題 // -- HX711 設定 -- const int LOADCELL_DOUT_PIN = 10; const int LOADCELL_SCK_PIN = 9; const float calibration_factor = -54117.79; // << 使用線性回歸計算的精確值 const long zero_offset = 356630; // << 空載時的原始讀數作為零點偏移 // << 新增 >> 定義用來供電的 GPIO 腳位 const int POWER_PIN = 8; // D8 // -- 深度休眠設定 -- #define uS_TO_S_FACTOR 1000000ULL // 微秒到秒的轉換因子 #define TIME_TO_SLEEP 3600 // 休眠時間(秒)- 1小時 = 3600秒 // -- WDT 設定 -- #define WDT_TIMEOUT 30 // 看門狗超時時間(秒) // ========================================================== // -- 全域變數 -- WiFiClient espClient; PubSubClient client(espClient); HX711 scale; RTC_DATA_ATTR int bootCount = 0; // 儲存在 RTC 記憶體中,重啟時保持 // 函式原型 bool setup_wifi(); bool reconnect_mqtt(); void send_weight_data(); void enter_deep_sleep(); float read_cpu_temperature(); void setup() { Serial.begin(115200); delay(2000); // 增加延遲確保串口穩定 Serial.println("========== ESP32 電子秤啟動 =========="); // 增加開機次數 ++bootCount; Serial.println("開機次數: " + String(bootCount)); // 檢查喚醒原因 esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); switch(wakeup_reason) { case ESP_SLEEP_WAKEUP_TIMER: Serial.println("由定時器喚醒"); break; default: Serial.printf("非深度休眠喚醒或首次啟動: %d\n", wakeup_reason); break; } // << 新增 >> 啟動 D8 引腳作為電源輸出 pinMode(POWER_PIN, OUTPUT); digitalWrite(POWER_PIN, HIGH); Serial.println("已將 D8 設置為 HIGH,為 HX711 供電。"); delay(1000); // 等待 HX711 穩定 // 初始化 HX711 Serial.println("初始化 HX711..."); scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); // 檢查 HX711 是否就緒 int retry_count = 0; while (!scale.is_ready() && retry_count < 10) { Serial.println("等待 HX711 就緒..."); delay(500); retry_count++; } if (scale.is_ready()) { scale.set_scale(calibration_factor); scale.set_offset(zero_offset); // 設定零點偏移 Serial.println("電子秤初始化完成(使用精確校準係數和零點偏移)。"); Serial.println("校準係數: " + String(calibration_factor, 2)); Serial.println("零點偏移: " + String(zero_offset)); } else { Serial.println("HX711 初始化失敗!"); } // 初始化 Wi-Fi 和 MQTT if (setup_wifi()) { Serial.println("Wi-Fi 連接成功"); client.setServer(mqtt_server, mqtt_port); if (reconnect_mqtt()) { Serial.println("MQTT 連接成功"); send_weight_data(); } else { Serial.println("MQTT 連接失敗"); } } else { Serial.println("Wi-Fi 連接失敗"); } // 進入深度休眠 enter_deep_sleep(); } void loop() { // 在深度休眠模式下,loop() 不會執行 // 所有邏輯都在 setup() 中完成 } // -- 連接 Wi-Fi 的函式(支援備用網路)-- bool setup_wifi() { WiFi.mode(WIFI_STA); delay(10); Serial.println(); Serial.print("嘗試連接主要 Wi-Fi: "); Serial.println(ssid1); WiFi.begin(ssid1, password1); // 嘗試連接主要 Wi-Fi,最多等待 15 秒 int attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(500); Serial.print("."); attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println(""); Serial.println("主要 Wi-Fi 連接成功!"); Serial.print("IP 地址: "); Serial.println(WiFi.localIP()); return true; } // 主要 Wi-Fi 失敗,嘗試備用 Wi-Fi Serial.println(""); Serial.println("主要 Wi-Fi 連接失敗,嘗試備用 Wi-Fi..."); Serial.print("嘗試連接備用 Wi-Fi: "); Serial.println(ssid2); WiFi.begin(ssid2, password2); attempts = 0; while (WiFi.status() != WL_CONNECTED && attempts < 30) { delay(500); Serial.print("."); attempts++; } if (WiFi.status() == WL_CONNECTED) { Serial.println(""); Serial.println("備用 Wi-Fi 連接成功!"); Serial.print("IP 地址: "); Serial.println(WiFi.localIP()); return true; } Serial.println(""); Serial.println("所有 Wi-Fi 連接均失敗!"); return false; } // -- 連接 MQTT Broker 的函式 -- bool reconnect_mqtt() { int attempts = 0; while (!client.connected() && attempts < 5) { Serial.print("正在嘗試連接 MQTT Broker..."); // 創建一個唯一的客戶端 ID String clientId = "XIAO-ESP32C3-Scale-"; clientId += String(random(0xffff), HEX); if (client.connect(clientId.c_str())) { Serial.println("連接成功!"); return true; } else { Serial.print("失敗, rc="); Serial.print(client.state()); Serial.println(" 3秒後重試..."); attempts++; delay(3000); } } return false; } // -- 發送重量數據的函式 -- void send_weight_data() { if (scale.is_ready()) { // 讀取原始數值(未經校準) long raw_value = scale.read_average(10); // 讀取 10 次取平均 // 使用校準係數手動計算重量(更精確) float weight_kg = (float)(raw_value - zero_offset) / calibration_factor; // 也讀取 HX711 庫的內建計算結果作為對比 float lib_weight_kg = scale.get_units(10) / 1000.0; // 庫函式計算結果(轉換為公斤) float cpu_temp = read_cpu_temperature(); // 讀取 CPU 溫度 Serial.print("HX711 原始數值: "); Serial.println(raw_value); Serial.print("手動計算重量: "); Serial.print(weight_kg, 3); Serial.println(" kg"); Serial.print("庫函式計算重量: "); Serial.print(lib_weight_kg, 3); Serial.println(" kg"); Serial.print("CPU 溫度: "); if (cpu_temp > -900) { Serial.print(cpu_temp, 1); Serial.println(" °C"); } else { Serial.println("讀取失敗"); } // 準備要發送的 JSON 格式數據(使用手動計算的更精確值) char json_payload[350]; if (cpu_temp > -900) { snprintf(json_payload, sizeof(json_payload), "{\"weight_kg\":%.3f, \"lib_weight_kg\":%.3f, \"hx711_raw\":%ld, \"cpu_temp_c\":%.1f, \"timestamp\":%lu, \"boot_count\":%d}", weight_kg, lib_weight_kg, raw_value, cpu_temp, millis(), bootCount); } else { snprintf(json_payload, sizeof(json_payload), "{\"weight_kg\":%.3f, \"lib_weight_kg\":%.3f, \"hx711_raw\":%ld, \"timestamp\":%lu, \"boot_count\":%d}", weight_kg, lib_weight_kg, raw_value, millis(), bootCount); } // 發佈到 MQTT Topic if (client.publish(mqtt_topic, json_payload)) { Serial.print("已發送 MQTT 訊息: "); Serial.println(json_payload); } else { Serial.println("MQTT 訊息發送失敗!"); } // 等待訊息發送完成 delay(1000); client.loop(); } else { Serial.println("HX711 未就緒!"); } } // -- 讀取 CPU 溫度的函式(簡化版,避免崩潰)-- float read_cpu_temperature() { // 使用簡單的方法讀取溫度,如果失敗就返回錯誤值 // 這個函式現在只返回一個估計值,避免使用可能不穩定的 API // 可以根據晶片運行時間估算溫度(這是一個粗略的方法) unsigned long uptime_ms = millis(); float estimated_temp = 25.0 + (uptime_ms / 10000.0); // 粗略估算 // 限制溫度範圍在合理區間內 if (estimated_temp > 80.0) estimated_temp = 80.0; if (estimated_temp < 20.0) estimated_temp = 20.0; return estimated_temp; } // -- 進入深度休眠的函式 -- void enter_deep_sleep() { Serial.println("準備進入深度休眠..."); // 斷開 Wi-Fi 和 MQTT 連接 if (client.connected()) { client.disconnect(); } WiFi.disconnect(true); WiFi.mode(WIFI_OFF); // 關閉 HX711 供電 digitalWrite(POWER_PIN, LOW); Serial.println("已關閉 HX711 供電"); // 設定休眠時間 esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); Serial.println("設定 " + String(TIME_TO_SLEEP) + " 秒後喚醒(" + String(TIME_TO_SLEEP/60) + " 分鐘)"); Serial.println("進入深度休眠模式..."); Serial.flush(); // 確保所有串口輸出完成 delay(100); // 小延遲確保所有操作完成 // 進入深度休眠 esp_deep_sleep_start(); } ``` ::: ![image](https://hackmd.io/_uploads/H1RaZoYoel.png) 使用node-red串接 ![Snipaste_2025-09-18_22-12-23](https://hackmd.io/_uploads/Sy9KCcKsgg.jpg) ![image](https://hackmd.io/_uploads/B1X705Foxl.png) 可以輸入空桶重量 預設為20kg去進行剩餘百分比計算 ![Snipaste_2025-09-18_22-10-51](https://hackmd.io/_uploads/SkidCcFoll.jpg) 🌟 🌟全文可以至下方連結觀看或是補充 全文分享至 https://www.facebook.com/LHB0222/ https://www.instagram.com/ahb0222/ 有疑問想討論的都歡迎於下方留言 喜歡的幫我分享給所有的朋友 \o/ 有所錯誤歡迎指教 # [:page_with_curl: 全部文章列表](https://hackmd.io/@LHB-0222/AllWritings) ![](https://i.imgur.com/nHEcVmm.jpg)