ESP32 怎麼讀 Modbus RTU?RS485 接線、寄存器位址、排錯與 TCP 閘道選擇指南

ESP32 Modbus RTU 實作指南,涵蓋 RS485 接線、寄存器位址、分層排錯與 TCP 閘道選擇

ESP32 怎麼讀 Modbus RTU?RS485 接線、寄存器位址、排錯與 TCP 閘道選擇指南

感測器明明有 A/B 端子,接上 ESP32 卻只看到逾時;交換線之後偶爾有回應,數字又像亂碼。問題通常不在「少裝一套函式庫」,而是電氣、串列參數、Modbus 請求、資料解析四層被混在一起。本文用一台可核對寄存器表的溫濕度變送器,帶你從安全接線、讀出 raw register、判讀錯誤碼,一路做到資料新鮮度與產線檢核。完成後,你會知道下一個該量哪裡、該改哪個參數,以及何時應從 RTU 升級為受管理的 TCP/閘道架構。

三分鐘快速判斷:先辨認手上的介面

你看到的標示或文件現在能確定什麼還缺什麼下一個動作
UART TX/RX微控制器邏輯層串列訊號不具備 RS-485 差動驅動能力核對 3.3V 邏輯,加入相容收發器
RS485 A/B有差動實體介面未必使用 Modbus找精確型號的功能碼、站號與寄存器表
Modbus RTU、9600 8N1協定跑在串列線上;參數可設定未知功能碼、位址基準、縮放把手冊欄位抄入測試紀錄表
文件寫 30001/40001常見的人類閱讀參考號碼程式參數通常不是直接填該數字確認資料區與 PDU offset(封包位址)
IP、TCP 502、Unit ID可能是 Modbus TCP server不代表可安全跨網際網路在隔離的區域網路核對模式與存取規則
只有 4–20mA/0–10V類比量測輸出沒有可讀的數位寄存器使用合適的類比輸入與訊號調理

最快的第一步,是先把設備名稱與通訊手冊對起來。只有「RS485」三個字,沒有功能碼和寄存器表,就沒有足夠資訊寫查詢程式。第一次採購也應買出完整測試閉環:ESP32 開發板、3.3V 邏輯相容的 RS-485 收發器、符合感測器規格的電源、A/B 雙絞線,以及可作交叉驗證的 USB–RS485 轉接器。先用單機短線建立基準,任何擴充都與這份基準逐項比較,故障範圍才不會隨系統規模一起膨脹。

把問題拆成四層,才不會同時亂改

第一層是 MCU 邏輯。 ESP32 的 UART TX/RX 是 3.3V 邏輯訊號,不可接到 RS-485 的 A/B。A/B 是差動匯流排,必須經過相容收發器。常見 5V MAX485 小板的接收輸出可能接近 5V,直接送入 ESP32 RX 有過壓風險。本篇因此排除 MAX485 直連;若另案採用,必須重新設計電平保護、方向控制、偏壓與終端。

第二層是 RS-485 與串列設定。 這裡要對上 A/B、鮑率、資料位元、同位元、停止位元、半雙工收發方向與線路拓樸。A、B、A+、B−、D+、D− 的命名可能因廠商視角不同,不能拿線色或字母當跨品牌通則。

第三層是 Modbus 請求。 ESP32 在本例是 client(舊文件常稱 master),感測器是 server(舊稱 slave)。Client 指定站號、功能碼、起始位址與數量;server 只在請求符合其設定時回覆。串列單播站號可用 1~247,0 是廣播且 server 不回覆,但這個位址空間不代表同一條實體線一定能掛 247 台。

第四層是資料表示。 通訊成功只表示收到了合法回覆,不保證單位正確。你仍要知道 16-bit word 是有號或無號、倍率與 offset、無效值、兩個 word 的順序,以及資料多久後算 stale(過期)。32-bit 整數或浮點數的 word order 沒有一個所有設備共用的答案。

ESP32 以 3.3V UART 連接 DFR0845 自動方向隔離式 RS485 模組,再由 A/B 幹線連到 Modbus RTU 設備,並分開標示邏輯與現場供電域

圖中的關鍵安全資訊也要用文字記住:設備電源不得接進 ESP32 GPIO;終端只設在幹線的兩個物理端點;跨電源域、長線或高雜訊現場應把隔離與保護納入一開始的設計。

