Modbus RTU 패킷 구조 분석: Slave Address·Function Code·Data·CRC 읽는 법

Modbus RTU 패킷 구조 분석: Slave Address·Function Code·Data·CRC 읽는 법#

1장 HEX 한 줄에서 Modbus RTU 요청을 읽어보자#

현장에서 다음과 같은 HEX 데이터를 발견했다고 하겠습니다.

11 03 00 00 00 02 C6 9B

처음 보면 의미 없는 숫자의 나열처럼 보입니다.

하지만 Modbus RTU 구조를 알고 있다면 다음처럼 나눌 수 있습니다.

11
→ Slave Address

03
→ Function Code

00 00
→ Starting Address

00 02
→ Quantity

C6 9B
→ CRC

이를 사람이 읽을 수 있는 문장으로 바꾸면:

Address 0x11인 장비에게 Holding Register 0번부터 2개를 읽어 달라는 요청

정도로 해석할 수 있습니다.

Modbus RTU 패킷 분석은 결국 바이트를 역할별로 나누고 Function Code에 따라 Data 영역을 다시 해석하는 작업입니다.


2장 Modbus RTU Frame의 기본 구조#

Modbus RTU Frame은 일반적으로 다음 구조로 이해할 수 있습니다.

[Address][Function][Data][CRC]

각 Field의 역할은 다음과 같습니다.

Field 일반적인 크기 역할
Address 1Byte 통신 대상 장비 식별
Function 1Byte 읽기·쓰기 등 수행할 작업
Data 가변 Address, Quantity, 값 등
CRC 2Byte Frame 오류 검출

Modbus RTU는 STX·ETX 같은 시작·종료 Byte를 사용하는 방식이 아닙니다.

대신 Serial Line에서의 Frame Timing과 Protocol 구조를 이용해 Message 경계를 구분합니다.


3장 Slave Address는 누구에게 보내는 요청인지 알려준다#

RS-485 Bus에 여러 Modbus 장비가 연결되어 있다고 하겠습니다.

Modbus Client
     │
     ├─ Device 0x11
     ├─ Device 0x12
     └─ Device 0x13

Client가:

11 03 00 00 00 02 CRC

를 보냈다면 첫 Byte:

11

은 0x11 Address의 장비를 대상으로 합니다.

다른 장비는 자신에게 온 요청이 아니므로 일반적으로 응답하지 않습니다.


3.1 주소 충돌이 생기면 어떻게 될까#

두 장비가 동일한 Address를 사용하면 같은 Request에 두 장비가 모두 반응할 수 있습니다.

그 결과:

응답 충돌

깨진 Frame

CRC Error

간헐적인 Timeout

같은 현상이 발생할 수 있습니다.

따라서 여러 장비를 하나의 RS-485 Bus에 연결할 때는 Device Address 중복을 먼저 확인해야 합니다.


3.2 Broadcast Address도 존재한다#

Modbus Serial Line에서는 Address 0이 Broadcast 용도로 사용될 수 있습니다.

개념적으로:

00 ...

은 특정 Device 하나가 아니라 여러 Server Device를 대상으로 할 수 있습니다.

Broadcast Request에서는 일반적으로 개별 Response를 기대하지 않습니다.

따라서 Address 0을 일반 장비 Address처럼 사용하는 것은 피해야 합니다.


4장 Function Code를 보면 무엇을 하려는지 알 수 있다#

두 번째 Byte는 Function Code입니다.

예:

11 03 ...
   ^^

0x03은:

Read Holding Registers

입니다.

대표적인 Function Code를 다시 정리하면:

Function 의미
0x01 Read Coils
0x02 Read Discrete Inputs
0x03 Read Holding Registers
0x04 Read Input Registers
0x05 Write Single Coil
0x06 Write Single Register
0x0F Write Multiple Coils
0x10 Write Multiple Registers

따라서 패킷 분석에서는 Address 다음으로 Function Code를 확인하는 것이 핵심입니다.

Function Code가 결정되어야 뒤에 오는 Data Field의 구조도 알 수 있기 때문입니다.


5장 Function Code에 따라 Data 구조가 달라진다#

다음 Frame을 다시 보겠습니다.

11 03 00 00 00 02 C6 9B

