1979년 Modicon이 만든 산업용 마스터-슬레이브 프로토콜. 단순함 덕분에 40년 넘게 현역이다.
변형
| 물리 계층 | 특징 | |
|---|---|---|
| Modbus RTU | RS-485/232 | 이진 형식. 가장 널리 쓰임 |
| Modbus ASCII | RS-485/232 | 텍스트 형식. 사람이 읽기 쉽지만 2배 크기 |
| Modbus TCP | 이더넷 | 포트 502. CRC 대신 TCP가 보장 |
데이터 모델 — 네 종류
읽기 쓰기 크기
Coil ○ ○ 1비트 (출력 릴레이)
Discrete Input ○ ✗ 1비트 (입력 센서)
Input Register ○ ✗ 16비트 (아날로그 입력)
Holding Register ○ ○ 16비트 (설정값, 범용) ← 가장 많이 사용
모든 값이 1비트 또는 16비트다. 32비트 값이나 float는 레지스터 2개를 이어 붙여 표현하며, 이 순서가 표준에 없다.
float 3.14 를 2개 레지스터로 → 어느 워드가 먼저인가? big-endian / little-endian / word-swapped 조합으로 4가지
장비마다 다르다. "값이 이상하게 나온다"면 워드 순서를 바꿔 본다. 실무에서 가장 자주 겪는 Modbus 문제다.
주요 기능 코드
0x01 Read Coils 0x02 Read Discrete Inputs
- 0x03 Read Holding Registers — ← 가장 많이 쓴다 0x04 Read Input Registers 0x05 Write Single Coil 0x06 Write Single Register 0x10 Write Multiple Registers
RTU 프레임
[슬레이브주소 1B][기능코드 1B][데이터 n B][CRC16 2B]
- 프레임 구분은 침묵 시간으로 한다 ★
- 3.5 문자 시간 이상 조용하면 프레임 끝
★ 시간 기반 프레이밍이 RTU의 특징이자 구현 난점이다.
9600bps 에서 1문자 = 약 1.04ms 3.5문자 = 약 3.65ms 동안 조용하면 프레임 종료
구현 방법
// UART IDLE 인터럽트 + DMA 가 가장 깔끔하다
HAL_UARTEx_ReceiveToIdle_DMA(&huart, buf, SIZE);
// 또는 타이머로 3.5문자 시간을 측정
바이트마다 인터럽트를 받아 타이머를 재시작하는 방식은 고속에서 CPU 부하가 크다. IDLE + DMA가 실무 표준이다.
주소 표기의 혼란 — 매우 흔한 함정
전통적 표기(1 기반) 프로토콜 주소(0 기반)
40001 = Holding Register → 주소 0
40002 → 주소 1
30001 = Input Register → 주소 0
장비 매뉴얼이 40001이라고 쓰면 실제로는 주소 0을 요청해야 한다. "1 차이로 값이 안 맞는다"면 이것이다.
매뉴얼마다 0 기반인지 1 기반인지 다르게 표기하므로, 실제로 읽어 보고 확인하는 것이 확실하다.
예외 응답
정상 응답 : 기능 코드 그대로 (예: 0x03) 예외 응답 : 기능 코드 + 0x80 (예: 0x83) + 예외 코드
- 0x01 Illegal Function — — 지원하지 않는 기능
- 0x02 Illegal Data Address — — 없는 주소 ← 가장 흔하다
- 0x03 Illegal Data Value — — 값 범위 초과
- 0x06 Slave Device Busy — — 나중에 재시도
최상위 비트가 켜져 있으면 예외 응답이다. 이것을 확인하지 않고 데이터로 파싱하면 엉뚱한 값을 읽는다.
한계 — 알고 쓰면 문제없다
❌ 보안 없음 — 인증·암호화가 전혀 없다. 폐쇄망 전제 ❌ 실시간성 없음 — 마스터 폴링 방식. 이벤트 통지 불가 ❌ 주소 공간 제한 — 슬레이브당 65536 레지스터 ❌ 단일 마스터 — 마스터가 죽으면 통신 정지 ❌ 데이터 타입 없음 — 16비트 워드가 전부. 의미는 문서로 약속
"단순해서 살아남았다" 는 것이 Modbus의 성격이다. 기능이 부족한 것이 아니라, 부족함이 곧 이식성과 상호 운용성을 만들었다.
실무 팁
✅ 폴링 주기를 슬레이브 응답 시간보다 여유 있게 ✅ 타임아웃과 재시도 횟수를 설정 (보통 3회) ✅ 연속된 레지스터는 한 번에 읽는다 (0x03으로 여러 개)
- 개별 요청보다 훨씬 빠르다 ✅ 통신 오류 카운터를 두어 회선 품질을 모니터링 ✅ 브로드캐스트(주소 0)는 응답이 없다 — 확인이 불가능