Learning Objectives
- 說明一個 16 位元暫存器能放的範圍,以及為什麼大的整數與小數要用兩個暫存器
- 用 struct 把 32 位元整數與浮點數拆成兩個暫存器,再組回來
- 說明「字組順序」的差異(ABCD、CDAB 等),從讀到的值判斷順序有沒有選錯
- 用 pymodbus 的 convert_from_registers/convert_to_registers 讀寫 FLOAT32 與 INT32
一個暫存器只有 16 位元
Modbus 的保持暫存器與輸入暫存器都是 16 位元:當成無號整數是 0~65535,當成有號整數(二補數)是 −32768~32767。 第 10 課用「乘 10 存整數」放溫度(23.5 °C 存成 235),這招很常用,但有兩個限制:
- 範圍不夠:流量計的累計量 100000 公升、能源表的累計 kWh,一下子就超過 65535。
- 精度要事先決定:乘 10 只留一位小數;要三位小數就乘 1000,範圍又更小。
所以很多設備把一個數放在兩個相鄰的暫存器裡,組成 32 位元:
| 型別 | 位元 | 範圍或精度 | 佔幾個暫存器 |
|---|---|---|---|
| INT16/UINT16 | 16 | −32768~32767/0~65535 | 1 |
| INT32/UINT32 | 32 | 約 ±21 億/0~約 42 億 | 2 |
| FLOAT32(IEEE 754 單精度) | 32 | 有效數字約 7 位 | 2 |
| FLOAT64(雙精度) | 64 | 有效數字約 15 位 | 4 |
這一課要回答:
- 一個 32 位元的數怎麼拆成兩個暫存器?
- 為什麼同一組暫存器,用不同的軟體讀會得到天差地遠的值?
- 用 pymodbus 怎麼讀寫這些型別?
逐步看:100000 拆成兩個暫存器
先用除法與餘數拆,再用 struct 拆,兩種方法得到同樣的結果。最後看負數:−2 拆出來是兩個很大的數,這是二補數。
浮點數與字組順序
小數最常用 IEEE 754 單精度浮點數(FLOAT32):4 個位元組,1 個符號位元、8 個指數位元、23 個小數位元。 它能表示很大與很小的數,但有效數字只有大約 7 位,很多十進位小數(例如 23.4)存不剛好,只能存最接近的值。
麻煩的是 4 個位元組放進兩個暫存器的順序。Modbus 規定一個暫存器裡是大端序(高位元組先送), 但兩個暫存器誰先,規格沒有規定,各廠牌自己決定。習慣用 ABCD 代表 4 個位元組由高到低:
| 寫法 | 暫存器 n | 暫存器 n+1 | 常見的說法 |
|---|---|---|---|
| ABCD | A B | C D | 大端序、高位字組在前 |
| CDAB | C D | A B | 字組交換、低位字組在前 |
| BADC | B A | D C | 位元組交換 |
| DCBA | D C | B A | 小端序 |
順序選錯不會出錯訊息,只會得到一個看起來很怪的數(例如 4.17e-08 或 86 億)。 所以讀新設備時,先查手冊的暫存器表與字組順序;手冊沒寫清楚時,用一個你知道答案的量(例如室溫)試讀,看哪一種順序合理。
逐步看:23.4 存成浮點數
注意「組回來」那一行:拿到的不是 23.4,而是 23.399999618530273。最後一行故意把兩個暫存器對調,看會得到什麼。
逐步看:用 pymodbus 讀 FLOAT32 與 INT32
模擬的儀表把氣壓(FLOAT32)放在保持暫存器 0、1,累計流量(INT32)放在 2、3,都是高位字組在前。
注意階梯圖的 MOV 34464 → MW3:PLC 的字組是有號 16 位元,在 PLC 的監看畫面上這個值會顯示成 −31072;同樣的 16 個位元,用 Modbus 讀到的是無號的 34464。
小測驗:範圍與拆分
能源表的累計電量是 250000 kWh(整數),最適合用哪種型別放進 Modbus 暫存器?
下面的程式會印出什麼?
value = 70000
print([value // 65536, value % 65536])
下面的程式會印出什麼?
import struct
raw = struct.pack(">HH", 65535, 65535)
print(struct.unpack(">i", raw)[0])
小測驗:字組順序
下面的程式模擬「設備用低位字組在前(CDAB)」,會印出什麼?
import struct
regs = list(struct.unpack(">HH", struct.pack(">f", 1.5)))
print(regs, regs[::-1])
同一台設備,用監控軟體甲讀到 25.3 °C,用你的 Python 程式讀到 1.2e+12。最先該檢查什麼?
重點整理
- 一個暫存器 16 位元:無號 0~65535、有號 −32768~32767。放不下或要小數時,用兩個暫存器組成 32 位元。
- INT32:高位字組=商(÷ 65536),低位字組=餘數;負數是二補數。
struct的>是大端序,H是 16 位元無號、i是 32 位元有號、f是單精度浮點數。 - FLOAT32 只有約 7 位有效數字,23.4 讀回來是 23.399999618530273,顯示時取到需要的位數。
- 一個暫存器內固定大端序,兩個暫存器的先後各廠牌不同(ABCD、CDAB…);選錯只會得到不合理的值,不會有錯誤訊息。
- pymodbus:
client.convert_from_registers(regs, client.DATATYPE.FLOAT32, word_order="little")讀,convert_to_registers寫,再用write_registers送出。
下一課換一種通訊方式:OPC UA 不只傳數字,還傳名稱、型別與結構。