Function Code가 03이므로 Data 영역은:

Starting Address

Quantity of Registers

로 해석합니다.

즉:

00 00
→ Starting Address = 0

00 02
→ Quantity = 2

입니다.

반면 Function Code가 06이면 같은 위치의 Byte가 전혀 다른 의미를 가질 수 있습니다.

예:

11 06 00 02 00 0A CRC

는:

11
→ Device Address

06
→ Write Single Register

00 02
→ Register Address

00 0A
→ Write Value = 10

입니다.

따라서 HEX 위치만 보고 의미를 외우면 안 되고 Function Code를 먼저 봐야 합니다.


6장 FC03 Read Holding Registers 요청을 직접 분석해보자#

예:

11 03 00 10 00 02 CRC

Field별로 나누면:

Offset 값 의미
0 11 Slave Address
1 03 Read Holding Registers
2~3 00 10 Starting Address
4~5 00 02 Quantity
6~7 CRC 오류 검출

Starting Address:

00 10

은 Hexadecimal 0x0010, 즉 Decimal 16입니다.

Quantity:

00 02

이므로 Register 2개를 요청합니다.

개념적으로:

Holding Register 16

Holding Register 17

을 읽는 요청입니다.


7장 응답 패킷은 요청과 구조가 다르다#

FC03의 정상 Response를 예로 들어보겠습니다.

11 03 04 00 19 00 64 CRC

Field별로 나누면:

11
→ Slave Address

03
→ Function Code

04
→ Byte Count

00 19
→ Register 1

00 64
→ Register 2

CRC
→ 오류 검출값

Byte Count = 04인 이유는 Register 두 개가 각각 2Byte이기 때문입니다.

2 Registers
×
2 Bytes

=
4 Bytes

입니다.


7.1 요청의 Quantity와 응답 Byte Count를 함께 본다#

Request:

Quantity = 2

Response:

Byte Count = 4

라면 구조적으로 맞습니다.

반대로 Quantity가 2인데 Response의 Byte Count가:

02

라면 정상적인 FC03 Response 구조와 맞지 않습니다.

이런 관계를 이용하면 Frame이 잘렸거나 Parser가 잘못된 위치에서 시작했는지 판단하는 데 도움이 됩니다.


8장 Register 값은 읽었다고 끝이 아니다#

Response:

00 19 00 64

를 두 개의 16Bit Register로 보면:

00 19
→ 25

00 64
→ 100

입니다.

하지만 실제 의미는 아직 알 수 없습니다.

제조사 Register Map에서:

Register 16
→ Motor Temperature
Scale 0.1

Register 17
→ Operation Count

라고 했다면 실제 값은:

Motor Temperature
2.5°C

Operation Count
100

일 수 있습니다.

따라서 Modbus Packet 분석은:

HEX 해석
↓
Register 값 추출
↓
Manufacturer Register Map
↓
Data Type·Scale 적용
↓
실제 업무 값

순서로 이어집니다.


9장 32Bit 값은 Register 두 개를 함께 읽어야 할 수 있다#

Register 하나는 기본적으로 16Bit입니다.

따라서 32Bit Integer 또는 Float를 사용하면 두 Register가 필요할 수 있습니다.

예:

Register 100
12 34

Register 101
56 78

두 개를 조합하면:

12 34 56 78

이라는 32Bit 값이 될 수 있습니다.

하지만 실제 장비에서는 Word Order가:

56 78 12 34

일 수도 있습니다.

그래서 다음을 확인해야 합니다.

Data Type

Byte Order

Word Order

Scale

값이 말도 안 되게 보인다면 통신 Packet보다 Data Conversion 규칙이 잘못된 경우도 많습니다.


10장 CRC는 마지막 2Byte를 검증한다#

Modbus RTU Frame 끝에는 CRC가 있습니다.

구조:

[Address][Function][Data][CRC Lo][CRC Hi]

Modbus RTU에서는 CRC의 Low Byte가 먼저 전송됩니다.

예를 들어 계산된 CRC 값 자체를 숫자로 표현했을 때:

0x9BC6

라면 Wire에는:

C6 9B

순서로 나타날 수 있습니다.

