글 / Modbus·OPC UA

Modbus TCP 프레임 구조

헤더 7바이트의 length로 메시지의 끝을 판별하고, transaction identifier로 요청과 응답을 대응시킨다.

작성일 · 읽는 시간 2분 · Modbus Messaging on TCP/IP Implementation Guide V1.0b 기준

헤더의 필요성

TCP는 연속된 바이트 스트림이므로 메시지의 경계를 구분하지 않는다. Modbus TCP는 메시지마다 앞에 MBAP 헤더 7바이트를 추가하여 길이와 요청·응답의 대응 관계를 표시한다. 헤더 뒤에는 기능 코드와 데이터가 오며, 직렬 통신에서 사용하던 CRC는 포함되지 않는다.

MBAP 헤더

필드 크기 의미
transaction identifier 2바이트 요청과 응답을 대응시키는 번호. 서버가 그대로 반환
protocol identifier 2바이트 Modbus는 0
length 2바이트 이 필드 뒤에 남은 바이트 수
unit identifier 1바이트 게이트웨이 후단 직렬 장비의 주소

모든 필드는 상위 바이트를 먼저 전송하는 big-endian이다. 서버는 TCP 502번 포트에서 대기한다. TCP에 직접 연결된 장비에서는 unit identifier가 의미를 갖지 않으며, 구현 가이드는 0xFF를 권장한다.

MBAP 헤더 네 필드의 배치와 length가 세는 범위

03 요청과 응답 예제

holding register 세 개를 주소 0x006B부터 읽는 예이다.

text요청 12바이트
00 01   transaction identifier
00 00   protocol identifier
00 06   length (뒤에 6바이트)
FF      unit identifier
03      기능 코드
00 6B   시작 주소
00 03   레지스터 개수
text응답 15바이트
00 01 00 00 00 09 FF   헤더 (length는 9)
03 06                  기능 코드, 데이터 바이트 수
02 2B 00 00 00 64      값 555, 0, 100

03 요청 12바이트와 응답 15바이트의 바이트 배치

수신 바이트 해석

pythonparse.py
import struct

rsp = bytes.fromhex("00 01 00 00 00 09 FF 03 06 02 2B 00 00 00 64")

# >는 big-endian, H는 2바이트, B는 1바이트다
tid, pid, length, unit = struct.unpack(">HHHB", rsp[:7])
# length는 unit identifier 1바이트도 세므로, 헤더 뒤로는 length - 1바이트가 온다
assert len(rsp) == 7 + length - 1

function, count = struct.unpack(">BB", rsp[7:9])
values = struct.unpack(f">{count // 2}H", rsp[9:])
print(tid, function, values)  # 1 3 (555, 0, 100)