RS-485 與 Modbus 實戰:MD02 溫濕度感測器程式解說 (2/3)

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 通訊。

【插圖說明:Modbus 非同步狀態機運作流程】
狀態 0 (等待)
用 millis() 計時,時間到才進入下一步,不阻塞 CPU
狀態 1 (發送查詢)
打包指令 (站號、功能碼、暫存器位址) 並送出
狀態 2 (輪詢接收)
不斷執行 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 雲端平台!