Modbus 32비트 값의 바이트 순서
레지스터 내부의 바이트 순서는 사양이 규정하지만 두 레지스터의 순서는 장비마다 다르므로 직접 확인해야 한다.
문제 정의
Modbus 레지스터는 16비트이므로 32비트 정수나 실수는 레지스터 두 개에 분할하여 저장한다. 사양은 레지스터 하나를 상위 바이트부터 전송한다는 것까지만 규정하고, 32비트 값을 두 레지스터에 분할하는 방법은 규정하지 않는다. 따라서 어느 레지스터가 상위 워드(값의 상위 16비트)인지는 장비마다 다르다.
바이트 순서의 종류
값 123456(0x0001E240)의 네 바이트를 차례로 A B C D라고 하면 저장 방법은 네 가지이다.
| 이름 | 수신 바이트 | 레지스터 두 개 | 의미 |
|---|---|---|---|
| ABCD | 00 01 E2 40 | 0x0001, 0xE240 | 상위 워드 먼저 |
| CDAB | E2 40 00 01 | 0xE240, 0x0001 | 하위 워드 먼저 |
| BADC | 01 00 40 E2 | 0x0100, 0x40E2 | 워드 내부의 바이트 순서를 반전 |
| DCBA | 40 E2 01 00 | 0x40E2, 0x0100 | 전체 바이트 순서를 반전 |
워드 순서만 다른 ABCD와 CDAB가 우선 확인할 후보이다.
struct를 이용한 두 순서 비교
import struct
regs = [0x0001, 0xE240] # 장비에서 읽은 레지스터 두 개
def u32(regs: list[int], word_swap: bool = False) -> int:
hi, lo = (regs[1], regs[0]) if word_swap else regs
# 두 레지스터를 4바이트로 이어 붙인 뒤 32비트로 다시 푼다
return struct.unpack(">I", struct.pack(">HH", hi, lo))[0]
print(u32(regs)) # 123456
print(u32(regs, word_swap=True)) # 3795845121
실수는 ">I"를 ">f"로 변경하여 해석한다. 123.456은 0x42F6, 0xE979로 저장되고, 순서를 반대로 해석하면 약 -1.88e25가 된다.
순서 확인 방법
설명서에 순서가 명시되어 있지 않으면 값을 이미 알고 있는 레지스터를 읽어 두 가지 방법으로 해석한다. 하나는 올바른 값이고 다른 하나는 비정상적인 값이다.
두 레지스터는 하나의 요청으로 함께 읽는다. 별도의 요청으로 읽으면 그 사이에 값이 변경되어 상위와 하위가 서로 다른 시점의 값이 될 수 있다.
참고
pymodbus 3.8.1부터 convert_from_registers는 word_order 인자("big", "little")로 워드 순서를 선택한다.