그래서 HEX Dump를 볼 때 CRC 숫자의 표기 순서와 실제 전송 Byte 순서를 혼동하지 않아야 합니다.


10.1 CRC 계산 범위#

CRC 계산에는:

Address

Function

Data

를 포함합니다.

CRC Field 자체는 계산 대상에 포함하지 않습니다.

예:

11 03 00 00 00 02

를 이용해 CRC를 계산한 다음 마지막에 CRC 두 Byte를 붙입니다.


11장 Python으로 Modbus RTU CRC를 계산해보자#

시험 환경에서 CRC를 확인하려면 다음처럼 구현할 수 있습니다.

def modbus_crc16(data: bytes) -> int:
    crc = 0xFFFF

    for b in data:
        crc ^= b

        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1

    return crc & 0xFFFF

예를 들어:

request = bytes([
    0x11,
    0x03,
    0x00,
    0x00,
    0x00,
    0x02
])

crc = modbus_crc16(request)

crc_low = crc & 0xFF
crc_high = (crc >> 8) & 0xFF

print(hex(crc_low), hex(crc_high))

처럼 Low Byte와 High Byte를 분리할 수 있습니다.

운영 코드에서는 직접 구현한 CRC를 바로 믿기보다 Known Test Vector나 검증된 Library와 결과를 비교하는 것이 좋습니다.


12장 Modbus RTU는 STX·ETX 대신 Timing도 중요하다#

Modbus RTU에는 일반적인:

STX
ETX

가 없습니다.

Serial Line에서 Frame 사이의 Silent Interval이 Message 경계를 식별하는 중요한 역할을 합니다.

전통적인 Modbus RTU 규칙에서는 Frame 사이에 최소 약 3.5 Character Time의 Silent Interval을 두는 방식이 사용됩니다.

따라서 Receiver는 단순히:

Byte가 안 왔다.

가 아니라 Timing을 함께 고려해야 합니다.


12.1 3.5 Character Time을 단순히 millisecond로 고정하면 안 된다#

Character Time은 Serial 설정과 Baud Rate의 영향을 받습니다.

예:

Baud Rate

Data Bits

Parity

Stop Bits

에 따라 한 Character를 전송하는 시간이 달라집니다.

따라서 모든 환경에:

3.5 Character
=
무조건 4ms

같은 식으로 고정해서 이해해서는 안 됩니다.


13장 Exception Response를 HEX에서 구분하는 방법#

정상 Request:

11 03 ...

을 보냈다고 하겠습니다.

장비가 요청을 처리할 수 없다면 Exception Response를 반환할 수 있습니다.

원래 Function Code:

03

의 최상위 Bit를 1로 만들면:

83

이 됩니다.

예:

11 83 02 CRC

이를 해석하면:

11
→ Device Address

83
→ FC03 Exception

02
→ Illegal Data Address

CRC
→ 오류 검출

입니다.

즉 잘못된 Register Address를 요청했을 가능성을 먼저 확인합니다.


13.1 대표적인 Exception Code#

Code 의미
01 Illegal Function
02 Illegal Data Address
03 Illegal Data Value
04 Server Device Failure
05 Acknowledge
06 Server Device Busy

예:

83 02

라면:

FC03 요청 중

Illegal Data Address

입니다.

이 상황에서 같은 Packet을 계속 Retry하는 것보다 Register Map을 확인하는 것이 우선입니다.


14장 응답이 전혀 없는 것과 Exception Response는 다르다#

두 상황을 구분해야 합니다.

Exception Response가 있음#

TX
11 03 ...

RX
11 83 02 ...

의미:

Device와 통신은 됨

Request도 해석함

하지만 요청 내용에 문제가 있음

입니다.


아무 응답도 없음#

TX
11 03 ...

RX
없음

가능한 원인:

전원 꺼짐

Address 오류

Baud 불일치

Parity 불일치

RS-485 배선 문제

CRC 오류

Device Offline

Bus Collision

등입니다.

따라서 Exception Response와 Timeout은 전혀 다른 진단 경로를 가져야 합니다.


15장 RS-485 문제도 Modbus 패킷 장애처럼 보일 수 있다#

Modbus RTU 자체가 정상적으로 구현되어 있어도 Physical Layer가 불안정하면 CRC Error가 발생할 수 있습니다.

