Modbus 是常用的工業總線協議,屬於 Application Layer,工業上常用 Modbus-RTU 搭配 RS-485 或者 Modbus-TCP 搭配 Ethernet 來傳輸,也可以使用 UART 或 RS-232 甚至 RF 等無線媒介傳遞 Modbus 協議,並沒有規定一定要搭配哪種實體層。本文著重介紹 Modbus-RTU 搭配 RS-485 的通訊架構。
Modbus 幾種類型與傳輸媒介搭配:
早期這個協議用比較多,但被傳輸更有效率的 Modbus-RTU 取代,請參考與 Modbus-RTU 之比較。
Modbus-RTU (Remote Terminal Unit) 的命名強調了它的應用場景是為遠程設備之間的高效通訊而設計。
Modbus-RTU 協議規定採用 Master / Slave 架構,一律由 Master 發起通訊,相應的 Slave 進行回應。 因為 Modbus 為 Application 協議,並無規範用什麼媒介傳遞
核心封包結構 Modbus-RTU 沒有差很多,只是是經由 Ethernet 媒介的網路架構,這使使得 Modbus-TCP 在許多方面都比 Modbus-RTU 來的強大,不論是速度、裝置數量限制,拓樸等。
Ethernet 通過交換機可形成 Star topology,得益於 Ethernet 架構,可以設計為 Ethernet client / server 形式,能有多個 clients 端,亦能同時對多個 Slaves 發起請求。
Modbus-RTU 模式
Modbus-TCP 模式
特性 | Modbus RTU | Modbus TCP |
---|---|---|
傳輸媒介 | RS-232 / RS-485 | Ethernet |
傳輸速率 | 低(受波特率限制,例如 9600bps、115200bps) | 高(受網絡速率限制,可達數百 Mbps) |
設備數量 | 受限(最多 32 個節點) | 基於 ip,幾乎無限 |
通信效率 | 單線串行通信,效率低 | 多連接並行通信,效率高 |
設備成本 | 低 | 較高 |
配置難度 | 簡單 | 複雜 |
適用場景 | 小規模、低成本工業通信 | 大規模、現代化網絡通信 |
架構 | 需要明確的主從關係(Master/Slave) | Client/Server 架構,可同時與多個設備通信 |
根據應用需求選擇:
優點:
這邊不會詳細列出 request 跟 response 格式,因為 Modbus 協議很簡單,Modbus Specification 也舉例的蠻清楚,只要找裝置實際操作,對照 spec 看基本很容易就可以理解。 因此這邊主要提供一些 Diagram,透過圖片勾勒出基本概念。 本文章主要討論的對象是 Modbus-RTU,為了撰寫方便,若有未表示協議類型的 Modubs 簡稱,請默認其為 Modbus-RTU。
Modbus 封包共分 4 大部分:
重點提要
1-247
可使用,其餘保留給特殊用途。傳送順序
Function Code 用來表示 Master 要 request Slave 做什麼,
Slave Register Map 分為 4 個部分:
Slave 設計 Register Map,告知 Master 自己數據資料格式。Master 透過 function code 即可指定對 Slave Register Map 哪一部分的資料進行讀或寫。由上圖可看到 I/O Address Name 前綴第一個數字即代表 Slave Register Map 類型,比如說 4xxxx 代表 Holding Registers。
而封包 PDU 之中指定的 address,是相對於 function code 指定操作對象(Slave Register Map 4 個部分之一)的相對位址。比如 function code 為 6; PDU Address 給 2 的話,那就是針對 Holding Register 區域 offset 2 的位置,即操作 Register Map 40003 (40001 + 2) 這個 Holding Register。
Modbus 本質是資料交換 protocol,不過從 Modbus Slave Register Map 4 個部分之命名方式,隱含其與 PLC I/O 的連結,如 coil, contact, analog in, analog out 等等。 源於歷史,當初設計這個 protocol 就是把 Slave 設計為擴充 Master 之 I/O 而生,Master 操作 Slave 猶如操作自己的 Coils 或 Contacts。
但既然 Modbus 本質是資料交換,那 Slave 端 Register Map 自然不限於跟 Coils 與 Contacts 交互,資料交換後得以進行任意運算或其他 I/O 操作。
Coils
或 Holding Registers
部分,資料流就是 : Master -> Slave。Discrete Inputs
或 Input Registers
部分,資料流就是 : Slave -> Master。Modbus RTU 協議規定相鄰的 Modbus frame 之間,必須有 「3.5 字元的傳輸時間之空閒間隔」來分隔封包:
在該時間區間內,master 與 slave 不做任何動作。 如果空閒間隔小於 3.5 字元時間,可能導致設備認為兩個 Modbus frame 是連續的一部分,從而發生解析錯誤。
3.5 字元時間如何計算?
以 UART 9600N81(8 個數據位元,無校驗位元,1 個停止位元)為例:
每個字元需要的時間:
(1 起始位 + 8 數據位 + 1 停止位) / baudrate
= (1 + 8 + 1) / 9600 秒
= 1.0417 毫秒/字元
3.5 字元的傳輸時間:
= 3.5 × 1.0417 毫秒
≈ 3.646 毫秒
字符傳送速度跟 UART baudrate 有關,跟封包大小無關。
Modbus RTU 要求 Modbus frame 內的所有字節必須無間斷發送。 若 Modbus frame 中間有超過 1.5 字元的空閒時間,接收端會認為該 frame 已經結束,並將剩餘部分當作下一個 Modbus frame 的開頭。
實驗步驟
在 Modbus RTU Slave 內部實作了 2 個 coils。
先以 Function Code 0x0F
Write Multiple Coils,將第 1 個 Coil 寫 ‘0’,第 2 個 Coil 寫 ‘1’。
再以 Function Code 0x01
Read Coils 讀取,將得到 Coils 讀值為 0x02。
幾點觀察:
至於每個 Byte 都是從 LSB 開始傳,最後才是 MSB,以上規範在規格書中都寫得很清楚。
不過一般使用者不需要了解那麼深入,只需要知道要放什麼資料即可,通常給使用者輸入的介面都是很友善的。
為了要驗證對於 protocol 了解的是否正確,一定要實測看看,所以找了現成的 Modbus-RTU Master 與 Slave 來測試。為了方便測試,Master 找了跑在 PC 上的 UI 程式,方便操作; 而 Slave 則找 Arduino 程式,畢竟手邊有許多 Arduino 裝置,若想測試多台 slaves 的 bus topology 會比較方便。
在 Windows PC 用 QModMaster 作為 Master 測試 Arduino Modbus RTU Slave 功能,測試 QModMaster 支援之 Function code: 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0f, 0x10。對照 Modbus Specification 手冊查看通訊格式,實測 Modbus-RTU Master 與 Slave 運作是否正確。
作者為: C.M.Bulliuer
程式架構簡單,但需要好幾個 libraries 才能運作,compile 不過只要依照缺少 library 之提示,透過 library manager 一一下載即可。下圖為需要之相依程式庫:
原始範例程式透過巨集定義是可以運行在多個平台的,只是為了呈現簡便,我只保留與我測試平台相關的部分。
#include <ModbusRTUSlave.h>
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__)
#define MODBUS_SERIAL Serial
#else
#define MODBUS_SERIAL Serial1
#define MODBUS_BAUD 115200
#define MODBUS_CONFIG SERIAL_8N1
#define MODBUS_UNIT_ID 10
const int16_t buttonPins[2] = {2, 3};
const int16_t ledPins[4] = {5, 6, 7, 8};
const int16_t dePin = 13;
const int16_t knobPins[2] = {A0, A1};
ModbusRTUSlave modbus(MODBUS_SERIAL, dePin);
const uint8_t numCoils = 2;
const uint8_t numDiscreteInputs = 2;
const uint8_t numHoldingRegisters = 2;
const uint8_t numInputRegisters = 2;
bool coils[numCoils];
bool discreteInputs[numDiscreteInputs];
uint16_t holdingRegisters[numHoldingRegisters];
uint16_t inputRegisters[numInputRegisters];
void setup() {
pinMode(knobPins[0], INPUT);
pinMode(knobPins[1], INPUT);
pinMode(buttonPins[0], INPUT_PULLUP);
pinMode(buttonPins[1], INPUT_PULLUP);
pinMode(ledPins[0], OUTPUT);
pinMode(ledPins[1], OUTPUT);
pinMode(ledPins[2], OUTPUT);
pinMode(ledPins[3], OUTPUT);
modbus.configureCoils(coils, numCoils);
modbus.configureDiscreteInputs(discreteInputs, numDiscreteInputs);
modbus.configureHoldingRegisters(holdingRegisters, numHoldingRegisters);
modbus.configureInputRegisters(inputRegisters, numInputRegisters);
MODBUS_SERIAL.begin(MODBUS_BAUD, MODBUS_CONFIG);
modbus.begin(MODBUS_UNIT_ID, MODBUS_BAUD, MODBUS_CONFIG);
}
void loop() {
inputRegisters[0] = analogRead(knobPins[0]);
inputRegisters[1] = analogRead(knobPins[1]);
discreteInputs[0] = !digitalRead(buttonPins[0]);
discreteInputs[1] = !digitalRead(buttonPins[1]);
modbus.poll();
analogWrite(ledPins[0], holdingRegisters[0]);
analogWrite(ledPins[1], holdingRegisters[1]);
digitalWrite(ledPins[2], coils[0]);
digitalWrite(ledPins[3], coils[1]);
}
User 為 Slave Register Map 的 4 個部分分別定義 Array 空間,並透過 configure...()
函式與 Register Map 綁定。
Configure 完畢後,使用者只須要操作自己定義的變數空間(user defined variables),無須去操作 Slave Register Map。
Slave 由 Modbus.poll()
函式,將 Slave Register Map 資料透過 Modbus 交換。
configure...()
等相關 configure API 只能設定一次,無法分次設置,所以必須一次給好完整空間。
這點比較可惜,因為我總想為 IO 設定有意義一點的名稱,比如 LED, fan 等等名稱,不想統統用 coils[] array 表示。
經過測試兩者運作無誤,透過 Modbus Protocol,Master 可以 request 控制 Slave 不管是數位或類比之輸入輸出。 QModMaster 以及 Arduino Slave Example 都很簡單也成功通過測試,可以作為 Modbus 學習參考工具。 若未來要自己實現自己的 Modbus-RTU Master 或者 Slave 裝置,都可以用這些工具交叉驗證。
先說結論: 這是 RS-485 的物理層特性 的限制,而非 Modbus。
每個設備(包括主站和從站)會對通信總線施加一定的負載,通常以 “單位負載(Unit Loads,UL)” 表示。 RS-485 總線上的每個裝置都是一個負載,RS-485 標準規範規定,驅動器必須能驅動 32 單位負載 。 每個負載會對總線施加一定的電氣負載,這些負載的累積不能超過驅動器的能力。意思是: 其中 1 台裝置作為驅動器 (Driver/Transceiver)發送訊號時,需要推動至少 31 台接收器 (Receiver)。
擴充方法:
Modbus 協議層限制了 ID 的範圍,Modbus 的從站地址(Slave ID)為 1 Byte,其中 0
、248-255
保留作其他用途(如廣播地址和未來擴展),其實際 Slaves 地址範圍為 1-247
。
Modbus 搭配 RS-485 時只能有 32 個設備是受制於 RS-485 物理層的能力。
這是 Modbus 協議的兩種傳輸模式,它們的主要區別在於數據編碼、傳輸速度以及處理方式。以下是它們的比較:
數據編碼:
0x0A
會被傳送為兩個字符 0
、A
(十六進制)。傳輸效率:
同步機制:
CR
(回車)和 LF
(換行)來標誌結束,這樣可以幫助接收方識別消息結尾。錯誤檢測:
總結來說,Modbus RTU 在大多數情況下都比 Modbus ASCII 更為高效,基本上現在很少在用 ASCII 模式,因此可以專注在 RTU 模式即可。