← 전체 글
Modbus/약 13분 읽기/ 조회

Modbus 예외 코드: 장치는 응답했다, 다만 거절했을 뿐

Modbus 예외 응답을 프레임 단위로 읽고, 통신 불량과 요청 거절을 구분하며, 잘못된 주소·게이트웨이·폴링 부하 원인을 좁히는 방법.

ModbusSCADA문제 해결네트워킹체크리스트

"Modbus가 죽었다"는 티켓의 절반은 사실 죽은 게 아닙니다. 장치는 응답했습니다. 다만 안 됨이라고 응답했을 뿐입니다.

이 차이를 케이블에 손대기 전에 몸에 익혀 두는 게 좋습니다. 타임아웃은 침묵입니다. 요청은 나갔는데 쓸 수 있는 응답이 돌아오지 않은 상태죠. 예외 응답은 다릅니다. 슬레이브가 프레임을 받았고, 파싱했고, 의도적으로 거절한 겁니다. 드라이버 로그에 Illegal Data AddressGateway Target Device Failed to Respond가 찍혔다면 이미 물리 계층은 통과한 상태입니다. 그 시점에 종단 저항이나 RS-485 극성을 붙잡고 있으면 오후 하나를 버리게 됩니다.

예외 응답은 실제로 어떻게 생겼나

Modbus Application Protocol Specification(V1.1b3, 7절)은 이 부분을 분명하게 정의합니다. 정상 응답은 보낸 함수 코드를 그대로 돌려줍니다. 예외 응답은 그 함수 코드의 최상위 비트를 세우고, 뒤에 예외 코드 1바이트를 붙입니다.

그래서 함수 0x03(Read Holding Registers)을 보냈는데 장치가 거절하면 응답 함수 코드는 0x83으로 돌아옵니다. 0x03 | 0x80이죠. Read Coils 0x010x81로, Write Multiple Registers 0x100x90으로 실패합니다. 그 다음 바이트가 예외 코드입니다.

거절된 holding register 읽기는 RTU 선로에서 이렇게 보입니다.

요청:  01 03 00 13 00 0A C5 CD
응답:  01 83 02 C0 F1

01 unit id, 83 = 오류가 붙은 holding register 읽기, 02 = Illegal Data Address, 그 뒤는 CRC입니다. 이걸 눈으로 읽을 수 있게 되면 매뉴얼을 열기 전에 진단의 절반이 끝납니다. SCADA 드라이버가 텍스트 문구만 보여 주고 원본 프레임을 감춘다면 캡처를 뜨세요. Wireshark는 Modbus/TCP를 기본으로 디코딩하고, 웬만한 시리얼 드라이버에는 켤 수 있는 원본 통신 로그가 있습니다.

예외 코드, 그리고 어디까지 믿을지

  • 01 Illegal Function — 그 객체나 그 영역에서, 또는 아예 해당 함수 코드를 지원하지 않습니다.
  • 02 Illegal Data Address — 주소 또는 범위가 구현된 맵 밖입니다.
  • 03 Illegal Data Value — 값, 길이, 쓰기 페이로드가 조건에 맞지 않습니다. 이건 요청 구조에 대한 판단이지, 공정값이 범위를 벗어났다는 뜻이 아닙니다(그건 장치 내부에서, 그것도 걸러 준다면, 따로 판단합니다).
  • 04 Slave Device Failure — 프레임은 정상이었는데 처리하다 내부에서 실패했습니다.
  • 06 Slave Device Busy — 시간이 더 필요하거나 다른 작업 중입니다. 나중에 재시도.
  • 0A Gateway Path Unavailable — 게이트웨이가 하위 네트워크로 가는 경로를 만들지 못했습니다.
  • 0B Gateway Target Device Failed to Respond — 게이트웨이는 전달했는데 하위 장치가 침묵했습니다.

이 코드들은 느슨하게 믿으세요. 저가형 펌웨어는 잘못된 함수, 잘못된 길이, 잘못된 주소를 전부 02 하나로 돌려주는 경우가 흔합니다. 그러니 숫자 자체를 과하게 읽지 마세요. 코드가 돌아왔다는 사실을 읽으세요. 그 네트워크의 무언가가 응답했고, 여러분의 unit id를 처리해서 여러분과 의견이 갈릴 만큼은 봤다는 뜻입니다.

맵을 탓하기 전에 요청부터 확인한다

HMI 태그 이름이 아니라, 드라이버가 선로에 실제로 올린 프레임에서 시작하세요. 태그 이름 뒤에는 오프셋, 워드 순서, 함수 코드 선택이 묻혀 있습니다. 무엇보다 먼저 적으세요. 함수 코드, 실제로 나간 시작 주소, 개수, unit id, 그리고 그 요청이 속한 폴링 그룹. 다섯 가지면 됩니다. 이게 보이면 대부분의 문제가 떨어져 나옵니다.

고전적인 함정은 주소 표기입니다. 매뉴얼에는 40001로 찍혀 있는데, 실제 PDU는 프로토콜 주소 0을 나릅니다. 어떤 드라이버는 문서상 레지스터 번호를 넣게 하고 알아서 빼 주며, 어떤 드라이버는 0 기반 주소를 그대로 넣게 하고 아무것도 빼 주지 않습니다. 여기서 추측하면 몇 시간이 날아갑니다. 이미 정상으로 읽히는 레지스터 하나에 맞춰 보거나, 캡처를 읽으세요. 주소는 프레임 안에 그대로 들어 있습니다.

