在建置水井村智慧養殖系統時,我們選用 Atlas Scientific Aquaponics Kit V1.8,
搭配 Adafruit HUZZAH32 ESP32 Feather,希望讀取水溫、pH、溶氧與導電度,
並為後續上傳 Django REST API 做準備。原本以為只要修改 Wi-Fi 與 API 即可完成整合,
沒想到真正花最多時間的,是硬體腳位、Enable 控制與 I²C 位址的確認。
一、系統架構
Atlas Aquaponics Kit V1.8
│
├── RTD(水溫)
├── pH
├── Dissolved Oxygen(DO)
└── Conductivity(EC)
│
▼
Adafruit HUZZAH32 ESP32 Feather
│
Arduino IDE
│
Django REST API(下一階段)
二、第一個問題:全部都是 No Data
最初程式執行時,四個感測器全部無法取得有效資料:
RTD:無有效資料
pH:無有效資料
DO:無有效資料
EC:無有效資料
因此,除錯並沒有先從 API 開始,而是先確認 ESP32 是否正常、I²C 是否正確、 Atlas 模組是否有供電,以及每個感測器是否真的出現在匯流排上。
三、第二個問題:I²C 腳位誤判
一開始依照 PCB 絲印誤判為:
SDA = GPIO14
SCL = GPIO32
重新對照 Adafruit HUZZAH32 Feather 的實際腳位後,修正為:
SDA = GPIO23
SCL = GPIO22
修正之後,ESP32 才開始穩定掃描到 Atlas EZO 模組。
四、第三個問題:Enable 腳位
依照 Atlas Aquaponics V1.8 PCB 與實際測試,確認 Enable 腳位如下:
| 功能 | GPIO |
|---|---|
| pH EN | GPIO13 |
| DO EN | GPIO12 |
| EC EN | GPIO27 |
| RTD EN | GPIO33 |
| CO₂ EN | GPIO15 |
| Humidity EN | GPIO32 |
程式初始化如下:
digitalWrite(EN_PH, LOW);
digitalWrite(EN_DO, LOW);
digitalWrite(EN_EC, LOW);
digitalWrite(EN_RTD, HIGH);
digitalWrite(EN_CO2, HIGH);
digitalWrite(EN_HUM, HIGH);
五、第四個問題:只有 EC 一直 No Data
修正腳位與 Enable 後,RTD、pH 與 DO 已能讀取,但 EC 仍顯示:
EC: No Data
甚至在單獨測試 EC 時,出現:
Wire.endTransmission = 4
一開始看起來像是 EC 模組故障、插槽異常、Enable 設定錯誤, 甚至懷疑探棒、供電或主機板本身有問題。
六、第五個問題:EC LED 與插槽交換測試
觀察 EC 模組時,發現剛通電時藍燈約亮一秒,隨後熄滅。 我們也進行了模組與插槽交換測試,但現象一致,因此逐漸排除 Conductivity 插槽本身故障的可能。
七、最後找到真正原因:EC 位址被修改
I²C 掃描結果如下:
找到 0x61 ( 97) -> EZO DO
找到 0x63 ( 99) -> EZO pH
找到 0x66 (102) -> EZO RTD
找到 0x69 (105) -> 其他 I2C 裝置
原本一直把 0x69 當成未知裝置,
後來才發現它其實就是 EC 模組,只是 I²C 位址已被修改。
原先以為 EC 位址是:
constexpr uint8_t ADDR_EC = 100; // 0x64
實際上應改成:
constexpr uint8_t ADDR_EC = 105; // 0x69,本機 EC 已改址
掃描名稱也同步修正:
case 0x69:
return "EZO EC(已改址)";
八、成功讀取四種感測器
水溫 RTD:30.01 °C
pH:6.80
溶氧 DO:31.92 mg/L
導電度 EC:441 µS/cm
目前確認的 I²C 位址如下:
| 感測器 | 十進位位址 | 十六進位位址 |
|---|---|---|
| DO | 97 | 0x61 |
| pH | 99 | 0x63 |
| RTD | 102 | 0x66 |
| EC | 105 | 0x69 |
九、本次最大的收穫
設備在出廠、維修、校正或前一個專案中,都可能修改 I²C 位址、輸出格式或 LED 設定。 最可靠的方法是先掃描全部位址,再逐一確認每個裝置的實際身分。
這次除錯流程可以整理成:
- 確認開發板型號與 PSRAM 設定。
- 確認正確的 SDA、SCL 腳位。
- 確認各感測器 Enable 腳位與有效電位。
- 掃描完整 I²C 位址。
- 逐顆單獨讀取。
- 不要假設模組位址仍為原廠預設值。
十、下一步:上傳 Django REST API
完成硬體驗證後,下一階段將把四項水質資料上傳至水井 USR Django API。
{
"token": "abc123",
"farm_name": "湖虎戰隊",
"pond_code": "1",
"water_temperature": 30.01,
"salinity": 0.22,
"ph": 6.80,
"dissolved_oxygen": 31.92,
"water_source": "直流變頻水車運轉中",
"recorded_at": "2026-08-02T14:30:00+08:00"
}
後續可延伸:
- Django Dashboard 即時儀表板
- 歷史趨勢分析
- MQTT 與 LoRa
- 異常警示
- AI 水質判讀與養殖決策建議
結語
這次除錯的價值,不只是讓程式成功讀到資料,而是更深入理解 Atlas Scientific EZO 模組、 ESP32、Enable 控制與 I²C 通訊的實際運作。對智慧養殖系統而言, 穩定的硬體資料來源是所有雲端分析、AI 判讀與自動控制的基礎。
本文由作者主導內容規劃,並使用生成式 AI 協助文字整理、程式說明與版面設計;
所有內容均依實際硬體測試結果進行修訂與確認。
附件:程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | /* Atlas Scientific Aquaponics V1.8 修正版:硬體驗證版 實體板: - Adafruit HUZZAH32 ESP32 Feather - Atlas Aquaponics V1.8 依 PCB 絲印與 HUZZAH32 實體排針重新確認: pH EN -> GPIO13 DO EN -> GPIO12 EC EN -> GPIO27 RTD EN -> GPIO33 CO2 EN -> GPIO15 HUM EN -> GPIO32 I2C 使用 Feather 板上標示的 SDA / SCL 腳: SDA -> GPIO23 SCL -> GPIO22 Arduino IDE: Board: Adafruit ESP32 Feather PSRAM: Disabled Serial Monitor: 115200 */ #include <Wire.h> #include <Ezo_i2c.h> #include <Ezo_i2c_util.h> constexpr uint8_t I2C_SDA = 23; constexpr uint8_t I2C_SCL = 22; constexpr uint8_t EN_PH = 13; constexpr uint8_t EN_DO = 12; constexpr uint8_t EN_EC = 27; constexpr uint8_t EN_RTD = 33; constexpr uint8_t EN_CO2 = 15; constexpr uint8_t EN_HUM = 32; constexpr uint8_t ADDR_DO = 97; // 0x61 constexpr uint8_t ADDR_PH = 99; // 0x63 constexpr uint8_t ADDR_EC = 105; // 0x64 constexpr uint8_t ADDR_RTD = 102; // 0x66; Ezo_board PH = Ezo_board(ADDR_PH, "PH"); Ezo_board DO = Ezo_board(ADDR_DO, "DO"); Ezo_board EC = Ezo_board(ADDR_EC, "EC"); Ezo_board RTD = Ezo_board(ADDR_RTD, "RTD"); constexpr unsigned long READ_DELAY_MS = 1100; constexpr unsigned long LOOP_DELAY_MS = 5000; void enableCircuits() { pinMode(EN_PH, OUTPUT); pinMode(EN_DO, OUTPUT); pinMode(EN_EC, OUTPUT); pinMode(EN_RTD, OUTPUT); pinMode(EN_CO2, OUTPUT); pinMode(EN_HUM, OUTPUT); // 沿用 Atlas 官方範例的極性邏輯 digitalWrite(EN_PH, LOW); digitalWrite(EN_DO, LOW); digitalWrite(EN_EC, LOW); digitalWrite(EN_RTD, HIGH); digitalWrite(EN_CO2, HIGH); digitalWrite(EN_HUM, HIGH); delay(2000); } const char* deviceName(uint8_t address) { switch (address) { case 0x61: return "EZO DO"; case 0x63: return "EZO pH"; case 0x64: return "EZO EC"; case 0x66: return "EZO RTD"; case 0x67: return "EZO Pump"; case 0x6F: return "Humidity"; case 0x71: return "CO2"; default: return "其他 I2C 裝置"; } } uint8_t scanI2C() { uint8_t count = 0; Serial.println(); Serial.println("========== I2C 掃描 =========="); for (uint8_t address = 1; address < 127; address++) { Wire.beginTransmission(address); uint8_t error = Wire.endTransmission(); if (error == 0) { Serial.printf( "找到 0x%02X (%3u) -> %s\n", address, address, deviceName(address) ); count++; } } Serial.printf("共找到 %u 個 I2C 裝置\n", count); Serial.println("=============================="); return count; } bool receiveValue(Ezo_board& sensor, float& value) { receive_and_print_reading(sensor); if (sensor.get_error() != Ezo_board::SUCCESS) { return false; } value = sensor.get_last_received_reading(); return true; } void readSensors() { Serial.println(); Serial.println("========== 感測器讀值 =========="); RTD.send_read_cmd(); delay(READ_DELAY_MS); float temperature = NAN; bool rtdOK = receiveValue(RTD, temperature); if (rtdOK && temperature > -1000.0f) { Serial.printf("水溫 RTD:%.2f C\n", temperature); PH.send_cmd_with_num("T,", temperature); DO.send_cmd_with_num("T,", temperature); EC.send_cmd_with_num("T,", temperature); } else { Serial.println("水溫 RTD:無有效資料"); PH.send_cmd_with_num("T,", 25.0); DO.send_cmd_with_num("T,", 25.0); EC.send_cmd_with_num("T,", 25.0); } delay(350); PH.send_read_cmd(); DO.send_read_cmd(); EC.send_read_cmd(); delay(READ_DELAY_MS); float phValue = NAN; float doValue = NAN; float ecValue = NAN; bool phOK = receiveValue(PH, phValue); bool doOK = receiveValue(DO, doValue); bool ecOK = receiveValue(EC, ecValue); Serial.println(); Serial.println("---------- 結果 ----------"); if (phOK) { Serial.printf("pH:%.2f\n", phValue); } else { Serial.println("pH:無有效資料"); } if (doOK) { Serial.printf("溶氧 DO:%.2f mg/L\n", doValue); } else { Serial.println("溶氧 DO:無有效資料"); } if (ecOK) { Serial.printf("導電度 EC:%.0f uS/cm\n", ecValue); } else { Serial.println("導電度 EC:無有效資料"); } Serial.println("=========================="); } void setup() { Serial.begin(115200); delay(1500); Serial.println(); Serial.println("Atlas Aquaponics V1.8 修正版硬體驗證啟動"); Serial.printf("SDA=GPIO%u, SCL=GPIO%u\n", I2C_SDA, I2C_SCL); enableCircuits(); Wire.begin(I2C_SDA, I2C_SCL); Wire.setClock(100000); Wire.setTimeOut(1000); delay(500); scanI2C(); readSensors(); } void loop() { delay(LOOP_DELAY_MS); scanI2C(); readSensors(); } |

沒有留言:
張貼留言