PDU, ADU와 기능 코드
Modbus 메시지는 두 겹입니다. 안쪽의 PDU(Protocol Data Unit)는 전송 방식과 무관하고, 바깥쪽의 ADU(Application Data Unit)는 전송 방식마다 다릅니다. 이 페이지는 MODBUS Application Protocol Specification V1.1b3의 §4.1 프로토콜 설명, §5 기능 코드 분류, §6 기능 코드 설명, §7 예외 응답을 근거로 합니다.
PDU
섹션 제목: “PDU”PDU는 기능 코드 1바이트와 데이터로 이뤄집니다. 최대 길이는 253바이트입니다.
+---------------+----------------------------+| 기능 코드 1B | 데이터 0~252B |+---------------+----------------------------+보유 레지스터 0을 하나 읽는 요청과 응답은 다음과 같습니다.
요청 PDU 03 0000 0001 기능 03, 시작 주소 0, 수량 1응답 PDU 03 02 002A 기능 03, 바이트 수 2, 값 0x002A(42)보유 레지스터 0에 42를 쓰는 요청은 06 0000 002A이고, 정상 응답은 요청과 같은 5바이트입니다.
PDComm은 PDU를 16진수 문자열("0300000001")이나 바이트 배열([3,0,0,0,1])로 받습니다. 응답은 10진수 바이트 배열([3,2,0,42])로 돌려줍니다. MBAP 헤더, 유닛 주소, CRC·LRC는 PDU에 넣지 않습니다. 전송 계층이 붙이고 뗍니다.
ADU
섹션 제목: “ADU”ADU는 PDU 앞뒤에 주소와 오류 검사를 붙인 것입니다. 같은 읽기 요청이 전송 방식마다 다음처럼 보입니다.
| 전송 | ADU 구조 | 최대 길이 | 같은 요청의 바이트 |
|---|---|---|---|
| Modbus TCP | MBAP 헤더 7B + PDU | 260B | 00 01 00 00 00 06 01 03 00 00 00 01 |
| RTU | 유닛 주소 1B + PDU + CRC 2B | 256B | 01 03 00 00 00 01 84 0A |
| ASCII | : + 16진 문자(주소, PDU, LRC) + CR LF |
513자 | :010300000001FB + CR LF |
- TCP의 MBAP 헤더는 Modbus TCP에서 설명합니다.
- RTU의 CRC와 ASCII의 LRC는 직렬 회선: RTU와 ASCII에서 설명합니다.
기능 코드의 분류
섹션 제목: “기능 코드의 분류”명세 §5는 기능 코드를 세 가지로 나눕니다.
| 분류 | 범위 | 설명 |
|---|---|---|
| 공개(public) | 1 |
Modbus Organization이 정의하고 검증한 코드입니다. |
| 사용자 정의(user-defined) | 65 |
제조사가 자유롭게 정의합니다. 다른 제조사 장비와 호환을 보장하지 않습니다. |
| 예약(reserved) | 그 밖의 일부 코드 | 일부 회사의 기존 제품이 사용하므로 새로 쓰지 않습니다. |
128 이상은 예외 응답용입니다. 요청의 기능 코드에 0x80을 더한 값이 예외 응답의 기능 코드입니다.
PDComm이 지원하는 기능 코드
섹션 제목: “PDComm이 지원하는 기능 코드”PDComm은 다음 19개 공개 기능 코드를 지원합니다. capabilities 명령의 functions 필드도 같은 목록을 돌려줍니다.
| 코드(10진/16진) | 이름 | 명세 | 요청 PDU 예 | PDComm 제한 |
|---|---|---|---|---|
| 1 / 0x01 | Read Coils | §6.1 | 0100000008 |
수량 1~2000 |
| 2 / 0x02 | Read Discrete Inputs | §6.2 | 0200000008 |
수량 1~2000 |
| 3 / 0x03 | Read Holding Registers | §6.3 | 0300000001 |
수량 1~125 |
| 4 / 0x04 | Read Input Registers | §6.4 | 0400000001 |
수량 1~125 |
| 5 / 0x05 | Write Single Coil | §6.5 | 050000FF00 |
값은 FF00(켜짐) 또는 0000(꺼짐) |
| 6 / 0x06 | Write Single Register | §6.6 | 060000002A |
|
| 7 / 0x07 | Read Exception Status | §6.7 | 07 |
직렬 전용 |
| 8 / 0x08 | Diagnostics | §6.8 | 0800001234 |
직렬 전용, 아래 표의 하위 기능 |
| 11 / 0x0B | Get Comm Event Counter | §6.9 | 0B |
직렬 전용 |
| 12 / 0x0C | Get Comm Event Log | §6.10 | 0C |
직렬 전용, 최근 이벤트 64개 |
| 15 / 0x0F | Write Multiple Coils | §6.11 | 0F0000000801A5 |
수량 1~1968 |
| 16 / 0x10 | Write Multiple Registers | §6.12 | 1000000001020009 |
수량 1~123 |
| 17 / 0x11 | Report Server ID | §6.13 | 11 |
직렬 전용 |
| 20 / 0x14 | Read File Record | §6.14 | 140706000100000001 |
파일 번호 1 이상, 레코드 0~9999 |
| 21 / 0x15 | Write File Record | §6.15 | 1509060001000000010009 |
모든 레코드 범위를 먼저 검사한 뒤 씁니다. |
| 22 / 0x16 | Mask Write Register | §6.16 | 160000FF000055 |
결과 = (현재값 AND And_Mask) OR (Or_Mask AND NOT And_Mask) |
| 23 / 0x17 | Read/Write Multiple Registers | §6.17 | 170000000100000001020009 |
읽기 1 |
| 24 / 0x18 | Read FIFO Queue | §6.18 | 180000 |
큐 길이 최대 31, 읽어도 큐를 비우지 않습니다. |
| 43 / 0x2B | Read Device Identification (MEI 14) | §6.21 | 2B0E0100 |
읽기 코드 1(basic)~4(individual) |
- 직렬 전용 기능은 명세가 Serial Line 전용으로 정한 기능입니다. PDComm TCP 서버는 예외 01로 응답하고, PDComm TCP 클라이언트는 요청을 보내지 않고
Unsupported오류를 돌려줍니다. - MEI 13(CANopen General Reference, §6.20)은 내용을 해석하지 않는 확장 인터페이스입니다. 기본 서버는 예외 01로 응답합니다.
- 사용자 정의 코드도 기본 서버에서는 예외 01입니다.
진단(FC 08) 하위 기능
섹션 제목: “진단(FC 08) 하위 기능”진단 기능은 하위 기능 코드(sub-function) 2바이트와 데이터 2바이트를 보냅니다. PDComm은 다음 15개를 지원합니다.
| 하위 기능(10진/16진) | 이름 | 요청 PDU | 응답 |
|---|---|---|---|
| 0 / 0x0000 | Return Query Data | 0800001234 |
데이터를 그대로 돌려줍니다. |
| 1 / 0x0001 | Restart Communications Option | 0800010000 |
응답한 뒤 통신을 재시작하고 카운터를 지웁니다. 데이터 FF00이면 이벤트 로그도 지웁니다. |
| 2 / 0x0002 | Return Diagnostic Register | 0800020000 |
진단 레지스터 값 |
| 3 / 0x0003 | Change ASCII Input Delimiter | 0800030A00 |
응답한 뒤 ASCII 종료 문자를 바꿉니다. |
| 4 / 0x0004 | Force Listen Only Mode | 0800040000 |
응답하지 않고 청취 전용 모드로 들어갑니다. |
| 10 / 0x000A | Clear Counters and Diagnostic Register | 08000A0000 |
카운터와 진단 레지스터를 지웁니다. |
| 11 / 0x000B | Return Bus Message Count | 08000B0000 |
버스에서 본 메시지 수 |
| 12 / 0x000C | Return Bus Communication Error Count | 08000C0000 |
CRC·LRC 오류 수 |
| 13 / 0x000D | Return Bus Exception Error Count | 08000D0000 |
보낸 예외 응답 수 |
| 14 / 0x000E | Return Server Message Count | 08000E0000 |
이 서버 앞으로 온 메시지 수 |
| 15 / 0x000F | Return Server No Response Count | 08000F0000 |
응답하지 않은 메시지 수 |
| 16 / 0x0010 | Return Server NAK Count | 0800100000 |
NAK 수 |
| 17 / 0x0011 | Return Server Busy Count | 0800110000 |
Busy 예외 수 |
| 18 / 0x0012 | Return Bus Character Overrun Count | 0800120000 |
문자 오버런 수 |
| 20 / 0x0014 | Clear Overrun Counter and Flag | 0800140000 |
오버런 카운터와 플래그를 지웁니다. |
청취 전용 모드의 서버는 어떤 요청에도 응답하지 않습니다. 빠져나오는 방법은 하위 기능 1(Restart Communications Option)뿐이며, 이 요청에도 응답은 오지 않습니다.
예외 응답
섹션 제목: “예외 응답”서버가 요청을 처리할 수 없으면 예외 응답(exception response)을 보냅니다. 예외 응답 PDU는 2바이트입니다.
요청 PDU 03 FFFF 0001 주소 65535부터 1개 읽기예외 응답 83 02 0x03 + 0x80, 예외 코드 02명세 §7의 예외 코드는 다음과 같습니다.
| 코드 | 이름 | 의미 |
|---|---|---|
| 01 | Illegal Function | 서버가 이 기능 코드를 지원하지 않거나 지금 상태에서 허용하지 않습니다. |
| 02 | Illegal Data Address | 주소, 또는 주소와 수량의 조합이 서버에 없습니다. |
| 03 | Illegal Data Value | 요청 데이터의 값이나 길이가 올바르지 않습니다. |
| 04 | Server Device Failure | 요청을 처리하다 복구할 수 없는 오류가 났습니다. |
| 05 | Acknowledge | 오래 걸리는 요청을 받아들였습니다. 결과는 나중에 조회합니다. |
| 06 | Server Device Busy | 서버가 다른 일을 처리하고 있습니다. 나중에 다시 보냅니다. |
| 08 | Memory Parity Error | 파일 레코드 메모리에서 패리티 오류를 찾았습니다. |
| 0A | Gateway Path Unavailable | 게이트웨이에 해당 대상으로 가는 경로가 없습니다. |
| 0B | Gateway Target Device Failed to Respond | 게이트웨이가 대상 장비의 응답을 받지 못했습니다. |
예외 응답은 정상적인 Modbus 응답입니다. PDComm에서 예외 응답을 받은 request나 execute는 호출 자체가 성공하고, 결과의 status가 exception입니다. 반면 연결 실패, 시간 초과, 잘못된 JSON, 라이선스 없는 connect는 호출이 실패합니다. C API는 pdcomm_modbus_call에서 PDCOMM_MODBUS_SERVICE_ERROR를 돌려주고, C++과 .NET은 예외를 던집니다.
{"ok":true,"result":{"response":[131,2],"source":"local","status":"exception"}}PDComm 서버가 언제 어떤 예외를 보내는지는 예외 응답 문제 해결에 정리했습니다.
로컬 실행과 실제 통신
섹션 제목: “로컬 실행과 실제 통신”PDComm은 같은 PDU를 두 가지 방법으로 처리합니다. 결과의 source 필드로 구분합니다.
| 명령 | source |
처리 |
|---|---|---|
execute |
local |
연결 없이 엔드포인트 자신의 데이터 모델에서 실행합니다. 서버 동작을 확인하는 데 씁니다. |
request |
wire |
연결된 클라이언트가 실제로 상대에게 보내고 응답을 받습니다. |
local 결과는 통신 성공의 증거가 아닙니다. 실제 장비와 통신했는지는 wire 결과로 확인합니다.