Learning Objectives
- 說明 RS-485 的接線特性:差動、兩線式半雙工、多點匯流排與終端電阻
- 拆解 Modbus RTU 訊框:站號、功能碼、資料、CRC,以及訊框之間的靜默間隔
- 自己用 Python 算出 CRC-16,並說明低位元組先送
- 用 pymodbus 的 ModbusSerialClient 讀取 PLC,處理參數不一致、沒有回應與例外回應
一對線,串起整條產線的儀表
溫控器、電力錶、變頻器、流量計……很多工業設備背後都有兩個標著 A、B(或 D+、D−)的端子:那是 RS-485 介面, 上面跑的通訊協定最常見的就是 Modbus RTU。一條雙絞線可以串起幾十台設備,讓 PLC 或電腦一台一台去問數值。
這一課要回答:
- RS-485 為什麼適合工廠?接線要注意什麼?
- Modbus RTU 一筆訊息長什麼樣子?每個位元組代表什麼?
- 接收端怎麼知道訊息在傳輸途中有沒有被干擾?
- 用 Python 讀取時,常見的錯誤有哪些?
RS-485 的特性
| 特性 | 說明 |
|---|---|
| 差動訊號 | 用兩條線的電壓差表示 0 與 1;雜訊通常同時加在兩條線上,相減之後就抵消了,所以抗雜訊、能傳遠 |
| 兩線式、半雙工 | 同一對線上,同一時間只能有一台設備在送(四線式是選用的做法) |
| 多點匯流排 | 設備依序串在同一條主幹線上(菊鏈),不要接成星狀的分岔 |
| 終端電阻 | 主幹線的兩端各接一顆終端電阻,減少訊號在線尾反射 |
| 共同地線 | 除了 A、B,還要一條共同地線(Common)把各設備的參考電位接在一起 |
Modbus 序列線路規格(Modbus over Serial Line V1.02)寫明:不加中繼器時一條 RS-485 匯流排至少允許 32 台設備, 主幹線最長可到 1000 m(實際依鮑率與線材而定)。RS-485 只規定電氣特性(TIA/EIA-485),上面跑什麼協定是另一回事。
安全:RS-485 的設備常裝在控制盤裡,旁邊可能有市電或 24 V 電源。接線前先斷電並依工廠規定上鎖掛牌,盤內的市電部分由合格人員處理。
Modbus RTU 的訊框
Modbus 是請求與回應的協定:一台 client(發問的一方,通常是 PLC、電腦或 HMI)送出請求,被問到的 server(儀表、變頻器、PLC)回應。 舊文件稱為 master/slave(主站/從站),Modbus Organization 在 2020 年改用 client/server,兩種說法現在都還很常見。
序列線路上的每台 server 有一個站號(1~247),0 是廣播:所有 server 都會執行,但不回應(只能用在寫入)。
一個 RTU 訊框:
站號 功能碼 資料(位址、數量、值…) CRC 低 CRC 高
1 位元組 1 位元組 N 位元組 1 位元組 1 位元組
例如「讀取站號 1 的保持暫存器,從位址 0 開始讀 2 個」:
01 03 00 00 00 02 C4 0B
站號 功能碼 起始位址 數量 CRC
- 位址、數量這類 16 位元的數字,高位元組在前(00 02)。
- CRC 卻是低位元組先送(C4 0B 代表 CRC 值 0x0BC4)。這是規格規定的,最容易搞混。
- RTU 沒有「開始」「結束」字元,接收端靠靜默間隔分辨訊框:訊框之間至少要安靜 3.5 個字元的時間。一個 RTU 訊框最多 256 個位元組。
常用的功能碼:01 讀線圈、02 讀離散輸入、03 讀保持暫存器、04 讀輸入暫存器、05 寫單一線圈、06 寫單一暫存器、 15 寫多個線圈、16 寫多個暫存器(下一課詳細說明這四種資料)。
逐步看:自己算 CRC-16
CRC(循環冗餘檢查)是把整個訊框「濃縮」成 16 個位元的檢查碼。送方算好附在最後;收方用同樣的方法再算一次,
不一樣就表示資料在路上被改了。注意每個位元組都要做 8 次「右移,必要時和 A001 做 XOR」;可以拖動進度條跳過中間的步驟,看每個位元組算完後 crc 的值。
用 pymodbus 經 RS-485 讀資料
電腦接 RS-485 通常用 USB 轉 RS-485 的轉接器,在 Windows 上會出現一個 COM 埠(Linux 是 /dev/ttyUSB0 這類名稱)。
Python 用 pymodbus 套件:
from pymodbus.client import ModbusSerialClient
client = ModbusSerialClient("COM4", baudrate=9600, parity="N", stopbits=2, timeout=1)
client.connect()
rr = client.read_holding_registers(0, count=2, device_id=1)
print(rr.registers)
client.close()
序列參數兩端必須完全一致:鮑率、資料位元、同位檢查(N 無/E 偶/O 奇)、停止位元。規格的預設是偶同位; 使用無同位時規格要求 2 個停止位元(很多設備也接受 1 個,以設備手冊為準)。參數不一致時,對方收到的是亂碼,不會回應。
pymodbus 的版本差異(本課用 3.x 的寫法,撰寫時最新版是 3.16):
| 版本 | 指定站號的參數 | 其他 |
|---|---|---|
| 3.10 以後 | device_id=1 |
ModbusSlaveContext 改名 ModbusDeviceContext |
| 3.3~3.9 | slave=1 |
3.8 起 count 等參數必須用關鍵字寫(count=2,不能只寫 2) |
| 更舊的版本 | unit=1 |
網路上很多舊範例是這種寫法 |
站號省略時預設是 1。照網路範例寫卻出現「unexpected keyword argument」,多半就是版本不同;先用 pip show pymodbus 看版本,再對照官方的 API 變更說明。
本課的模擬器 slave= 和 device_id= 都接受。
逐步看:讀保持暫存器與線圈
看畫面下方的通訊紀錄:每一次讀取都有一筆「→」請求和一筆「←」回應,都是真的 RTU 訊框,最後兩個位元組是 CRC。拿回應的 16 進位到上面的小工具解讀看看。
逐步看:沒有回應與例外回應
兩種錯誤長得不一樣:沒有回應(站號不存在、參數不一致、線斷了、CRC 錯)只能等到逾時; 例外回應表示 server 收到了、但做不到,會回傳功能碼加 0x80(03 變成 83)與例外碼。注意第二次請求之後虛擬時間跳了 1 秒,那是在等逾時。
小測驗:RS-485 與訊框
RS-485 匯流排的終端電阻要接在哪裡?
CRC 值是 0x0BC4,RTU 訊框最後兩個位元組依序是?下面的程式會印出什麼?
c = 0x0BC4
print(format(c & 0xFF, "02X"), format(c >> 8, "02X"))
client 送出一個站號 0 的寫入請求,會發生什麼事?
小測驗:錯誤處理
讀取時收到回應 01 83 02 C0 F1,代表什麼?
下列哪一種情況,client 會收到「例外回應」而不是「沒有回應」?
重點整理
- RS-485:差動、兩線式半雙工、多點菊鏈,主幹線兩端接終端電阻,另接共同地線。
- Modbus 是請求與回應:client 發問、server 回答;站號 1~247,0 是不回應的廣播。
- RTU 訊框=站號+功能碼+資料+CRC;16 位元的數字高位元組在前,CRC 低位元組在前;訊框之間靠 3.5 字元的靜默間隔分開。
- CRC-16:初值 FFFF,每個位元組做 8 次「右移,最低位元是 1 就和 A001 做 XOR」。
- 鮑率、同位、停止位元兩端必須一致;不一致、站號不存在、CRC 錯誤都是「沒有回應」,只能等逾時。
- 位址超出範圍等「收到了但做不到」的情況,server 回例外回應(功能碼+0x80、例外碼)。
- pymodbus 3.10 起用
device_id=,之前是slave=;照舊範例寫出錯時先查版本。
下一課整理 Modbus 的四種資料:線圈、離散輸入、輸入暫存器、保持暫存器,以及最容易出錯的位址編號。