가장 흔한 02는 블록 최적화다

드라이버는 라운드트립을 줄이려고 가까운 태그를 하나의 큰 읽기로 묶습니다. 처리량에는 좋지만, "어제 레지스터 하나 시험할 때는 됐던" 맵에서 Illegal Data Address가 나오는 전형적인 원인입니다. 예를 들어:

  • 40020 압력
  • 40021 온도
  • 40030 상태 워드

하나씩 읽으면 셋 다 정상입니다. 드라이버가 40020부터 40030까지 한 번에 묶으면 장치가 블록 전체를 거절합니다. 4002240029가 애초에 구현되지 않았기 때문이죠. 해결책은 절대 네트워크 변경이 아닙니다. 폴링 블록을 나누거나, 드라이버 최대 블록 크기를 낮춰 빈 주소를 건너뛰지 못하게 하세요.

장치 자체의 한계도 확인하세요. 스펙은 읽기당 holding register 125개(0x7D)를 허용하지만, 값싼 RTU 컨트롤러나 게이트웨이 뒤의 시리얼 장치는 그보다 한참 낮은 값에서 막힙니다. 애매하면 최대 블록을 32개 정도로 보수적으로 낮추고 예외가 멈추는지 보세요.

쓰기는 읽기에 없던 이유로 실패한다

쓰기 예외는 짐이 더 많습니다. 권한, 운전 모드, 인터록, 스케일. 쓰기가 고장 났다고 결론 내리기 전에, 그 주소가 실제로 쓰기 가능으로 문서화돼 있는지, 장치가 기대하는 함수 코드를 쓰고 있는지(단일 06 vs 복수 16 레지스터, 단일 05 vs 복수 15 코일), 필요하다면 장치가 원격/통신 허용 모드에 있는지 확인하세요.

쓰기에서 나온 Illegal Data Value는 통신 결함이 아니라 허용 공학 범위를 벗어난 설정값인 경우가 많습니다. 쓰기에서 나온 Illegal Function은 그 영역에서 06은 안 되고 16만 구현돼 있다는 뜻일 때가 잦습니다. 멀티 레지스터 쓰기만 고집하는 드라이브 파라미터 블록에서 정확히 이 상황을 여러 번 봤습니다.

게이트웨이: unit id를 의심하라

Modbus TCP-RTU 게이트웨이는 실패 지점을 통째로 하나 더 만듭니다. TCP 쪽은 녹색인데 시리얼 쪽은 과부하이거나, 배선이 틀렸거나, 엉뚱한 슬레이브를 보고 있을 수 있습니다. 게이트웨이가 드라이버가 보낸 unit id를 몰래 remap하지 않는지 확인하세요. RTU 구간의 baud, parity, stop bit, 종단을 확인하세요. 그리고 마스터 수를 세세요. 엔지니어링 노트북, 히스토리언, HMI 둘이 게이트웨이 하나로 폴링하면 장치 결함처럼 보이는 재시도와 busy 예외가 만들어집니다.

게이트웨이 전용 코드가 값을 하는 지점이 바로 여기입니다. 0A는 라우팅과 게이트웨이 설정을 먼저 보게 하고, 0B는 하위 RTU 구간이나 슬레이브 자체를 먼저 보게 합니다. 진단 경로의 실제 갈림길을 공짜로 쥐여 주는 셈입니다.

맵은 멀쩡한데 런타임이 아닐 때

수동 폴링 하나로는 깨끗하게 읽히던 레지스터가 런타임이 전체 폴링 그룹을 한꺼번에 돌리는 순간 예외를 뱉기 시작할 수 있습니다. 느린 장치는 06 busy를 내거나 프레임을 흘리거나 게이트웨이 뒤에서 타임아웃 납니다. 맵에 손대기 전에 변수를 줄이세요. 불필요한 폴링 그룹을 끄고, 장치 하나·함수 코드 하나만 남겨 시험하고, 느린 시리얼 장치는 timeout과 요청 간 지연을 늘리세요. 오래된 RTU 장비는 300~500 ms 턴어라운드 지연이 드문 일이 아닙니다.

폴링 주기를 낮췄더니 예외가 사라졌다면 그게 답입니다. 다만 그 설정을 운 좋은 추측으로 남기지 마세요. 실제로 버텨 준 스캔 주기, timeout, retry, 블록당 레지스터 수를 기록해서, 다음 사람이 장치 처리 능력을 처음부터 다시 발견하지 않게 하세요. 그리고 빠른 HMI 표시 태그는 느린 유지보수 진단과 같은 폴링 그룹에 넣지 마세요. 펌웨어 리비전 레지스터를 트렌드 주기로 읽을 이유는 없습니다.

결국 이 일의 전부는, 서로 맞아야 하는데 보통 안 맞는 세 가지 관점을 나란히 놓는 것입니다. 매뉴얼이 말하는 맵, 드라이버가 실제로 선로에 올린 요청, 장치가 돌려준 응답. 예외 코드는 이 셋을 잇는 가장 짧은 선입니다. 그러니 실패가 아니라 답변으로 읽으세요.