先讀通一台:ESP32-S3、DFR0845 與 XY-MD04

主範例使用三個可精確辨識的項目:

  1. ESP32-S3-DevKitC-1 N8R2 執行 Arduino 程式並擔任 RTU client。GPIO16/17 是本文配置,不代表每塊 ESP32 板都採相同腳位。
  2. DFRobot Gravity DFR0845 主動式隔離 RS485 轉 UART 模組 負責 UART 與 A/B 的實體層轉換。其 UART 邏輯側支援 3.3~5V,收發方向由模組內部處理;它本身不解析 Modbus。
  3. XY-MD04 SHT40 RS485 Modbus RTU 溫濕度變送器 是本次 server,另以符合其 5~28V 規格的電源供電。

斷電接線順序

先拔除 USB 與感測器電源。ESP32-S3 的 GPIO17(TX)接 DFR0845 的 R;GPIO16(RX)接 DFR0845 的 T。換句話說,是 ESP32 TX → DFR R,ESP32 RX ← DFR T。VCC 與 GND 依模組 UART 側標示接妥。DFR0845 的 A/B 再接到感測器 A/B,感測器使用自己的合規 DC 電源。

這個模組沒有提供給主控操作的 DE/RE 腳位,因此程式只呼叫 mb.begin(&RS485)。若在這條接線上多寫一個 DE/RE GPIO,不是「多一層保險」,而是虛構硬體介面。

DFR0845 有終端開關,但只有當它位於匯流排實體端點,且線路設計需要時才啟用。第一次桌上測試只接一台、使用短線、採已知 9600 8N1。若沒有回應,記錄目前 A/B 對應後只交換一次,避免反覆亂試而失去判斷依據。

通電前先填一張測試紀錄

不要讓設定只存在某位工程師的記憶裡。每次測試建立一列,寫下設備完整型號、外殼或電路板批次、通訊文件版本、供電電壓與限流值、站號、鮑率、資料格式、A/B 對應、功能碼、起始位址、數量與解析規則。再加上測試日期、程式版本和接線照片編號。日後如果同名設備換批次後讀不到,這張表能快速指出差異發生在硬體、設定或寄存器表。

第一次通電可採三段驗證。第一段只開感測器電源,量測端子電壓與極性,確認沒有把 12V 或 24V 帶到 UART 側。第二段用 USB–RS485 工具發出已知 request,保存送出與回覆的十六進位 frame。第三段才換成 ESP32,使用相同站號、serial format、FC、位址與數量。若第二段成功、第三段失敗,範圍便縮到 ESP32 腳位、UART 設定、收發模組接法或程式;若第二段也失敗,就先處理設備、供電、A/B 與現場設定。

此處也要分清楚「隔離」的邊界。DFR0845 將 UART 邏輯側與 RS-485 現場側隔開,但感測器仍需自己的合規電源與正確接地設計。模組的隔離電源能力若要拿來供外部設備,必須核對設備啟動電流、峰值與降額,不能只比較平時功耗。量測前先確認哪一側是 USB/ESP32 地,哪一側是現場電源地,避免用示波器地夾意外跨接隔離。

本例唯一可套用的寄存器表

依這個精確商品隨附的通訊協議,本例用功能碼 FC04 讀 Input Register:

欄位本例設定
Server ID1(可能已被現場修改)
Serial format9600、8N1
功能碼FC04,讀取 Input Register
起始 PDU 位址0x0001
數量2 個連續 register
0x0001溫度,signed 16-bit,除以 10 得 °C
0x0002濕度,unsigned 16-bit,除以 10 得 %RH

站號 1 的請求可表示為 01 04 00 01 00 02 20 0B。這份 mapping 只適用本文所指的精確 SKU 與相符批次文件,不能套到所有外觀相似或同樣使用 SHT40 的 RS-485 感測器。本輪未連接實體硬體進行讀值測試;以下是依已核實協議製作並通過編譯的實作候選,部署前仍要以 USB–RS485 工具和實機做交叉驗證。

可編譯的非阻塞輪詢程式

使用 Arduino Library Manager 的 modbus-esp8266 4.1.0。名稱雖含 ESP8266,該函式庫也支援 ESP32 的 RTU/TCP、client/server 與非阻塞 callback。4.1.0 的設定檔會先建立預設 RTU timeout,因此本例先載入 ModbusSettings.h,再於 ModbusRTU.h 前重設毫秒與微秒兩個巨集,讓 800 ms 真正生效;mb.task() 則要在每次 loop() 持續執行。

