
들어가기 앞서서
포트를 열었다.
Open()이 예외 없이 통과했다.
Write()로 바이트를 보냈다.
그런데 장비에서 아무 응답이 없다. 식은 땀이 난다. 분명히 사양서에는 맞게 적혀 있다.
코드를 다시 훑는다.
보레이트 맞다.
데이터 비트 8, 패리티 None, 정지 비트 1. 전부 사양서와 같다. 그런데 수신 버퍼는 비어 있거나, 아니면 내가 보낸 바이트가 그대로 돌아와 있다.
일단 등뒤에 관리자의 싸늘한 시선이 등에 꽂힌다.
이때 대부분의 개발자는 코드를 의심한다.
프레임 구조를 다시 계산하고, 체크섬을 재점검하고, 타임아웃 값을 늘려본다.
그 방향은 틀렸다.
문제는 선(케이블)에 있었다.
전압이 달리 생긴 두 규격
RS-232와 RS-485는 둘 다 "시리얼 통신"이라는 이름을 공유하지만, 신호를 전선에 싣는 방식이 근본적으로 다르다.
RS-232
RS-232는 단극 전압 방식으로 동작한다.
전선 하나와 기준선(GND)의 전압 차이로 0과 1을 구분한다.
RS-232 voltage levels
GND ────────── 기준선
+3V 이상 → 논리 0
-3V 이하 → 논리 1
PC 뒷면의 9핀 D-SUB 커넥터가 이 방식이다.
구조는 단순하지만, GND를 기준으로 하는 단극 신호는 노이즈에 취약하다. 공장 바닥에는 전동기, 인버터, 용접기에서 나오는 전자기 간섭이 상시 존재한다. 이 노이즈가 신호선에 올라타면 수신 쪽이 0과 1을 잘못 읽을 수 있다.
그래서 RS-232는 보통 짧은 거리, 대략 15m 이내에서 주로 사용된다.
RS-485
RS-485는 차동 신호(differential signal) 방식으로 동작한다.
신호선은 두 가닥이다.
A ─────────
B ─────────
수신기는 각 선의 절대 전압이 아니라, 두 선 사이의 전압 차이를 본다.
RS-485 differential signal
A - B > 0 → 논리 1
A - B < 0 → 논리 0
노이즈가 두 선에 동시에 올라타도 두 선의 차이는 크게 변하지 않는다.
Before noise:
A = +2V
B = +1V
A - B = +1V
After common-mode noise +5V:
A = +7V
B = +6V
A - B = +1V
이것이 차동 신호의 핵심이다.
두 선에 같은 방향으로 들어온 노이즈는 상쇄되고, 수신기는 여전히 같은 논리값을 읽을 수 있다.
그래서 RS-485는 공장 환경처럼 노이즈가 많은 곳에서도 수백 미터 거리까지 안정적으로 통신할 수 있다.
왜 현장은 RS-485를 쓰는가
공장에는 생각보다 많은 장비가 붙는다.
PLC 하나로 끝나는 경우는 거의 없고, LVDT 앰프 여러 대, 인버터, 바코드 스캐너, 터치패널 같은 장비들이 줄줄이 연결된다.
문제는 이걸 전부 PC나 PLC와 통신시켜야 한다는 점이다.
여기서 RS-232와 RS-485의 차이가 현실적으로 드러난다.
RS-232는 기본적으로 1:1 통신이다.
장비 하나를 연결하려면 포트 하나가 필요하다.
PC에서 보면 이런 식이다.
PC ── COM1 ── 장비1
PC ── COM2 ── 장비2
PC ── COM3 ── 장비3
장비가 늘어날수록 COM 포트도 같이 늘어나야 한다.
장비가 10대면 포트도 10개가 필요하다.
이건 실제 현장에서는 거의 불가능하다.
포트도 부족하고, 배선도 복잡해지고, 유지보수는 더 힘들어진다.
조금만 구조가 바뀌어도 선을 다시 따야 한다.
그래서 RS-485를 쓴다.
RS-485는 구조 자체가 다르다.
두 가닥(A, B) 선을 하나의 버스로 쓰고, 모든 장비를 여기에 매단다.
PC ── A/B ──┬── 장비1 (주소 1)
├── 장비2 (주소 2)
└── 장비3 (주소 3)
모든 장비가 같은 선을 공유하지만, 충돌하지 않는다.
이유는 주소 기반으로 동작하기 때문이다.
PC가 특정 장비를 부를 때는 이렇게 요청한다.
“주소 1번, 데이터 보내라”
그러면 주소 1번 장비만 응답하고, 나머지는 아무 반응도 하지 않는다.
이 구조 덕분에 선은 두 가닥인데, 장비는 여러 개를 붙일 수 있다.
배선은 단순해지고, 포트 수 제한에서도 자유로워진다.
거기에 RS-485는 거리도 길고 노이즈에도 강하다.
이게 산업 현장에서 RS-485가 표준처럼 굳어진 이유다.
물론 산업 현장이라고 항상 RS-485만 쓰는 건 아니다.