확인할 항목:

A/B 또는 D+/D-

Baud Rate

Data Bits

Parity

Stop Bits

Termination

Bias

Cable

Ground Potential

EMI

Topology

입니다.

특히 A/B 명칭은 제조사마다 표기가 다를 수 있으므로:

A = 무조건 +
B = 무조건 -

라고 판단하지 말고 Manual의 극성 정의를 확인해야 합니다.


15.1 CRC Error가 간헐적이면 환경도 본다#

예:

평상시 정상

Gate Motor 동작 순간
CRC Error 증가

라면 Motor·Inverter 등의 Noise와 Cable 경로를 확인할 수 있습니다.

반면:

모든 Packet에서 CRC 실패

라면:

잘못된 Serial Setting

CRC 구현 오류

잘못된 Frame Boundary

Protocol 불일치

같은 문제를 우선 살펴볼 수 있습니다.


16장 같은 Bus의 여러 장비를 분석하는 방법#

예를 들어 다음과 같이 Address를 배정했다고 하겠습니다.

0x11
→ Gate Controller

0x12
→ Loop Detector

0x13
→ Reader Controller

Capture:

11 03 ...
11 03 ...

12 02 ...
12 02 ...

13 04 ...

처럼 보일 수 있습니다.

Address를 기준으로 분리하면 어떤 장비와 통신하는지 쉽게 추적할 수 있습니다.

좋은 Log는 다음 정보를 같이 남깁니다.

Timestamp

TX / RX

Device Address

Function Code

Raw HEX

CRC Result

Parsed Data

예:

10:15:20.125 TX
ADDR=11
FC=03
RAW=11 03 00 00 00 02 ...

10:15:20.143 RX
ADDR=11
FC=03
CRC=OK
VALUES=25,100

17장 패킷 분석에서는 요청과 응답을 한 쌍으로 본다#

Request:

11 03 00 00 00 02 CRC

Response:

11 03 04 00 19 00 64 CRC

두 Frame을 함께 보면:

Device
11

Function
03

Requested Quantity
2

Returned Byte Count
4

가 일치하는지 확인할 수 있습니다.

단독 Response만 보는 것보다 Request와 Response를 연결해서 보는 것이 훨씬 정확합니다.


18장 Modbus RTU 장애 진단 순서#

현장에서 응답이 이상하다면 다음 순서로 확인하면 좋습니다.

1. 장비 전원이 켜져 있는가?
↓
2. RS-485 배선이 맞는가?
↓
3. Baud·Parity·Stop Bit가 같은가?
↓
4. Device Address가 맞는가?
↓
5. Request Frame CRC가 정상인가?
↓
6. Function Code를 장비가 지원하는가?
↓
7. Starting Address가 맞는가?
↓
8. Quantity가 유효한가?
↓
9. Exception Response가 있는가?
↓
10. Response CRC가 정상인가?
↓
11. Register 값을 올바른 Data Type으로 변환했는가?

이렇게 보면 Modbus가 안 된다는 막연한 장애를 여러 단계로 분해할 수 있습니다.


19장 증상별로 어디를 먼저 볼까#

증상 우선 확인
응답 자체가 없음 전원·Address·Serial 설정·배선
모든 CRC가 실패 Serial 설정·Frame Parsing·CRC 계산
간헐적 CRC 실패 Noise·Cable·Collision·Termination
Exception 01 지원 Function Code
Exception 02 Register Address·범위
Exception 03 Quantity·Request 값
응답은 정상인데 값 이상 Data Type·Scale·Endian
특정 장비만 응답 없음 Address·배선·장비 전원
여러 장비가 동시에 이상 Bus·Termination·Master 설정
값이 한 Register씩 밀림 0-based·1-based Address

20장 Modbus RTU에서 자주 하는 오해#

Slave Address만 맞으면 통신된다#

아닙니다.

Serial Parameter와 Function Code, CRC 등도 모두 맞아야 합니다.

CRC는 인증 역할을 한다#

아닙니다.

CRC는 우발적인 데이터 손상을 검출하기 위한 값이지 송신자를 인증하는 보안 기술이 아닙니다.