此程式不把逾時轉成 0,也不會用失敗回合覆蓋最後成功時間。raw=[...] 保留原始 word,方便跟電腦工具比對;溫度先轉為 int16_t,因此負溫可正確解析。callback 只保存結果,主迴圈才印出與換算,能避免阻塞通訊狀態機。800ms 是本例明確 timeout,不是所有 Modbus 設備的通則。

上線前至少做四個測試:正常回覆;錯功能碼得到例外 01;錯位址得到例外 02;拔除 A 或 B 後在 800ms 附近得到 0xE4。接回線後,程式應無須重開機便恢復。最後將 raw、縮放值、設備顯示與 USB–RS485 工具結果放在同一張紀錄表比較。

地址與資料解析:先翻譯,再寫程式

Modbus 有 Coil、Discrete Input、Input Register、Holding Register 四種主要資料物件。困難往往不是背名稱,而是文件的「30001」或「40001」與封包地址不同。PDU 中的資料位址是 0~65535;不少手冊的參考號碼由 1 起算,函式庫 API 卻收 zero-based offset。常見換算是資料模型編號減一,但仍要以精確手冊的封包範例確認。

教學假資料示意:由 Holding 或 Input 資料區決定功能碼,再確認 40001 或 30001 的起算方式、PDU 位址、數量與資料解析

每新增一種設備,就填一張「地址翻譯單」:

  1. 資料位於哪一區,對應 FC01、02、03 或 04?
  2. 手冊列的是參考號碼、PDU offset,還是已完整寫出的 request frame?
  3. 起算方式是 0-based 還是 1-based?
  4. 一筆值占一個或兩個 16-bit word?
  5. 是 signed、unsigned、BCD 或 IEEE 754?倍率、offset 與單位是什麼?
  6. 兩個 word 的排列是高字在前或低字在前?byte swap 是否另有規定?
  7. 哪些值代表斷線、感測器故障或超量程?資料多久後失效?

以本例來說,答案不是把「30002」填進 API,而是依協議用 FC04 從 0x0001 一次讀兩個 word。第一個轉 int16_t 再除以 10,第二個維持無號再除以 10。換一個型號、韌體或批次,這些答案都可能改變。

排錯順序:每回合只改一件事

八階 Modbus 排錯流程:從供電與設備身分開始,每次只改 A/B、串列參數、站號、功能碼、位址或解析中的一個變因並保存證據
現象優先檢查一次只做的測試
完全逾時供電、A/B、站號、鮑率與同位元用同參數的 USB–RS485 工具讀同一台設備
每次 CRC/亂碼Serial format、雜訊、參考地、收發切換回到短線單機、停高雜訊負載,再觀察 UART
例外 01功能碼不受支援按手冊改為正確 FC,不掃描寫入功能
例外 02起始位址或數量錯誤對照封包範例,只讀一段已知合法範圍
回覆成功但數值離譜signed、倍率、word/byte order先印 raw,再逐步解析並與參考工具比較
單台正常,多台失敗重複站號、分支、終端、偏壓或輪詢過密回到一台,再逐台加入並記錄錯誤率
馬達啟動時才失敗EMI、共模、接地、供電壓降將故障時間與電源/波形/設備事件對齊

若完全逾時,先確認感測器端實際有合規電壓,不要先改 register。若收到例外碼,實體層多半已能往返,此時集中檢查功能碼與位址。若收到合法值但單位錯,保留線路與 baud,回到資料解析。這種分層法能把每次改動變成有證據的實驗。

善用觀測點,不用猜封包

序列監控只能看到應用層結果,必要時應增加觀測點。先在 ESP32 UART 側用邏輯分析儀確認 TX 確實送出,RX 是否收到資料,解碼設定也必須與 9600 8N1 一致。UART 有 request、A/B 卻沒有活動,優先檢查收發模組供電與接線;A/B 有回覆、UART RX 沒資料,則回頭看 DFR0845 的 T 線與邏輯側供電。量 RS-485 差動訊號要用合適的差動探棒或隔離量測方法,不能隨意把接地式示波器探棒跨到未知電位的現場線。

