TCP-직렬 게이트웨이
게이트웨이(gateway)는 Modbus TCP 요청을 받아 직렬 버스의 장비에 전달하고 응답을 돌려줍니다. 이 튜토리얼은 PDComm 헤드리스 시뮬레이터를 게이트웨이로 쓰고, 직렬 쪽 장비는 가상 시리얼 포트 건너편의 RTU 서버로 대신합니다.
터미널 B 터미널 G 터미널 V 터미널 ATCP 클라이언트 ──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 - 터미널 네 개. 모두 같은 작업 폴더에서 시작합니다.
1. 가상 포트 쌍을 만듭니다
섹션 제목: “1. 가상 포트 쌍을 만듭니다”터미널 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]}3. 게이트웨이를 엽니다
섹션 제목: “3. 게이트웨이를 엽니다”터미널 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입니다.
4. TCP 클라이언트로 요청합니다
섹션 제목: “4. TCP 클라이언트로 요청합니다”터미널 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의 요청은 직렬 버스로 나가지 않았으므로 기록이 없습니다.
6. 정리합니다
섹션 제목: “6. 정리합니다”- 터미널 B:
quit - 터미널 G와 A:
{"action":"shutdown"} - 터미널 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 통신을 참고합니다.