콘텐츠로 이동
무료 평가 시작

TCP-직렬 게이트웨이

게이트웨이(gateway)는 Modbus TCP 요청을 받아 직렬 버스의 장비에 전달하고 응답을 돌려줍니다. 이 튜토리얼은 PDComm 헤드리스 시뮬레이터를 게이트웨이로 쓰고, 직렬 쪽 장비는 가상 시리얼 포트 건너편의 RTU 서버로 대신합니다.

터미널 B 터미널 G 터미널 V 터미널 A
TCP 클라이언트 ──TCP──> 게이트웨이 127.0.0.1:1504 ── modbus-b ══ pair ══ modbus-a ── RTU 서버(주소 7)
유닛 1 ─────────────> routes {"1":7, "2":9}
  • role이 gateway인 구성과 routes 경로 표를 이해합니다.
  • TCP 유닛 식별자가 직렬 주소로 바뀌는 것을 trace로 확인합니다.
  • 경로가 없을 때(예외 0A)와 직렬 장비가 응답하지 않을 때(예외 0B)를 구분합니다.
  • RTU와 ASCII 통신의 준비물: SDK, C++ 콘솔, pdcomm-vserial
  • 터미널 네 개. 모두 같은 작업 폴더에서 시작합니다.

터미널 V에서 실행하고 열어 둡니다.

터미널 창
pdcomm-vserial pair --link-a "$PWD/modbus-a" --link-b "$PWD/modbus-b"

2. 직렬 장비를 대신할 RTU 서버를 엽니다

섹션 제목: “2. 직렬 장비를 대신할 RTU 서버를 엽니다”

터미널 A에서 주소 7인 RTU 서버를 modbus-a에 엽니다.

터미널 창
"$MODBUS_SDK/bin/pdcomm-modbus-headless"
{"action":"configure","config":{"transport":"rtu","role":"server","address":"127.0.0.1:0","port":"/absolute/path/modbus-a","unit":7,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000}}
{"action":"connect"}
{"action":"data_set","table":"holding_registers","address":0,"values":[1200,1185]}

터미널 G에서 두 번째 헤드리스 시뮬레이터를 게이트웨이로 구성합니다.

터미널 창
"$MODBUS_SDK/bin/pdcomm-modbus-headless"
{"action":"configure","config":{"transport":"rtu","role":"gateway","address":"127.0.0.1:1504","port":"/absolute/path/modbus-b","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":1000,"routes":{"1":7,"2":9}}}
{"action":"connect"}

게이트웨이 구성의 각 필드는 다음 뜻입니다.

필드 뜻
transport 직렬 버스 쪽 전송 방식, rtu 또는 ascii. TCP 쪽은 항상 Modbus TCP입니다. tcp를 지정하면 Gateway requires a serial bus transport 오류입니다.
address TCP 클라이언트를 받을 주소
port, baud, parity, stop_bits 직렬 버스 설정. 버스의 장비와 같아야 합니다.
routes TCP 유닛 식별자 → 직렬 주소. 여기서는 유닛 1을 주소 7로, 유닛 2를 주소 9로 보냅니다.
timeout_ms 직렬 장비의 응답을 기다리는 시간. 넘으면 예외 0B를 돌려줍니다.

unit은 게이트웨이에서 쓰이지 않지만 형식상 1247 값이 필요합니다. 경로 표의 키(TCP 유닛)는 1255, 값(직렬 주소)은 1~247이어야 합니다. 0이나 범위 밖 값은 Invalid gateway route입니다.

터미널 B에서 C++ 콘솔을 시작하고 게이트웨이에 연결합니다. 클라이언트의 timeout_ms는 게이트웨이의 직렬 대기 시간보다 길게 잡습니다. 그래야 예외 0B를 시간 초과 대신 받을 수 있습니다.

터미널 창
./build/modbus-tutorial/pdcomm-modbus-tutorial
{"action":"configure","config":{"transport":"tcp","role":"client","address":"127.0.0.1:1504","port":"","unit":1,"baud":19200,"parity":"even","stop_bits":1,"timeout_ms":3000}}
{"action":"connect"}
{"action":"request","pdu":"0300000002"}
{"action":"request","pdu":"0600010064"}
{"action":"request","unit":2,"pdu":"0300000001"}
{"action":"request","unit":3,"pdu":"0300000001"}
{"action":"request","pdu":"0300000002"}
{"ok":true,"result":{"response":[3,4,4,176,4,161],"source":"wire","status":"success"}}
{"ok":true,"result":{"response":[6,0,1,0,100],"source":"wire","status":"success"}}
{"ok":true,"result":{"response":[131,11],"source":"wire","status":"exception"}}
{"ok":true,"result":{"response":[131,10],"source":"wire","status":"exception"}}
{"ok":true,"result":{"response":[3,4,4,176,0,100],"source":"wire","status":"success"}}
요청 결과 설명
유닛 1 읽기·쓰기 정상 경로 1→7로 직렬 서버에 전달됐습니다.
유닛 2 예외 0B([131,11]), 약 1초 뒤 경로 2→9는 있지만 주소 9인 장비가 응답하지 않았습니다.
유닛 3 예외 0A([131,10]), 바로 경로 표에 유닛 3이 없습니다. 직렬 버스에 아무것도 보내지 않습니다.

5. 직렬 쪽 프레임을 확인합니다

섹션 제목: “5. 직렬 쪽 프레임을 확인합니다”

터미널 G에서 게이트웨이의 기록을 읽습니다.

{"action":"trace"}

direction이 tx인 항목의 첫 바이트가 직렬 주소입니다.

bytes 뜻
[7,3,0,0,0,2,196,109] 유닛 1의 읽기가 주소 7로 나갔습니다.
[7,3,4,4,176,4,161,95,156](rx) 주소 7의 응답
[9,3,0,0,0,1,133,66] 유닛 2의 읽기가 주소 9로 나갔지만 rx가 없습니다.

유닛 3의 요청은 직렬 버스로 나가지 않았으므로 기록이 없습니다.

  1. 터미널 B: quit
  2. 터미널 G와 A: {"action":"shutdown"}
  3. 터미널 V: {"command":"stop"}
  • 직렬 버스는 한 번에 거래 하나만 처리합니다. 여러 TCP 클라이언트의 요청은 최대 64개까지 대기열에 들어가고, 넘치면 예외 06(Server Device Busy)을 받습니다.
  • 대기 중에 TCP 연결이 끊긴 클라이언트의 요청은 버립니다. 늦게 온 직렬 응답이 다른 요청의 응답으로 전달되지 않습니다.
  • 직렬 전용 기능(07, 08, 11, 12, 17)은 예외 01입니다.
  • 직렬 요청을 자동으로 다시 보내지 않습니다. 실패는 예외 0B로 돌려줍니다.
  • TCP 클라이언트는 최대 32개까지 받습니다.
  • 게이트웨이도 서버처럼 poll을 반복해야 동작합니다. 헤드리스 시뮬레이터와 GUI는 이 반복을 직접 합니다.

실제 RS-485 장비에 연결하려면 port를 USB-RS485 어댑터의 이름으로 바꾸고 버스의 설정에 맞춥니다. 배선과 설정은 RTU와 ASCII 통신을 참고합니다.