(현장에서 찍은 컨버터)
RS-232 장비도 자주 섞인다. 이유는 단순하다.
예산 문제이거나, 구매 실수다.
이럴 때는 RS-232↔RS-485 변환기를 달아서 해결한다.
이때 USB-시리얼 변환기를 선택한다면 품질을 따져야 한다.
마트나 인터넷 최저가로 구한 범용 USB 변환기는 산업 현장의 폴링 부하를 감당하지 못하는 경우가 있다. 30ms 주기로 반복 폴링이 걸리면 USB 호스트 컨트롤러 부하가 올라가고, 일부 변환기는 이 상태에서 COM 포트를 스스로 해제한다. 코드에서는 SerialPort.Open()이 FileNotFoundException을 던지는 것으로 나타난다.
포트가 있다가 없어지는 간헐적 증상이라 원인 추적이 어렵다.
진지하게 운영하는 라인이라면 USB 변환기보다 PCI/PCIe RS-485 카드를 권장한다. 버스 직결이라 USB 경합이 없고, 드라이버가 안정적이며, FTDI 칩셋 기반 산업용 제품은 갈바닉 절연까지 지원해서 전기적 노이즈 차단도 된다.
USB 변환기를 써야 한다면 FTDI FT232 또는 FT485 칩셋 기반 제품을 확인하고, 드라이버는 제조사 공식 버전을 쓰도록 하자.
이걸 왜 아냐고? 나도 알고 싶지 않았다.
RS-422는 무엇인가
RS-485와 전기적으로 거의 같다.
차동 신호를 쓴다. 노이즈에 강하고 거리가 길다.
차이는 방향성이다.
RS-485는 반이중(Half-Duplex)이 기본이다. 두 선으로 송신과 수신을 번갈아 한다. 한 순간에 한 방향만 가능하다.
RS-422는 송신 전용 선 2가닥, 수신 전용 선 2가닥으로 총 4선을 쓴다. 송신과 수신이 동시에 가능한 전이중(Full-Duplex)이다. 대신 멀티드롭은 안 된다. 1:1 전용이다.
PLC의 고속 시리얼 포트, 서보 드라이버 통신에서 RS-422를 자주 만난다. PC 쪽 시리얼 포트가 "RS-422 지원"이라고 표기되어 있으면 4선 연결이 필요하다는 뜻이다.
현장 테스트를 할때 주의사항들
포트도 맞고, 보레이트도 맞고, 케이블도 제대로 꽂혀있다. 근데 안 된다. 이 상황에서 어디를 봐야 하는지, 현장에서 자주 만나는 증상 순서대로 정리한다.
ECHO_ONLY -가장 흔한 오진
포트를 열고 바이트를 보냈는데 내가 보낸 바이트가 그대로 돌아온다.
이 상황이 두 가지 다른 원인에서 발생한다.
첫 번째 - RS-232 포트에 RS-485 장비를 연결한 경우
이건 소프트웨어로 해결할 수 없다.
RS-232 포트는 단극 전압 신호를 출력한다. RS-485 장비는 차동 신호를 기대한다. 두 규격은 전기적으로 호환이 안 된다. PC가 출력한 단극 신호가 RS-485 라인을 구동하지 못하고 TX가 RX로 루프백되어 돌아온다.
그래서 내가 보낸 바이트가 그대로 수신 버퍼에 나타난다.
장비를 아무리 들여다봐도, 코드를 아무리 수정해도 해결이 안 된다. RS-232↔RS-485 변환기(컨버터)를 달거나, RS-485를 지원하는 시리얼 카드를 설치해야 한다.
이 상황이 의심스러울 때 가장 빠른 확인 방법은 PC 장치 관리자다.
DeviceID: ACPI\PNP0501
이 접두사가 보이면 마더보드 내장 UART다.
RS-232 전용이다. RS-485 장비와 통신하려면 외부 장비가 필요하다.
두 번째 — RS-485 반이중에서 발생하는 정상적인 에코
RS-485 Half-Duplex에서는 PC가 보낸 바이트가 같은 버스 라인을 통해 수신 쪽으로도 들어온다. 이건 하드웨어 설계상 정상이다.
문제는 소프트웨어가 이 에코를 처리하지 않으면 장비의 실제 응답을 에코로 오해하거나, 에코와 응답이 섞여서 파싱이 어긋나는 것이다.
처리 방법은 단순하다. 요청을 보낸 직후, 요청 길이만큼의 바이트를 먼저 읽어서 버린다. 그 다음에 오는 바이트가 장비의 실제 응답이다.
// RS-485 Half-Duplex 에코 처리
port.Write(command, 0, command.Length);
// 에코 버리기: 보낸 만큼 읽어서 무시
var echo = new byte[command.Length];
var totalRead = 0;
while (totalRead < command.Length)
{
totalRead += port.Read(echo, totalRead, command.Length - totalRead);
}
// 여기서부터 실제 장비 응답
var response = ReadResponse(port);
세 번째 - 에코는 오는데 응답이 없는 경우
에코가 정상으로 돌아오는데 그 뒤에 아무것도 오지 않는다면, 케이블과 포트는 정상이다. 의심해야 할 것은 장비 전원이다.
RS-485 버스는 물리적으로 연결된 상태에서 장비 전원이 꺼져 있어도 에코는 그대로 돌아온다. 에코는 버스 라인의 신호 반사이지, 장비가 처리해서 보내는 것이 아니기 때문이다. 장비가 꺼져 있으면 에코까지는 오고 응답 데이터는 영원히 오지 않는다.
현장에서 "코드를 바꾸지 않았는데 갑자기 안 된다"는 증상의 상당수가 이 경우다. 장비 전원 차단기가 내려가 있거나, 장비가 알람 상태로 통신을 중단한 경우다.
종단 저항 - 놓치기 쉬운 변수
RS-485 버스를 길게 쓸 때 한 가지 더 챙겨야 할 것이 있다.
종단 저항(Termination Resistor)이다.
버스 양 끝에 120Ω 저항을 달아야 한다. 이게 없으면 신호가 버스 끝에서 반사되어 되돌아온다. 반사된 신호가 원래 신호와 간섭하면 데이터가 뒤집힌다.
짧은 거리(수 미터)에서는 문제가 안 될 수 있다. 하지만 케이블이 길어지거나 장비 수가 늘어나면 증상이 나타난다.
현장에서 RS-485 통신이 간헐적으로 실패하거나, 보레이트가 높을수록 오류가 잦아진다면 종단 저항을 의심한다.
많은 장비에는 본체에 딥 스위치로 종단 저항을 내장/해제하는 기능이 있다.
여담이지만, 선은 짧을수록 좋다.
길게 늘어진 케이블은 노이즈도 태우고, 지나가던 작업자 발목에도 걸린다.
두 번째 경우가 생각보다 훨씬 자주 일어난다.
규격을 확인하는 법
케이블만 보고는 판단이 어렵다.
RS-232도, RS-485도 DB9 커넥터를 쓸 수 있다. 생김새가 같아도 속은 다르다.
확인 순서:
1. 장비 매뉴얼의 통신 사양 페이지 "Interface: RS-485" 또는 "RS-232C" 라고 명시되어 있다.
2. 케이블 배선도 RS-232는 TXD, RXD, GND 3선이 기본이다. RS-485는 A(+), B(-) 2선이 기본이다. RS-422는 TX+, TX-, RX+, RX- 4선이다.
3. 레거시 코드 이전에 동작하던 코드가 있으면 SerialPort 초기화 부분을 본다. RtsEnable, DtrEnable 설정이 있거나 특정 흐름 제어가 있으면 힌트가 된다. RS-485 변환기 중에는 RTS 신호로 송수신 방향을 전환하는 것들이 있다.
C# 코드에서 달라지는 것
RS-232와 RS-485의 SerialPort 초기화 코드 자체는 동일하다.
보레이트, 데이터 비트, 패리티, 정지 비트를 맞추는 방식이 같다.
차이는 세 군데다. 다만, 그 전에 앞서서 필요한 배경지식이 있다.
RTS와 DTR이란?
RS-232 규격에는 데이터 선(TXD, RXD) 외에 제어 신호선이 따로 있다. 그 중 실무에서 자주 마주치는 두 가지가 RTS와 DTR이다.
RTS(Request To Send)는 원래 "나 지금 데이터 보낼 준비됐다"를 상대방에게 알리는 신호다. 모뎀 시절에는 이 신호로 흐름 제어를 했다. 지금 산업 현장에서는 그 본래 용도보다 RS-485 변환기의 송수신 방향 전환 신호로 더 많이 쓰인다. RS-485는 반이중이라 한 순간에 송신 또는 수신 중 하나만 가능한데, 일부 변환기는 RTS가 HIGH일 때 송신 모드, LOW일 때 수신 모드로 전환한다.
DTR(Data Terminal Ready)은 원래 "단말기가 준비됐다"를 알리는 신호다. 지금은 일부 장비에서 전원 또는 활성화 신호로 전용된다. DTR이 LOW 상태면 장비가 통신 자체를 거부하는 경우가 있다.
RS-485 변환기를 쓰는 경우, 변환기 모델에 따라 RTS 신호로 TX/RX 방향을 전환하는 것들이 있다.
//예전에 작업했던 포트와 보레이트 예시
var port = new SerialPort("COM4", 19200, Parity.None, 8, StopBits.One);
// RS-485 Half-Duplex에서 방향 전환이 필요한 변환기를 쓴다면
port.RtsEnable = true; // 일부 변환기는 RTS로 TX/RX 방향을 전환한다
port.DtrEnable = false;
RS-232 직결이거나 자동 방향 전환 변환기를 쓴다면 둘 다 건드리지 않아도 된다.
//예전에 작업했던 포트와 보레이트 예시
var port = new SerialPort("COM4", 19200, Parity.None, 8, StopBits.One);
// RS-232 직결이라면 둘 다 false가 일반적
port.RtsEnable = false;
port.DtrEnable = false;
RTS/DTR이 변환기 방향 전환 용도가 아닌 장비 자체의 활성화 신호로 쓰이는 경우도 있다. 일부 바코드 스캐너는 DTR과 RTS가 모두 HIGH 상태일 때만 데이터를 내보내도록 설계되어 있다. 이 경우 두 값을 false로 두면 포트는 열리고 통신 파라미터도 맞는데 데이터가 아예 나오지 않는다. 매뉴얼에 DTR/RTS 요구사항이 명시되어 있는지 확인하는 것이 이 경우 첫 번째 순서다.
// 일부 바코드 스캐너 — 매뉴얼에 DTR/RTS 요구사항 명시된 경우
port.DtrEnable = true;
port.RtsEnable = true;
RTS 방향 전환 방식은 변환기 모델마다 다르다. 자동으로 처리하는 변환기도 있고, 수동으로 RTS를 토글해야 하는 것도 있다. 매뉴얼을 확인한다.
에코 처리가 필요한 상황인지 여부:
물리 규격 | 에코 처리 필요 여부 |
|---|---|
RS-232 | 불필요 |
RS-422 (Full-Duplex) | 불필요 |
RS-485 Half-Duplex | 필요 |
RS-485 Full-Duplex (4선) | 불필요 |
물론, 현장은 만만한 곳이 아니기때문에, 분명히 확인했는데
에코가 또 오지 않을때가 있다. 자동 방향 전환 기능이 내장된 변환기는 TX/RX 전환을 하드웨어가 처리하기 때문에 에코를 호스트에 돌려보내지 않는다.
이런 변환기에서 "보낸 만큼 읽어서 버린다"는 코드를 그대로 실행하면, 에코가 없으니 응답 앞부분을 에코로 착각해서 버리게 된다.
에코 크기는 보낸 명령 프레임 길이와 같다. 그런데 프로토콜에 따라 명령 프레임 자체가 가변 길이인 경우가 있다. 같은 장비라도 명령 종류에 따라 에코 크기가 달라지므로, 에코 버리는 코드에 하드코딩 대신 command.Length를 그대로 넘기는 것이 안전하다.
port.Write(command, 0, command.Length);
// 에코 크기 = 보낸 명령 길이
var echoSize = command.Length;
var buffer = new byte[echoSize + expectedResponseSize];
var totalRead = ReadWithTimeout(port, buffer);
if (totalRead < echoSize)
{
// 에코도 짧게 왔거나 아예 없음 → 자동 방향전환 변환기 환경
// 받은 데이터 전체를 응답으로 처리
ParseResponse(buffer[..totalRead]);
return;
}
if (totalRead == echoSize)
{
// 에코만 오고 응답 없음 → 장비 전원 꺼짐 또는 주소 불일치
Log("ECHO_ONLY");
return;
}
// 정상: 에코 버리고 응답 파싱
ParseResponse(buffer[echoSize..totalRead]);
// ReadWithTimeout 구현 예시
private static int ReadWithTimeout(SerialPort port, byte[] buffer)
{
var totalRead = 0;
var deadline = DateTime.UtcNow.AddMilliseconds(port.ReadTimeout);
while (totalRead < buffer.Length && DateTime.UtcNow < deadline)
{
var available = port.BytesToRead;
if (available > 0)
{
var chunk = Math.Min(available, buffer.Length - totalRead);
totalRead += port.Read(buffer, totalRead, chunk);
}
else
{
Thread.Sleep(5);
}
}
return totalRead;
}
에코 처리 코드를 짤 때는 변환기 사양서에서 Auto Direction Control 또는 Echo Suppression 항목을 확인한다.
표기가 있으면 에코 없는 환경으로 처리한다.
RS-485 위에서 무엇이 달리는가
지금까지 다룬 것은 전부 물리 계층이다. 어떤 선을 쓰는가, 신호가 어떻게 생겼는가, 에코가 왜 생기는가.
이것들은 데이터가 선에 실리기 전의 이야기다.
선이 준비됐으면 그 위에서 어떤 형식으로 데이터를 주고받을지를 정해야 한다. 이것이 프로토콜이다.
산업 현장에서 RS-485 위에 올라타는 프로토콜 중 가장 넓게 쓰이는 것이 Modbus RTU다. 1979년에 Modicon이 만든 프로토콜인데, 지금도 인버터, 전력 계측기, 온도 조절기, 저가 센서 대부분이 이 방식을 지원한다. Mitsubishi MC Protocol, Siemens S7, Omron FINS 같은 PLC 제조사 전용 프로토콜과 달리 Modbus는 특정 벤더에 종속되지 않는다.
그래서 제조사가 다른 장비들이 같은 RS-485 버스에 섞여 있어도 Modbus를 공통 언어로 쓸 수 있다.
Modbus가 정확히 어떤 프레임 구조를 가지고, CRC를 어떻게 계산하고, C#에서 어떻게 구현하는지는 다음 편에서 다룬다.
정리
포트를 열고 바이트를 보냈는데 응답이 없거나 에코만 돌아온다면, 코드보다 먼저 이것을 확인한다.
장비가 RS-485인데 PC 포트가 RS-232뿐인가?
그러면 변환기가 필요하다.
RS-485 Half-Duplex를 쓰는데 에코 처리 코드가 없는가?
그러면 응답 파싱이 어긋난다.
버스가 긴데 종단 저항이 없는가?
그러면 간헐적 오류가 난다.
세 가지 중 하나가 빠져 있으면 코드를 아무리 잘 짜도 통신이 되지 않는다.
다음 편에서는 이렇게 선과 포트를 준비한 뒤, 실제로 어떤 형식으로 데이터를 주고받는지를 다룬다.
프레임 구조와 프로토 콜이다.