保存一組正常 frame,日後故障時逐欄比對站號、FC、起始位址、數量、CRC 與回覆長度。CRC 通常由成熟函式庫產生與驗證;應用程式的工作是保留錯誤碼與事件時間,而不是在已正確運作的函式庫外再包一套互相矛盾的 CRC。若錯誤只在特定負載啟動時出現,將 frame 錯誤時間與馬達、繼電器、變頻器及電源最低點對齊,往往比反覆換函式庫更快找到根因。

從偶爾成功到可長期運轉

一條半雙工 RTU bus 同時只能有一個請求。每台設備應保存 next_due、最後成功時間、連續失敗數與 round-trip time。輪詢週期先看設備的資料更新速度,再用現場實測留餘裕。發生 timeout 時可做一次短重試;連續失敗後採 1、2、5 秒等有上限的退避,避免故障設備塞滿整條 bus。

「有上次數值」和「現在有有效數值」是兩回事。程式中的 lastGoodAtSTALE_AFTER_MS 就是最小實作。監看畫面應分別顯示 valid、stale、offline;控制邏輯遇到 stale 應進入預先定義的 fail-safe,而不是沿用舊溫度,更不能把 timeout 當成 0°C 或 0%RH。

配線擴充時採 trunk/daisy-chain,支線保持短。終端只位於幹線兩個物理端點,阻值與是否啟用依線材阻抗、速率、收發器和設備文件決定;不是每台都打開 120Ω。偏壓/fail-safe 網路也由整條 bus 統一管理,先盤點模組是否內建,避免多組電阻並聯造成過重負載。

先定義可接受的服務品質

「穩定」要能量化。環境監測可以先定義每台設備的目標更新週期、允許逾時率、最大連續失敗次數與 freshness deadline。例如畫面每秒更新一次,並不代表每 10ms 輪詢一次;一秒一次 request 已足夠時,多出的流量只會壓縮其他站點的回應空間。若某設備本身每半秒才更新內部量測,更快讀取通常只會得到重複值。

多站排程不要用一串固定 delay()。為每台 server 保存下一次到期時間,讓快速控制資料與慢速環境資料分組。每回合只排入一筆 transaction,完成或逾時後才排下一筆。記錄每台的回應時間分布,而不只平均值;timeout 應高於正常高分位延遲並留合理餘裕,但也不能長到故障站長時間霸占輪詢表。

告警同樣要分層。單次 CRC 或 timeout 可記錄為通訊事件,不必立刻宣告設備故障。連續多次失敗後才轉為 offline,能降低瞬態雜訊造成的告警風暴。恢復時可要求連續兩次成功再回到 healthy,並保留故障開始、恢復、累積次數與當時設備事件。這些規則應可設定、有預設值,而且在畫面上看得見。

資料消費端還要認得品質旗標。送往 MQTT、資料庫或 SCADA 時,除了溫度和濕度,也帶上量測時間、接收時間、站號、map 版本與 quality。若值已 stale,保留最後數字可以協助診斷,但 quality 必須明確標示失效;自動控制只能取用符合新鮮度與範圍檢查的資料。如此一來,通訊中斷不會被誤解成製程真的變成零值。

隔離耐壓、TVS 與 ESD 數字只描述特定保護能力,不等於整機已通過 EMC、EFT、surge、溫度、IP 或安規認證。跨盤、戶外、不同接地系統與變頻器附近,需另做屏蔽、接地、浪湧、隔離電源與長時間壓力測試。開發板桌上讀得到,是功能證明;不是產線放行證明。

何時繼續 RTU,何時轉 TCP 或閘道器

同一機台或控制箱內只有幾台儀表,隔離 RS-485 加 RTU 通常容易維護。多個遠端盤體已有受管理 Ethernet 時,可讓每盤保留短 RTU bus,再以真正的協定閘道器轉成 Modbus TCP。閘道器必須處理 RTU frame 與 TCP 的 MBAP header、Unit Identifier 和連線管理,並提供清楚的映射文件。

透明串列隧道只是把收到的 bytes 搬進 TCP socket。它可能傳送 RTU-over-TCP,卻不會因此成為 Modbus TCP gateway。尤其常見的 Waveshare RS485 TO ETH 透明傳輸模組,官方明示不支援 Modbus TCP;不要因商品名稱或網路埠就把兩者畫上等號。

