RS-485 與 Modbus 實戰:MD02 溫濕度感測器程式解說 (2/3)
延續上一篇我們完成了程式庫引入與變數宣告,本篇我們將進入程式的運作核心:系統初始化設定 (setup) 與 主程式迴圈 (loop) 中的 Modbus 非同步狀態機邏輯。這部分是決定微控制器能否「不卡頓」地同時處理通訊與網路任務的關鍵。
一、 系統初始化:setup() 函式
在 setup() 階段,我們必須依序喚醒各個硬體介面。包含設定通訊速率(Baud Rate)、切換硬體線路至 RS-485,連上網路,並啟動 Modbus 主站服務。
void setup() {
Serial.begin(115200); // 初始化除錯用的硬體序列埠 (看 log 用)
mySerial.begin(9600); // 初始化與 Modbus 溝通的軟體序列埠 (9600 bps)
// 設定 UART 多工器選擇腳位為輸出模式
pinMode(S0, OUTPUT);
pinMode(S1, OUTPUT);
USE_RS485(); // 呼叫巨集,切換多工器至 RS-485 模式
reconnectWiFi(); // 執行 WiFi 連線 (自訂函式)
master.start(); // 啟動 Modbus 主站服務
master.setTimeOut(2000); // 設定 Modbus 通訊逾時時間為 2000 ms
u32wait = millis() + 1000; // 設定初始等待時間 (開機 1 秒後才開始發送查詢)
u8state = 0; // 初始化狀態機為 0 (等待狀態)
}
📝 重點解析: 我們將 mySerial.begin(9600) 設定為 9600,這是因為大多數工業級 RS-485 感測器(如 MD-02)出廠預設的通訊速率就是 9600 bps。開發板與感測器的頻率必須一致,才能順利「對話」。
二、 強健的物聯網基石:WiFi 斷線重連機制
工業與物聯網設備常常部署在無人看管的環境,網路不穩是常態。因此我們撰寫了一個 reconnectWiFi() 函式,它有一個非常重要的防呆機制:先主動斷線消除殘留的 Session,再重新連線。
void reconnectWiFi() {
ConnectTimeOut = millis(); // 記錄開始連線的時間點
while(WiFi.status() != WL_CONNECTED){
Serial.println();
Serial.print("Attempting to connect to SSID:");
Serial.println(ssid);
WiFi.disconnect(); // 關鍵!先主動斷線,消除殘留 session
// ...(後續執行重新連線與逾時跳出機制)
}
}
三、 核心靈魂:loop() 內的 Modbus 狀態機
在主程式 loop() 中,絕對不能使用 delay() 函數來做延遲等待。為了不讓等待數據的過程「卡死」整個微控制器,程式採用了狀態機(State Machine)的架構來非同步處理 Modbus 通訊。
用 millis() 計時,時間到才進入下一步,不阻塞 CPU
打包指令 (站號、功能碼、暫存器位址) 並送出
不斷執行 master.poll() 傾聽感測器回傳的數據
以下是這三個狀態對應的程式碼:
switch( u8state ) {
/* 狀態 0: 等待狀態 */
case 0:
if (millis() > u32wait) u8state++; // 等待時間到,狀態變為 1
break;
/* 狀態 1: 設定並發送 Modbus 查詢 */
case 1:
telegram[0].u8id = 8; // 目標從站位址為 0x08
telegram[0].u8fct = 4; // 功能碼 4 (0x04):讀取輸入暫存器
telegram[0].u16RegAdd = 1; // 起始暫存器位址:1 (讀取溫度)
telegram[0].u16CoilsNo = 1; // 讀取暫存器數量:1 個
telegram[0].au16reg = au16data; // 將收到的數據存入 au16data 陣列指標
.
.
.
// 送出後狀態會由底層自動推進至 2
break;
/* 狀態 2: 輪詢並處理回應 */
case 2:
master.poll(); // 不斷輪詢檢查序列埠是否有來自從站的回應訊息
// 收到完整回應或逾時後,u8state 會被程式庫改回 0
break;
}
📝 重點解析: 在 case 1 中,我們明確定義了 功能碼 4 以及 起始位址 1。這是依照 MD-02 規格書的定義,告訴感測器「請把溫度的數據交出來」。而 master.poll() 則是 Modbus 主站的傾聽機制,負責將線路上的 16 進位封包收集起來並進行 CRC 錯誤檢查。
四、 多工作業:不卡頓的計時器技巧
正因為我們沒有在 Modbus 狀態機中使用 delay(),我們的微控制器可以在同一時間執行其他任務。我們同樣使用 millis() 來建立一個「每 10 秒檢查一次」的網路防呆機制:
// 任務:每 10 秒檢查一次 WiFi 狀態
reTryConnTime = millis();
if(reTryConnTime - reTryConnPreTime > reTryConnIntervalSwitch){
reTryConnPreTime = reTryConnTime;
status = WiFi.status();
if(status != WL_CONNECTED) {
reconnectWiFi(); // WiFi 斷線,嘗試重新連接
}
}
第二段總結: 透過上述的設計,我們成功建立了一個能同時 穩定發送 RS-485 訊號、非同步接收 Modbus 數據、並具備網路自我監測修復能力 的主迴圈架構。下一段(3/3),我們將解說最後一個步驟:如何把 au16data 陣列中冷冰冰的 16 進位原始數據,換算成人類看得懂的攝氏溫度,並將其送上 ThingSpeak 雲端平台!