Learning Objectives
- 分辨線圈、離散輸入、輸入暫存器、保持暫存器的大小、讀寫權限與功能碼
- 把設備手冊的編號(例如 40001)正確換算成程式裡的位址,避免差一錯誤
- 用 16 位元暫存器表示負數與小數(二補數、乘以 10)
- 說明本課程模擬 PLC 的 I、Q、IW、MW 對應到哪一種 Modbus 資料
Modbus 只認得四種資料
上一課用功能碼 03 讀到了 PLC 的資料。Modbus 不管對方是 PLC、電力錶還是變頻器,一律把資料看成四張表:
| 資料表 | 大小 | client 能不能寫 | 讀的功能碼 | 寫的功能碼 | 常放什麼 |
|---|---|---|---|---|---|
| 離散輸入(discrete input) | 1 位元 | 唯讀 | 02 | 無 | 按鈕、限位開關、感測器的開關狀態 |
| 線圈(coil) | 1 位元 | 可讀寫 | 01 | 05(單一)、15(多個) | 輸出、啟停命令 |
| 輸入暫存器(input register) | 16 位元 | 唯讀 | 04 | 無 | 量測值:溫度、壓力、電流 |
| 保持暫存器(holding register) | 16 位元 | 可讀寫 | 03 | 06(單一)、16(多個) | 設定值、參數、各種狀態 |
「線圈」這個名字來自繼電器的線圈,也就是輸出。很多設備把幾乎所有資料都放在保持暫存器裡, 所以 03 與 06 是最常用的功能碼;拿到一台新設備,第一件事是找手冊裡的暫存器對照表(register map)。
這一課要回答:
- 四張表怎麼對應到 PLC 的 I、Q、IW、MW?
- 手冊寫「40001」,程式裡的位址要填多少?
- 16 位元的暫存器怎麼放負數與小數?
本課程模擬 PLC 的對應
| Modbus | 位址 | 模擬 PLC |
|---|---|---|
| 線圈 | 0~15 | Q0.0~Q1.7(線圈 0=Q0.0、線圈 8=Q1.0) |
| 離散輸入 | 0~15 | I0.0~I1.7 |
| 輸入暫存器 | 0~7 | IW0~IW7(類比輸入原始值) |
| 保持暫存器 | 0~31 | MW0~MW31 |
超出範圍會收到例外碼 2(非法資料位址)。各廠牌 PLC 的對應方式都不一樣,有的要在軟體裡自己設定,以手冊為準。
逐步看:四張表各讀一次
對照畫面上 PLC 的輸入、輸出燈與字組,看每一次讀取拿到的是哪一部分。最後試著寫保持暫存器;離散輸入與輸入暫存器沒有寫入的功能碼。
位址從 0 開始,編號從 1 開始
Modbus 規格裡有兩種數法:
- 訊框(PDU)裡的位址:從 0 開始,0~65535。pymodbus 的
address參數就是這個。 - 資料模型的編號:每張表的元素從 1 開始編號。規格寫明:編號 X 的元素,在訊框裡的位址是 X − 1。
很多設備手冊還沿用一種 5 位數(或 6 位數)的寫法,用第一個數字表示哪一張表:
| 手冊寫法 | 資料表 | 程式裡的位址 |
|---|---|---|
| 00001~ | 線圈 | 編號 − 1 |
| 10001~ | 離散輸入 | 編號 − 10001 |
| 30001~ | 輸入暫存器 | 編號 − 30001 |
| 40001~ | 保持暫存器 | 編號 − 40001 |
所以手冊的「40002」要寫成 read_holding_registers(1, …)。直接填 40002 會讀到完全不同的位址(多半超出範圍,收到例外碼 2);
只差 1 的錯誤更麻煩:不會出錯,只是讀到隔壁的值。有的手冊直接寫從 0 開始的位址,有的用 16 進位,拿到手冊先確認它用哪一種,
再拿一個已知的值(例如型號代碼)讀讀看。
逐步看:40002 要填多少
看通訊紀錄裡第一筆請求的位址欄位 9C 42:那是 40002 的 16 進位,PLC 沒有這麼大的位址,回了例外碼 2。
16 位元能放什麼數字
一個暫存器只有 16 個位元,在訊框裡高位元組在前(大端序,big-endian):250 傳成 00 FA。
- 無號整數:0~65535。
- 有號整數:用二補數表示 −32768~32767。−50 存成 65486(0xFFCE),讀回來時,最高位元是 1 就減 65536。
- 小數:很多設備把數值乘以 10 或 100 存成整數,例如 25.0 °C 存成 250,手冊會寫「單位 0.1 °C」或「比例 ×10」。
Modbus 本身不知道暫存器裡是有號還是無號,怎麼解讀由手冊決定。
pymodbus 回傳的 registers 一律是 0~65535 的無號數,負數要自己轉換。
需要更大的整數或浮點數時,要用兩個暫存器組成 32 位元,第 14 課會專門介紹(也會用到 convert_from_registers)。
一次能讀的數量也有上限:讀暫存器最多 125 個、讀線圈或離散輸入最多 2000 個(因為一個 Modbus PDU 最多 253 個位元組)。
逐步看:寫入負數的補正值
階梯圖每次掃描都算 MW3=MW1+MW2。Python 把 −5.0 °C 的補正值寫到 MW2,PLC 用有號整數計算,得到 20.0 °C;但 Python 讀回來的 MW2 是 65486,要自己轉回負數。
小測驗:四張表
要讀一台變頻器的「目前輸出頻率」(唯讀的 16 位元量測值),最可能用哪一個功能碼?
在本課程的模擬 PLC 上,線圈 10 對應哪個輸出?
手冊寫「40010:馬達速度設定」。程式要寫哪個位址?
小測驗:16 位元的數字
下面的程式會印出什麼?
v = 65436
if v >= 0x8000:
v -= 0x10000
print(v / 10)
暫存器的值 300,在 Modbus 訊框裡是哪兩個位元組?
重點整理
- 四張表:離散輸入(1 位元、唯讀、02)、線圈(1 位元、讀寫、01/05/15)、輸入暫存器(16 位元、唯讀、04)、保持暫存器(16 位元、讀寫、03/06/16)。
- 訊框裡的位址從 0 開始;手冊的 4xxxx 寫法從 1 開始數,位址=編號 − 40001。先用已知的值確認。
- 暫存器 16 位元、高位元組在前;有號數用二補數,讀回來 ≥ 32768 就減 65536;小數常用「乘以 10」存成整數。
- 本課程模擬 PLC:線圈=Q、離散輸入=I、輸入暫存器=IW、保持暫存器=MW;各廠牌的對應以手冊為準。
- 要改變設備的行為,寫保持暫存器裡的設定值;輸入類的資料只能讀。
下一課換到乙太網路:Modbus TCP 的訊框多了 MBAP 標頭、少了 CRC,用法幾乎一樣。