現場需求優先架構主要條件
單一控制箱內數台設備ESP32+隔離 RS-485+RTU管好站號、終端、電源與維護介面
多盤體且已有 OT Ethernet每盤 RTU+協定閘道器+TCP文件化 Unit ID、映射、連線容量與故障行為
Wi-Fi 只用來上傳監測值RTU client 與 HTTPS/MQTT 應用分層不必對外提供 Modbus TCP
SCADA 在受管 OT LAN 輪詢Modbus TCP server/gateway固定資產清冊、VLAN、ACL 與允許清單
跨網際網路維護VPN/受控跳板後再進 OT 區TCP 502 不得直接 port-forward 到公網

Modbus TCP 使用 MBAP header 加 PDU,標準連接埠為 502,不使用 RTU CRC,也沒有 A/B、baud 或串列終端。一般 Modbus TCP 不提供應用期待的身分驗證與加密。需要更完整的協定安全時,可評估以 TLS 和憑證為基礎、使用 802 埠的 Modbus Security;無論哪條路,都仍要有網路分區、allowlist firewall/ACL、日誌、更新與撤權流程。

選閘道器時至少問六件事:它是 protocol gateway 還是 transparent tunnel;可同時維持多少 TCP connection;多個 client 如何分享一條 RTU bus;Unit ID 如何映射到 serial server ID;逾時、例外與斷線如何回報;設定檔能否匯出、版本控管與快速還原。若 SCADA 同時快速輪詢,而閘道器又允許多個維護工具連線,排程不清楚就可能把原本穩定的 RTU 線塞滿。

切換架構前先畫資料所有權。現場閉迴路控制應能在上層網路中斷時維持安全狀態;監測與歷史資料可延遲補送;遠端寫入則要有更嚴格的角色、範圍與稽核。測試不只驗證「允許的人連得上」,也要從辦公網、訪客網及未列入清冊的主機確認連不上。遠端帳號撤權、憑證過期、閘道重開與時間不同步,也都應列入驗收。

進產線前的放行清單

  • [ ] BOM 寫到精確開發板/模組、收發器、隔離電源、保護元件、端子與替代料,不只寫「ESP32+485」。
  • [ ] 每個設備 SKU、批次與韌體都有版本化 register map;功能碼、PDU 位址、signed、倍率與 word order 可追溯。
  • [ ] 每台 server 站號唯一,baud、parity、stop bits 與設定方式已記錄;變更後是否要重新上電也完成批次驗證。
  • [ ] 做過斷線、A/B 反接、重複站號、錯 baud、例外碼、CRC 錯誤、設備斷電與 bus stuck 測試。
  • [ ] 定義 timeout、重試、退避、stale、offline、fail-safe 與通訊恢復行為;恢復時輸出不突跳。
  • [ ] 日誌至少包含時間、站號、FC、起始位址、數量、結果碼、往返時間、連續失敗與最後成功時間。
  • [ ] 寫入功能預設關閉;確有需求時才加入權限、範圍檢查、稽核與 read-back。
  • [ ] 完成供電擾動、溫度、EMI/ESD/EFT、線長、接地與長時間 soak 測試;保護元件規格不冒充整機認證。
  • [ ] TCP/Wi-Fi 僅開放給清冊中的 client;辦公網與訪客網的負測試必須連不上 502。
  • [ ] 沒有硬編碼密碼、私鑰或預設憑證;停用遠端帳號後重新連線必須失敗。
  • [ ] 維護介面能看見韌體、register map 版本、錯誤率、last-good 與 stale 狀態。
  • [ ] 批量採購前,用預計線材、電源、閘道與至少一個實際批次完成交叉驗證,並保留合格樣品。

第一台的成功標準不是「序列監控出現一個看似合理的數字」,而是能重現 request、核對 raw、說明每一步轉換,且斷線後進入可預測狀態。把測試紀錄、接線版本、程式版號與合格 raw frame 一起交接,下一位維護者才能分辨設定被改動、設備換批或線路劣化,而不必從交換 A/B 重新開始。先把這條閉環做紮實,再逐台擴充。當維護範圍跨盤、跨網段或進入 SCADA,才升級為有映射、有存取控制、有故障定義的閘道架構。這樣從開發板到產線,每一次升級都有清楚的技術理由,而不是靠換線與重開機碰運氣。

分享到社群

發佈留言