CRC가 틀리면 장비가 NACK를 보낸다#

반드시 그렇지 않습니다.

잘못된 CRC Frame은 응답 없이 폐기될 수 있습니다.

Function Code 03이면 항상 같은 값이 나온다#

아닙니다.

어느 Holding Register를 읽느냐에 따라 결과가 다릅니다.

Exception Response는 통신 실패다#

오히려 장비가 Request를 수신하고 해석한 뒤 오류를 알려준 것이므로 Physical 통신 자체는 성공했을 가능성이 높습니다.

Register 값이 정상 범위면 해석도 맞다#

Scale이나 Data Type이 잘못되어도 우연히 그럴듯한 숫자가 나올 수 있습니다.


21장 현장 점검 체크리스트#

□ RTU 통신이 맞는가?

□ RS-485인지 다른 Serial Interface인지 확인했는가?

□ Device Address가 맞는가?

□ Address 중복이 없는가?

□ Baud Rate가 동일한가?

□ Data Bits가 동일한가?

□ Parity가 동일한가?

□ Stop Bits가 동일한가?

□ Function Code가 맞는가?

□ Starting Address가 맞는가?

□ 0-based·1-based 문제를 확인했는가?

□ Quantity가 허용 범위인가?

□ CRC가 정상인가?

□ Response Function Code를 확인했는가?

□ Exception Response 여부를 확인했는가?

□ Data Type을 확인했는가?

□ Scale을 확인했는가?

□ Byte·Word Order를 확인했는가?

□ Timestamp와 TX/RX 방향을 기록했는가?

22장 자기 점검#

Slave Address는 무엇을 위한 것인가#

하나의 Serial Bus에 연결된 여러 Modbus 장비 중 어떤 Device를 대상으로 하는 Request인지 식별하기 위해 사용합니다.

Function Code 03은 무엇인가#

Holding Register를 읽는 Read Holding Registers입니다.

CRC는 무엇을 확인하는가#

Modbus RTU Frame이 전송 과정에서 손상되었는지 검출하는 데 사용합니다.

Response의 Function Code가 0x83이라면 무엇인가#

FC03 Request에 대한 Exception Response입니다. 다음 Byte의 Exception Code를 확인해야 합니다.

응답은 정상인데 Register 값이 이상하다면 어디를 봐야 하는가#

Register Address, Data Type, Scale, Byte Order와 Word Order를 확인해야 합니다.

23장 이 글을 마치며#

Modbus RTU Packet을 처음 보면:

11 03 00 00 00 02 C6 9B

같은 HEX 숫자의 나열일 뿐입니다.

하지만 다음 순서로 분해하면 의미가 보입니다.

Slave Address
↓
Function Code
↓
Starting Address
↓
Quantity 또는 Data
↓
CRC

Response 역시:

Slave Address
↓
Function Code
↓
Byte Count
↓
Register Data
↓
CRC

처럼 읽을 수 있습니다.

그리고 문제가 있다면:

Function Code + 0x80
↓
Exception Code

형태의 Response를 찾아볼 수 있습니다.

특히 다음 다섯 가지를 기억하면 됩니다.

Modbus RTU Packet은 Address·Function·Data·CRC 구조로 이해할 수 있습니다.

Function Code를 먼저 확인해야 그 뒤의 Data Field를 올바르게 해석할 수 있습니다.

Request의 Quantity와 Response의 Byte Count를 비교하면 Frame 구조가 정상인지 확인하는 데 도움이 됩니다.

CRC 오류는 Protocol 구현 문제뿐 아니라 RS-485 배선·Noise·Serial 설정 문제에서 발생할 수도 있습니다.

Exception Response가 있다면 단순 Timeout과 구분하고 Exception Code를 이용해 Address·Function·Data 문제를 좁혀야 합니다.

결국 Modbus RTU 패킷 분석의 핵심은 HEX 값을 암기하는 것이 아닙니다.

한 줄의 Raw Frame을 Address·Function·Data·CRC로 분리하고, Function Code에 맞는 구조로 다시 해석한 뒤 제조사 Register Map과 연결해 실제 장비가 무엇을 요청하고 응답했는지 복원하는 것이 핵심입니다.

이 페이지의 목차