Modbus 함수 코드 01~16 정리, 프레임 예시와 예외 코드
요약
Modbus 함수 코드(Function Code)는 마스터가 슬레이브에게 "무엇을 할지"를 알려 주는 1바이트 명령 번호입니다.
같은 레지스터라도 어떤 함수 코드로 요청하느냐에 따라 읽기·쓰기 동작과 응답 모양이 달라지므로, 통신이 안 될 때 가장 먼저 확인할 항목입니다.
이 글은 현장에서 주로 쓰는 FC01·02·03·04·05·06·15·16의 요청·응답 프레임을 16진수 예시로 풀고, 예외 응답(함수 코드 + 0x80)과 예외 코드 01~04의 의미를 표로 정리합니다.
FC03은 한 번에 최대 125개, FC16은 최대 123개 레지스터까지 다룰 수 있다는 점도 계산으로 확인합니다.
센서나 PLC를 Modbus로 연결했는데 값이 안 들어오는 경우, 원인을 배선이나 통신 속도에서만 찾다가 시간을 오래 쓰는 일이 많습니다. 실제로는 장비가 Holding Register(FC03)에 값을 두었는데 프로그램은 Input Register(FC04)로 묻고 있거나, 장비가 지원하지 않는 함수 코드로 요청하고 있는 경우도 자주 나옵니다.
이럴 때 통신 분석 프로그램으로 주고받은 바이트를 열어 보면 원인이 바로 보입니다. 응답 두 번째 바이트가 0x83이고 그 뒤가 0x02라면 "FC03 요청에 잘못된 주소 예외"라는 뜻입니다. 이 글은 이런 16진수 프레임을 직접 읽을 수 있도록 함수 코드별 구조를 예시와 함께 정리합니다.
1. Modbus 함수 코드(Function Code)란?
Modbus 함수 코드는 요청 프레임에서 슬레이브 주소 다음에 오는 1바이트 값으로, 어떤 데이터 영역을 읽을지 쓸지를 정합니다. Modbus 규격(Modbus Application Protocol Specification V1.1b3)은 1~127 범위를 함수 코드로 쓰고, 128 이상은 예외 응답 표시용으로 남겨 둡니다.
Modbus 데이터는 코일(Coil), 접점 입력(Discrete Input), 입력 레지스터(Input Register), 보유 레지스터(Holding Register) 네 영역으로 나뉩니다. 함수 코드는 이 네 영역 가운데 하나를 골라 읽거나 쓰는 명령입니다. 영역별 주소 붙이는 방식(00001·10001·30001·40001 표기)은 Modbus 주소 체계 (Coil / Holding Register / Input Register) 글에 따로 정리되어 있습니다.
2. Modbus 프레임 구조는 어떻게 생겼나?
Modbus 메시지는 통신 방식과 상관없이 같은 PDU(Protocol Data Unit, 함수 코드 + 데이터)를 쓰고, 그 앞뒤에 붙는 부분만 RTU와 TCP가 다릅니다. PDU는 최대 253바이트이고, RS-485 같은 시리얼 회선의 전체 프레임(ADU)은 최대 256바이트, TCP는 최대 260바이트입니다.
2-1. Modbus RTU 프레임
Modbus RTU 프레임은 [슬레이브 주소 1바이트] + [함수 코드 1바이트] + [데이터] + [CRC 2바이트]로 이루어집니다. CRC-16은 오류 검사용 값이며 하위 바이트를 먼저 보냅니다. 반면 주소·개수·레지스터 값 같은 데이터는 상위 바이트를 먼저 보냅니다.
2-2. Modbus TCP 프레임
Modbus TCP는 CRC 대신 7바이트 MBAP 헤더를 앞에 붙이고, 기본 포트 502를 씁니다. MBAP 헤더는 트랜잭션 ID 2바이트, 프로토콜 ID 2바이트(항상 0), 길이 2바이트(뒤에 남은 바이트 수), 유닛 ID 1바이트입니다. RTU와 TCP의 차이는 RS485 / Modbus RTU / Modbus TCP 차이와 비교 분석 글에서 더 자세히 볼 수 있습니다.
| 구분 | Modbus RTU | Modbus TCP |
|---|---|---|
| 앞부분 | 슬레이브 주소 1바이트 | MBAP 헤더 7바이트 (마지막 1바이트가 유닛 ID) |
| 가운데 | PDU (함수 코드 + 데이터) | PDU (함수 코드 + 데이터) |
| 오류 검사 | CRC-16 2바이트 (하위 바이트 먼저) | 없음 (TCP 자체 검사에 맡김) |
| 최대 길이 | 256바이트 | 260바이트 |
| FC03 요청 예 | 11 03 00 6B 00 03 76 87 | 00 01 00 00 00 06 11 03 00 6B 00 03 |
3. Modbus 함수 코드 01~16 한눈에 보기
Modbus 장비 대부분은 아래 8개 함수 코드만으로 운영됩니다. 읽기는 영역마다 함수 코드가 하나씩 있고, 쓰기는 코일과 보유 레지스터에만 있습니다. 접점 입력과 입력 레지스터는 장비가 측정한 값이라 외부에서 쓸 수 없습니다.
| 함수 코드 | 이름 | 대상 영역 | 한 번에 다룰 수 있는 개수 |
|---|---|---|---|
| 01 (0x01) | Read Coils | 코일 (1비트, 읽기·쓰기) | 1~2000개 |
| 02 (0x02) | Read Discrete Inputs | 접점 입력 (1비트, 읽기 전용) | 1~2000개 |
| 03 (0x03) | Read Holding Registers | 보유 레지스터 (16비트, 읽기·쓰기) | 1~125개 |
| 04 (0x04) | Read Input Registers | 입력 레지스터 (16비트, 읽기 전용) | 1~125개 |
| 05 (0x05) | Write Single Coil | 코일 1개 | 1개 (ON=FF00, OFF=0000) |
| 06 (0x06) | Write Single Register | 보유 레지스터 1개 | 1개 |
| 15 (0x0F) | Write Multiple Coils | 연속된 코일 | 1~1968개 |
| 16 (0x10) | Write Multiple Registers | 연속된 보유 레지스터 | 1~123개 |
이 밖에 07(Read Exception Status)·08(Diagnostics)·11·12(통신 이벤트 카운터·로그)·17(Report Server ID)은 시리얼 회선 전용이고, 22(Mask Write Register)·23(Read/Write Multiple Registers)·43(장비 식별 정보 읽기)은 일부 장비만 지원합니다. 10진수 15·16을 16진수로 쓰면 0x0F·0x10이므로, 프레임에서 "10"이 보이면 FC16이라는 점에 주의합니다.
4. 읽기 함수 FC01·02·03·04 프레임 예시
아래 예시는 모두 슬레이브 주소 0x11(17번) 장비와 주고받는 Modbus RTU 프레임입니다. 읽기 요청은 네 함수 코드 모두 [주소][함수 코드][시작 주소 2바이트][개수 2바이트][CRC] 8바이트로 모양이 같고, 응답은 [주소][함수 코드][바이트 수][데이터][CRC]입니다.
| 함수 코드 | 요청 (16진수) | 응답 (16진수) | 읽는 법 |
|---|---|---|---|
| FC01 | 11 01 00 13 00 25 0E 84 | 11 01 05 CD 6B B2 0E 1B 45 E6 | 0x0013(19)번부터 코일 37개(0x25). 37비트를 담으려면 5바이트 필요 |
| FC02 | 11 02 00 C4 00 16 BA A9 | 11 02 03 AC DB 35 20 18 | 0x00C4(196)번부터 접점 입력 22개(0x16). 3바이트, 마지막 바이트 위 2비트는 0으로 채움 |
| FC03 | 11 03 00 6B 00 03 76 87 | 11 03 06 AE 41 56 52 43 40 49 AD | 0x006B(107)번부터 보유 레지스터 3개. 레지스터 1개 = 2바이트이므로 바이트 수 06 |
| FC04 | 11 04 00 08 00 01 B2 98 | 11 04 02 00 0A F8 F4 | 0x0008(8)번 입력 레지스터 1개. 값 0x000A = 10 |
4-1. 비트 데이터(FC01·FC02) 읽는 순서
FC01·FC02 응답의 각 데이터 바이트는 가장 낮은 비트(bit 0)가 가장 앞 번호입니다. FC01 예시 첫 바이트 0xCD는 2진수로 1100 1101이므로 bit 0부터 읽으면 1, 0, 1, 1, 0, 0, 1, 1입니다. 즉 요청한 첫 코일은 ON, 둘째는 OFF, 셋째·넷째는 ON입니다. 사람이 왼쪽부터 읽으면 순서가 뒤집히므로 해석 오류가 자주 생기는 부분입니다.
4-2. 레지스터 데이터(FC03·FC04) 읽는 순서
FC03·FC04 응답의 레지스터 값은 상위 바이트가 먼저 옵니다. FC03 예시의 첫 레지스터 AE 41은 0xAE41 = 44609입니다. 같은 값이라도 장비가 부호 있는 정수(int16)로 정의했다면 44609 − 65536 = −20927로 해석해야 하므로, 데이터 형식은 반드시 장비 레지스터 맵에서 확인합니다.
5. 쓰기 함수 FC05·06·15·16 프레임 예시
Modbus 쓰기 함수는 성공하면 슬레이브가 요청 내용을 되돌려 보내 확인해 줍니다. FC05·FC06은 요청 전체를 그대로 돌려주고(에코), FC15·FC16은 시작 주소와 개수만 돌려줍니다.
| 함수 코드 | 요청 (16진수) | 응답 (16진수) | 읽는 법 |
|---|---|---|---|
| FC05 | 11 05 00 AC FF 00 4E 8B | 11 05 00 AC FF 00 4E 8B | 0x00AC(172)번 코일을 ON(FF00). OFF는 00 00 |
| FC06 | 11 06 00 01 00 03 9A 9B | 11 06 00 01 00 03 9A 9B | 0x0001번 보유 레지스터에 값 3 쓰기 |
| FC15 | 11 0F 00 13 00 0A 02 CD 01 BF 0B | 11 0F 00 13 00 0A 26 99 | 0x0013번부터 코일 10개, 바이트 수 02, 데이터 CD 01 |
| FC16 | 11 10 00 01 00 02 04 00 0A 01 02 C6 F0 | 11 10 00 01 00 02 12 98 | 0x0001번부터 레지스터 2개에 10(000A)과 258(0102) 쓰기 |
FC05에서 ON·OFF 값은 FF00과 0000 두 가지만 유효하며, 0001 같은 다른 값을 보내면 장비가 예외 코드 03(잘못된 데이터 값)으로 응답합니다. FC06과 FC16은 결과가 같아 보여도 장비에 따라 FC06만 받거나 FC16만 받는 경우가 있으므로, 설정값을 쓰기 전에 장비 설명서에서 지원 함수 코드를 확인합니다.
6. Modbus 예외 응답과 예외 코드 01~04 의미는?
Modbus 예외 응답은 슬레이브가 요청을 받았지만 처리할 수 없을 때 보내는 응답으로, 함수 코드에 0x80을 더한 값과 예외 코드 1바이트로 이루어집니다. 예를 들어 FC01 요청이 실패하면 0x81, FC03이면 0x83, FC16이면 0x90이 돌아옵니다.
예시 프레임 0A 81 02 B0 53은 "10번(0x0A) 장비가 FC01 요청에 예외 코드 02(잘못된 데이터 주소)로 답했다"는 뜻입니다. 예외 응답이 왔다는 것은 배선·통신 속도·CRC는 정상이라는 의미이기도 합니다.
| 예외 코드 | 이름 | 의미 | 현장에서 흔한 원인 |
|---|---|---|---|
| 01 | Illegal Function | 장비가 지원하지 않는 함수 코드 | FC03 장비에 FC04로 요청, FC16 미지원 장비에 다중 쓰기 |
| 02 | Illegal Data Address | 없는 주소이거나 주소+개수가 범위를 넘음 | 40001을 0번이 아닌 1번으로 보냄(1 차이), 읽는 개수를 너무 많이 지정 |
| 03 | Illegal Data Value | 요청 데이터 값이 허용되지 않음 | FC05에 FF00·0000 외 값, 개수 0 또는 최대치 초과, 설정 범위 밖 값 |
| 04 | Server(Slave) Device Failure | 요청 처리 중 복구할 수 없는 오류 | 장비 내부 센서 오류, 게이트웨이 뒤쪽 장비 이상 |
| 05 / 06 | Acknowledge / Device Busy | 처리 중이거나 바쁨, 나중에 다시 요청 | 긴 설정 저장 작업 중 폴링 |
| 0A / 0B | Gateway Path Unavailable / Target Failed to Respond | 게이트웨이 경로 없음 / 뒤쪽 장비 무응답 | TCP-RTU 변환기의 유닛 ID 설정 오류, RS-485 쪽 장비 전원·배선 문제 |
7. 한 번에 몇 개까지 읽을 수 있을까? (프레임 길이 계산)
Modbus 함수 코드별 최대 개수는 RTU 프레임 최대 길이 256바이트에서 나옵니다. FC03 응답 길이는 "주소 1 + 함수 코드 1 + 바이트 수 1 + 데이터(2 × 레지스터 수) + CRC 2"입니다.
- FC03 3개 읽기 : 1 + 1 + 1 + (2 × 3) + 2 = 11바이트 — 위 예시 응답 11 03 06 … 49 AD의 길이와 같습니다.
- FC03 125개 읽기 : 1 + 1 + 1 + 250 + 2 = 255바이트 — 256바이트 안에 들어가는 최대치입니다. 126개면 257바이트가 되어 넘칩니다.
- FC16 123개 쓰기 : 요청이 주소 1 + 함수 코드 1 + 시작 주소 2 + 개수 2 + 바이트 수 1 + 데이터 246 + CRC 2 = 255바이트입니다. 요청에 시작 주소·개수 4바이트가 더 붙기 때문에 FC03보다 2개 적습니다.
- FC01 2000개 읽기 : 2000 ÷ 8 = 250바이트 데이터로 FC03 125개와 같은 크기입니다.
값 환산도 함께 확인해 둡니다. 온도 센서가 0.1℃ 단위 정수로 값을 준다면, FC16 예시의 01 02(= 258)는 25.8℃가 됩니다. 배율(×0.1, ×0.01)과 부호 여부, 32비트 값의 워드 순서는 장비마다 다르므로 레지스터 맵을 기준으로 설정합니다.
8. 현장에서 자주 생기는 문제와 점검 순서
Modbus 통신 문제는 "응답이 아예 없는 경우"와 "예외 응답이 오는 경우"를 먼저 나누면 원인을 빠르게 좁힐 수 있습니다. 응답이 없으면 물리 계층과 통신 설정 문제이고, 예외 응답이 오면 함수 코드·주소·값 문제입니다.
- 응답 없음 : 슬레이브 주소, 통신 속도·패리티·정지 비트, RS-485 A/B 극성과 종단 저항을 확인합니다. 배선 점검은 RS-485 직렬 통신 규격 글을 참고합니다.
- CRC 오류 : 노이즈나 속도 불일치가 원인인 경우가 많습니다. 프레임 끝 CRC는 하위 바이트가 먼저인지 확인합니다.
- 예외 01 : 장비 설명서에서 그 값이 Holding Register(FC03)인지 Input Register(FC04)인지 다시 확인합니다.
- 예외 02 : 레지스터 맵의 40001 표기를 프레임 주소 0x0000으로 바꿨는지, 1 차이가 나지 않는지 확인합니다. 한 번에 읽는 범위 중간에 비어 있는 주소가 있어도 02가 나올 수 있어 범위를 나눠 읽어 봅니다.
- Modbus TCP 게이트웨이 : 예외 0A·0B나 무응답이면 MBAP 헤더의 유닛 ID가 RS-485 쪽 장비 주소와 맞는지 확인합니다.
모니터링 시스템에서는 폴링 주기마다 실패를 그대로 알람으로 올리면 일시적인 노이즈에도 알람이 쏟아집니다. 일반적으로 응답 대기 시간(타임아웃)과 재시도 횟수를 정해 두고, 연속 실패가 몇 번 이어질 때 "통신 단절" 알람을 내도록 설계합니다. 또한 쓰기 함수(FC05·06·15·16)는 장비 설정을 바꾸므로 모니터링 전용 계정에서는 막아 두는 편이 안전합니다.
9. 관련 산업용어
■ PDU (Protocol Data Unit)
함수 코드와 데이터를 합친 부분으로, RTU와 TCP에서 똑같이 쓰이며 최대 253바이트입니다.
■ ADU (Application Data Unit)
PDU에 주소·CRC(RTU) 또는 MBAP 헤더(TCP)를 붙인 전체 프레임입니다.
■ MBAP 헤더
Modbus TCP 프레임 앞 7바이트로, 트랜잭션 ID·프로토콜 ID·길이·유닛 ID를 담습니다.
■ CRC-16
Modbus RTU 프레임 끝 2바이트 오류 검사값입니다. 받은 쪽이 다시 계산해 값이 다르면 그 프레임을 버립니다.
■ 폴링 (Polling)
마스터가 정해진 주기로 각 슬레이브에 차례로 요청을 보내 값을 가져오는 방식입니다. 동작 방식은 Modbus TCP 산업용 통신 프로토콜 글에도 정리되어 있습니다.
10. 자주 묻는 질문
Q. Holding Register는 몇 번 함수 코드로 읽나요?
FC03(Read Holding Registers)으로 읽습니다. 값을 쓸 때는 1개는 FC06, 여러 개는 FC16을 씁니다.
Q. FC03과 FC04 차이는?
FC03은 읽기·쓰기가 가능한 보유 레지스터를, FC04는 읽기 전용 입력 레지스터를 읽습니다. 측정값을 어느 영역에 둘지는 제조사가 정하므로 장비 레지스터 맵을 따라야 합니다.
Q. Modbus 응답에 0x83이 오면 무슨 뜻인가요?
FC03 요청이 실패했다는 예외 응답입니다. 0x83 = 0x03 + 0x80이며, 바로 뒤 바이트가 01·02·03·04 중 어떤 예외인지 알려 줍니다.
Q. Modbus 예외 코드 02 Illegal Data Address 해결 방법은?
대부분 주소가 1 차이 나거나 읽는 개수가 장비 범위를 넘은 경우입니다. 40001 표기를 프레임 주소 0으로 바꿨는지 확인하고, 1개만 읽어 보면서 범위를 좁힙니다.
Q. FC03으로 한 번에 몇 개까지 읽을 수 있나요?
최대 125개입니다. RTU 프레임 최대 256바이트 안에 데이터 250바이트가 들어가는 한계이며, 장비에 따라 이보다 적게 제한하기도 합니다.
Q. FC05에서 코일 ON 값은?
FF 00입니다. OFF는 00 00이며, 이 두 값 외에는 예외 코드 03으로 거부됩니다.
11. 마무리
Modbus 함수 코드는 데이터 영역과 동작을 정하는 1바이트 명령이며, 현장에서는 FC01·02·03·04(읽기)와 FC05·06·15·16(쓰기) 8개면 대부분 해결됩니다. 프레임은 [주소][함수 코드][데이터][CRC] 구조이고, 데이터는 상위 바이트 먼저, CRC는 하위 바이트 먼저 보냅니다. 응답 함수 코드가 0x80 이상이면 예외 응답이며, 그 뒤 바이트(01 함수 미지원·02 주소 오류·03 값 오류·04 장비 오류)가 원인을 알려 줍니다. 통신 문제는 무응답과 예외 응답을 먼저 나누고, 예외라면 함수 코드와 주소 오프셋부터 확인하는 것이 가장 빠릅니다.
참고 기준 : Modbus Organization, Modbus Application Protocol Specification V1.1b3 (2012), Modbus Messaging on TCP/IP Implementation Guide V1.0b, Modbus over Serial Line Specification and Implementation Guide V1.02
태그 : Modbus, 함수코드, FunctionCode, HoldingRegister, InputRegister, 코일, FC03, FC04, FC06, FC16, 예외코드, IllegalDataAddress, ModbusRTU, ModbusTCP, CRC16, RS485
- 다음글MQTT란? 브로커·토픽·QoS 쉽게 이해하기 26.10.21