Skip to content

01 포트와 보레이트

포트와 보레이트 - 시리얼 통신 첫걸음

Makonea·2026년 4월 19일·22분

들어가며...

장비 제어 개발을 하러 가서 사양서를 펴보면 처음 보는 프로그래머는 당황할 것이다. IP 주소도 없고, REST API도 없고, JSON도 없다. 대신 이런 표가 나온다.

항목

포트 이름

COM4

비트/초

19200

데이터 비트

8

패리티

없음

정지 비트

1

사양서에는 이런 정체 불명의 데이터들이 적혀있고,

그대로 코드에 박아 넣고 실행하면 통신이 된다.

되면 좋다. 문제는 안 되는 경우가 왕왕 있다는 것이다. 그럼 이때부터 식은땀이 나기 시작한다. 납기일을 못맞추면 어떡하지? 나 파산인데 오만가지 생각이 들기 시작한다.

그렇게 현장에 가서 딥스위치를 열어보면 사양서는 19200인데 장비는 9600으로 맞춰져 있거나, 8-N-1이라고 적혀 있는데,

실제로는 8-E-1로 동작하거나, 심한 경우 사양서에 적힌 모델명과 현장에 설치된 장비의 펌웨어 버전이 달라서 프레임 구조 자체가 조금씩 다르다. 교체 과정에서 누가 설정을 바꿨을 수도 있고, 사양서가 여러 세대 전의 문서일 수도 있고, 그냥 오타일 수도 있다.

이때 필요한 것은 "사양서가 틀렸다"고 화를 내는 것이 아니라, 직접 값을 바꿔가며 맞춰볼 수 있는 능력이다. 그러려면 각 값이 무슨 의미인지 알아야 한다. 의미를 알면 증상을 보고 어디가 어긋났는지 역추적할 수 있다. 이 글은 그 역추적 능력을 위한 출발점이다. 산업 현장 코딩 시리즈의 첫 편이고, 시리얼 통신의 가장 낮은 계층 3가지를 다룬다.

그리고 산업 현장 코딩은 윈도우용 코드만 쓴다. 왜냐하면 적어도 한국 환경에서는 장비 제어는 윈도우가 표준이기때문이다.

시리얼 통신이 왜 아직 남아 있는가

RS-232 규격은 1960년에 제정됐다. 지금으로부터 60년도 더 된 물건이다.

이더넷이 1980년대, Wi-Fi가 1990년대에 나왔고, 그 사이 데이터 전송 기술은 수십 번 세대가 바뀌었다. 그런데 공장에 가면 아직도 이 1960년대 방식이 현역으로 돌아간다.

왜 그런가.

첫째, 단순함이 신뢰성이다. 시리얼 통신은 프로토콜 스택이 거의 없다. 두 장치 사이에 전선 몇 가닥이 있고, 한쪽이 비트를 내보내면 다른 쪽이 받는다. 중간에 OS 커널도, 드라이버 스택도, 재조립 로직도 없다. 장애 지점이 적다는 것은 20년 동안 하루도 안 멈추고 돌아가야 하는 환경에서 결정적인 미덕이다.

둘째, 레거시 자산의 무게. 한 대에 수억 원짜리 측정 장비가 RS-232 인터페이스를 달고 나왔다면, 그 장비가 수명을 다하기 전까지 호스트 쪽도 RS-232를 계속 말해야 한다. 공장에서 장비의 수명은 보통 15년에서 30년 사이다. 1995년에 들어온 장비가 2025년에도 돌아가는 일이 드물지 않다. 그리고 그걸 유지보수하는게 이 바닥의 룰이다.

셋째, 규격의 동결이 오히려 장점이다. 표준이 오래 전에 박제되었기 때문에, 새 제품이 나와도 과거 규격을 그대로 지원한다. 60년 된 규격이 아직도 호환성 걱정 없이 쓰인다는 말이다. 이건 소프트웨어 쪽에서 보면 거의 마법 같은 일이다.

넷째, 현실적인 확장 - RS-485. 다만 RS-232는 거리와 노이즈에 취약하고, 한 번에 한 장치만 연결할 수 있다는 한계가 있다. 그래서 공장에서는 같은 “시리얼 통신” 범주 안에서 RS-485가 널리 쓰인다. 최근에 대부분 작업은 485지만, PC쪽에서는 프로그래머가 하는 PC쪽은 여전히 232도 많이 쓰인다.

RS-485는 전압을 절대값이 아니라 두 선의 차이로 판단하는 차동 신호 방식을 사용해서 노이즈에 강하고, 수십 미터에서 수백 미터까지 안정적으로 통신할 수 있으며, 한 라인에 여러 장치를 물릴 수 있다. 중요한 점은 데이터를 주고받는 방식 자체는 여전히 시리얼이라는 것이다.

즉, 포트·보레이트·데이터 비트 같은 약속은 그대로 유지되고, 단지 전기적인 신호 방식만 바뀐 것이다.

결국 시리얼 통신은 사라지지 않는다. 오히려 새로 들어가는 개발자가 이 규격을 배워야 한다. 그 시작점이 포트와 보레이트다.

첫 번째 약속 - 포트

포트는 "어느 통로로 말할 것인가"를 정한다. 웹 개발자에게 익숙한 비유로 바꾸면 TCP 포트 번호와 비슷한 역할이지만, 물리적 성격이 강하다는 차이가 있다.

Windows에서 시리얼 포트는 COM1, COM2, COM3 같은 이름으로 관리된다. 장치 관리자를 열면 포트(COM & LPT) 항목 아래에 현재 인식된 포트들이 나열된다. USB-to-Serial 변환기를 꽂으면 이 목록에 새 항목이 늘어나고, 빼면 사라진다.

여기서 함정이 기다린다.

COM 포트 번호는 논리적 식별자일 뿐이다. PC 뒷면의 9핀 커넥터가 COM1일 거라고 짐작했는데, 알고 보니 메인보드 BIOS 설정에서 그 포트가 비활성화되어 있고, USB 변환기가 COM1을 차지하고 있을 수 있다. 또는 그 반대일 수도 있다.

장비 매뉴얼은 "COM2에 연결되어있다"라고 쓰여 있는데, 여기서 말하는 COM2가 물리적 어느 커넥터인지는 현장에서 직접 확인해야 한다.

장치 관리자에서 각 포트를 클릭해서 속성을 보면 DeviceID 장치 인스턴스 나온다. 이것을 보면 그 포트가 어떤 종류인지 읽을 수 있다.

DeviceID 접두사

의미

ACPI\PNP0501

메인보드 내장 UART (대부분 RS-232)

FTDIBUS\VID_0403

FTDI 칩 USB 변환기

USB\VID_067B

Prolific 칩 USB 변환기

SERENUM\*

다른 장치가 시리얼 포트를 점유한 상태

마지막 SERENUM\*이 특히 중요하다. 터치스크린 컨트롤러나 지문 인식기 같은 것들이 시리얼 버스에 붙어서 COM 번호를 먹는 경우가 있다. 현장에서 "COM1이 열리지 않는다"는 증상의 절반은 이 원인이다.

(물론 SERENUM 자체가 점유 상태를 의미하는 것은 아니다. 다만, 대다수의 경우는 그러하다는 것이다)

특히 현장 세팅에서 가장 자주 나오는 실수이기도 한데, 무엇인지 모르면 "코드가 잘못됐나" 싶어서 몇 시간을 허비하기도 한다.

포트의 두 번째 함정은 점유 문제다.

시리얼 포트는 한 번에 하나의 프로세스만 열 수 있다. 앞서 떠 있던 앱 인스턴스가 제대로 종료되지 않고 좀비로 남아 있으면, 새 인스턴스는 UnauthorizedAccessException: Access to the path 'COM4' is denied 에러를 만난다.

현장에서 이 에러가 나면 taskkill로 잔여 프로세스를 정리해야 한다. 애플리케이션 입장에서는 시작할 때 같은 이름의 다른 인스턴스가 있는지 확인하는 가드 코드를 넣어두는 것이 안전하다. 특히 터치 스크린 계열을 연결했을때 빈번하게 발생하는 에러이다.

특히 이 문제가 중요한 것은 많은 시리얼 장비는 사실상 하나의 리더만 허용한다. 즉, 여러 모듈이 각자 포트를 직접 열어 읽는 구조로는 설계하기 어렵다.

이런 경우에는 포트를 전담하는 단일 읽기 루프를 두고, 수신한 데이터를 내부 캐시나 큐에 적재한 뒤 다른 모듈이 그 복사본을 소비하게 만드는 편이 안전하다. 포트를 공유 자원으로 다루지 않으면, 나중에 UI 갱신·로깅·판정 로직이 서로 충돌하기 쉽다.

The baud is usually used to quantify this, one baud being equal to one single element per second -ITU-T G.701
보통 이를 정량화하기 위해 보(baud)를 사용하며, 1 보드는 초당 하나의 신호 요소를 의미한다. - ITU-T G.701

두 번째 약속 - 보레이트

단위의 기원 - 1870년대 전신에서 시작되었다

보레이트(Baud Rate)는 시리얼 통신 이전의 유산이다. 단위 이름 자체가 19세기 프랑스의 전신 공학자 에밀 보도(Émile Baudot)에게서 왔다.

보도는 1870년대에 5비트 전신 코드를 만들었다. 이전까지 모스 부호는 각 문자의 길이가 달라서(점 하나짜리 E, 네 개짜리 H) 전송 속도를 객관적으로 측정하기 어려웠다. 보도의 코드는 모든 문자가 같은 길이였고, 따라서 "초당 몇 개의 신호 상태를 바꿀 수 있는가"를 단일한 수치로 표현할 수 있게 되었다. 이 수치가 나중에 그의 이름을 따 baud라는 단위로 정착했다.

보레이트(baud)라는 용어의 공식적인 의미는 개별 모뎀 규격에서 정의된 것이 아니라, 국제전기통신연합(ITU)의 용어 정의 계열 문서에서 ‘초당 신호 요소(symbol)의 변화 횟수’로 정리된 개념이다. 예를 들어 ITU-T G.701은 symbol rate를 단위 시간당 전송되는 신호 요소의 수로 설명하고, baud를 그 단위로 사용한다고 적고 있다.

RS-232 쪽은 조금 다른 계보다. 1962년 미국전자공업협회(EIA)가 표준화한 RS-232는 전압 레벨과 타이밍, 비트 전송 방식을 정의했고, 실무에서는 baud ≈ bit rate로 쓰인다. 왜 그런지는 아래서 설명한다.

현업에서는 bps와 같은 의미로 쓴다

우리가 다루는 UART 기반 시리얼 통신에서는 거의 항상 한 심볼이 한 비트다. 전압이 High면 1, Low면 0.

그래서 심볼 변화 횟수와 비트 전송 속도가 수치상으로 일치한다. 9600 baud는 9600 bps다.

이것이 실무에서 두 단어가 혼용되는 이유다. 엄밀히 말해 둘은 다른 단위이지만, 우리가 만나는 거의 모든 산업 장비는 NRZ(Non-Return-to-Zero) 방식이라 1 symbol = 1 bit가 성립한다. 매뉴얼에서 "9600 baud"라고 쓰여 있으면 그냥 "초당 9600비트"로 읽으면 된다.

산업 현장에서 자주 만나는 값들은 다음과 같다.

보레이트

쓰이는 장비 예시

1200 / 2400

매우 오래된 센서, 저속 전력 계측기

9600

바코드 스캐너, 저가 RS-485 장비, 출력 프린터

19200

중급 PLC, LVDT 앰프 일부

38400 / 57600

중급 계측기

115200

고속 AMP, 디버그 콘솔, 최신 장비

이 값들이 관습이 된 이유는 초기 UART 칩의 클록 분주 구조와 관련이 있다. 1.8432 MHz 수정 발진기를 정수로 분주해서 얻을 수 있는 깨끗한 값들이 9600, 19200, 38400, 57600, 115200이다. 이 계보가 지금까지 이어져서 현대 USB 변환 칩도 같은 값 집합을 우선 지원한다.

현장 장비의 DIP 스위치를 열어보면 대부분 이 집합에서 고르게 되어 있는 이유다.

한 걸음 더 - 보레이트와 bps가 달라지는 경우

위에서 "1 심볼 = 1 비트"라고 단순화했는데, 이게 무너지는 지점이 있다. 통신 공학의 변조(modulation) 기법이 들어오는 순간이다.

모뎀이 전화선을 통해 데이터를 보낼 때, 한 번의 신호 변화에 여러 비트의 정보를 실어 보낼 수 있다. 대표적인 방식이 QAM(Quadrature Amplitude Modulation)이다. QAM-16은 진폭과 위상을 조합해 한 심볼에 4비트를, QAM-64는 6비트를, QAM-256은 8비트를 담는다.

이 관점에서 다시 보면 관계식이 분명해진다.

bps = baud × (심볼당 비트 수)

예를 들어 V.34 모뎀 표준은 2400 baud의 심볼 속도로 동작하지만, 심볼 하나에 최대 14비트까지 담아서 33600 bps를 낸다. 이 경우 baud = 2400이지만 bps = 33600이다. 완전히 다른 숫자가 된다.

같은 원리가 현대 유무선 통신 전반에 깔려 있다. Wi-Fi의 256-QAM, 1024-QAM, 5G의 고차 QAM 전부 "한 심볼에 더 많은 비트를 실어 같은 대역폭에서 더 높은 처리량을 얻는" 전략이다. 셀룰러의 세대 전환도 결국 이 심볼당 비트 수를 끌어올린 이야기다.

산업 현장의 RS-232/485 시리얼은 이 세계와 거리가 있다. 전압 두 단계로 한 비트를 표현하는 단순한 구조이고, 변조라고 할 만한 것이 없다. 그래서 baud = bps가 성립하고, 실무에서 두 단어를 혼용해도 오해가 생기지 않는다. 하지만 용어의 정확한 의미를 알고 있으면, 훗날 무선 모뎀이나 고속 직렬 규격(PCIe, SATA, USB 3.x 등) 문서를 읽을 때 같은 단어가 왜 다르게 쓰이는지 바로 연결된다. 이 규격들은 8b/10b, 128b/130b 같은 코딩 기법을 쓰기 때문에 baud와 유효 데이터 전송률이 또 다르게 갈라진다.

결론만 정리하면 이렇다. 매뉴얼에서 "9600 baud"를 보면 "초당 9600비트"로 읽는다. 그런데 이 단어가 원래 symbol rate를 뜻한다는 것, 그리고 변조가 들어가는 순간 둘이 갈라진다는 것은 기억해둘 가치가 있다.

보레이트가 틀리면 무슨 일이 일어나는가

송신 쪽이 9600으로 한 바이트를 내보내는 동안, 수신 쪽이 19200으로 샘플링하면 한 비트 자리를 두 번씩 읽는다. 결과는 의미 없는 바이트 열이다. 0x48을 보냈는데 0xE3을 받는 식이다. 코드를 아무리 들여다봐도 원인이 안 보인다. 물리 계층의 타이밍이 어긋나면 상위 로직은 전부 거짓말을 하기 때문이다.

실무에서 보레이트를 확정하는 순서

첫째, 사양서를 본다. 이게 출발점이다. 하지만 앞에서 말했듯 사양서와 현장이 항상 일치하지는 않으므로, 그다음에는 장비의 DIP 스위치를 직접 열어서 확인한다. DIP 스위치의 조합은 장비 매뉴얼 뒷면이나 본체 측면 스티커에 표로 인쇄되어 있다. 이 두 가지가 충돌하면 장비 쪽이 진실이다. 일단 그보다 중요한 것은 요구한 원청에 따지는 것이다. 이걸 하지 않을 경우 납기 늦어짐에 덤터기를 쓰게 된다.

둘째, 레거시 코드가 있으면 거기 하드코딩된 값을 본다. 이전에 실제로 동작했던 코드라면 현장 값과 맞았다는 뜻이기 때문에 가장 신뢰할 수 있는 증거 중 하나다.

셋째, 그래도 안 되면 테스트로 값을 바꿔가며 시도한다. 9600, 19200, 38400, 115200 순서로 돌려보면 대부분 걸린다. 응답이 의미 있는 바이트로 보이기 시작하는 조합이 정답이다. 이 단계까지 오면 반나절은 이미 지나가 있다.

출장비는 이미 계약금에 포함되어있기에 사실상의 손해가 누적이 된다.

세 번째 약속 - 데이터 비트와 패리티와 정지 비트

"19200-8-N-1"이라는 표기에서 8-N-1이 이 세 값이다. 한 바이트를 실제로 선에 실을 때, 데이터 자체 말고도 부가 비트가 붙는다. 그 부가 비트의 구성이다.

LSB → MSB
  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──────┐
  │S │d0│d1│d2│d3│d4│d5│d6│d7│P │ Stop │
  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──────┘
   시작  데이터 8비트          패리티  정지

  S    = Start bit (항상 1비트)
  d0~7 = Data bits (보통 8, 때로 7)
  P    = Parity bit (없거나 Even/Odd)
  Stop = Stop bit (1 또는 2)

[그림 1] UART 시리얼 통신의 물리적 프레임 구조와 샘플링 타이밍. 스타트 비트의 하강 엣지를 기점으로 정해진 보레이트에 맞춰 각 비트의 중앙값을 읽어 들인다.

다이어그램 맨 앞의 스타트 비트(S)부터 짚고 넘어갈 필요가 있다. 시리얼 라인은 아무 데이터도 없을 때 HIGH(1) 상태로 대기한다. 송신 쪽이 데이터를 보내기 시작할 때 맨 먼저 LOW(0)를 한 비트 폭만큼 내보내는데, 이것이 스타트 비트다. 수신 쪽은 이 하강 엣지를 감지하는 순간 "지금부터 보레이트에 맞춰 비트를 샘플링하겠다"고 판단한다. 클록 선을 별도로 공유하지 않는 두 장치가 같은 타이밍으로 대화할 수 있는 이유가 이 스타트 비트에 있다.

데이터 비트는 한 바이트에 담기는 유효 비트 수다. 8이 압도적으로 흔하다. 7은 아주 오래된 ASCII 전용 장비에서만 본다. 요즘 새 장비는 거의 전부 8이다. 가끔 7을 이야기하는 선배들도 있는데, 소위 말하는 원시 유물에서나 볼 수 있다.

패리티 비트는 오류 검출용이다. None (없음), Even (짝수), Odd (홀수) 세 가지가 있다. Even이면 데이터 비트 중 1의 개수에 패리티 비트를 더한 값이 짝수가 되도록 송신 쪽이 맞춰서 보낸다. 수신 쪽은 받은 바이트의 1 개수가 짝수인지 검사해서 어긋나면 오류로 판정한다.

산업 현장에서는 None이 기본값이다. 이유는 패리티의 구조적 한계에 있다. Even 패리티를 예로 들면, 0b01001101은 1이 4개라 패리티 비트가 0으로 붙는다. 수신 쪽은 받은 9비트에서 1의 개수가 짝수인지만 확인한다. 딱 1비트가 뒤집히면 잡히지만, 2비트가 동시에 뒤집히면 1의 개수가 다시 짝수가 되어 오류를 그냥 통과시킨다. 오류를 검출만 할 뿐 수정도 못 한다. 반면 CRC-16이나 CRC-32는 다항식 나눗셈 기반이라 연속 오류 패턴까지 훨씬 넓은 범위를 커버한다. 상위 프로토콜에 CRC가 있으면 패리티는 실질적으로 기여하는 것이 없다.

정지 비트는 한 바이트가 끝났다는 표시다. 1이 기본이고, 오래된 저속 장비에서 수신 쪽 처리 시간을 벌어주려고 2를 쓰는 경우가 있다. 대부분의 현대 장비에서 1이면 충분하다.

세 값을 한 번에 읽는 법은 8-N-1 같은 표기를 그대로 해석하는 것이다. 앞의 숫자가 데이터 비트, 가운데 글자가 패리티(N/E/O), 뒤의 숫자가 정지 비트. 한 번 익혀두면 어느 매뉴얼을 봐도 같은 자리에 같은 정보가 있다.

그렇게 사양서를 바탕으로 예시 코드를 작성하면 다음과 같다.

// 사양서: COM4, 19200-8-N-1
var port = new SerialPort(
    portName : "COM4",
    baudRate : 19200,
    parity   : Parity.None,   // N
    dataBits : 8,             // 8
    stopBits : StopBits.One   // 1
);

세 값이 어긋났을 때 나타나는 증상은 다르다.

데이터 비트가 틀리면 - 7비트 장비에 8비트로 연결하면 매 바이트의 최상위 비트 처리가 달라져서 수신값이 128씩 뒤틀린다. 0x41('A')을 보냈는데 0xC1로 읽히는 식이다.

패리티가 틀리면 - 장비가 Even으로 보내는데 None으로 받으면 패리티 비트가 데이터 비트로 편입되어 9비트짜리 쓰레기가 들어온다. SerialPort는 이 상황을 SerialError 이벤트의 RXParity 플래그로 알릴 수 있다.

정지 비트가 틀리면 - 장비가 스톱 비트 2개를 내보내는데 1개로 받으면 다음 스타트 비트를 정지 비트로 오해한다. 프레이밍 에러가 나고 FrameError 플래그가 뜬다.

패리티를 None으로 쓰는 현장에서는 패리티 에러 자체가 발생하지 않지만, 프레이밍 에러나 오버런은 여전히 날 수 있다. 진단 코드를 붙여두면 통신 이상 원인을 절반은 줄일 수 있다.

port.ErrorReceived += (_, e) =>
{
    // 현장에서 이 로그가 뜨면 케이블·보레이트·정지비트부터 의심한다
    File.AppendAllText("serial_error.log",
        $"[{DateTime.Now:HH:mm:ss.fff}] SerialError={e.EventType}\n");
};

port.Open();

ErrorReceived는 스레드를 소비하지 않고 이벤트만 올라오기 때문에 운영 코드에 붙여둬도 부담이 없다. 실제 통신 흐름에는 영향을 주지 않으면서 현장 로그에 단서를 남긴다.

참고로, 산업 현장에서 None이 기본값인 데는 이유가 있다. 패리티 비트는 오류를 검출만 하고 수정하지 못한다. 1비트 오류는 잡히지만 2비트 동시 오류는 통과된다. 그래서 상위 프로토콜이 자체 오류 검증을 갖추는 방향으로 설계된다.

PLC 통신이라면 MC Protocol 프레임 자체에 체크섬이 붙어 있다. 바코드 스캐너라면 CR/LF 종단자와 응답 포맷 파싱이 오류를 걸러낸다. 패리티 비트가 끼어들 자리가 없다.

그래서 현장 코드에는 SerialError 이벤트나 RXParity 플래그를 구독하는 코드가 없는 경우가 많다. Parity.None을 쓰는 이상 패리티 에러 자체가 발생하지 않으니 잡을 것도 없다. 프레이밍 에러나 오버런은 여전히 날 수 있지만, 현장에서는 그보다 보레이트 불일치나 케이블 접촉 불량을 먼저 의심하는 편이 현실적이다. 다만, 이걸 자신있게 말하면 계약서를 쓸때 담당자가 널 더 좋게 보는 경향성은 있었다.

// 현장 코드의 전형적인 모습 — ErrorReceived 없음
var port = new SerialPort("COM4", 19200, Parity.None, 8, StopBits.One);
port.Open();
// 오류 검출은 상위 프로토콜(체크섬, 응답 포맷 파싱)에서 담당한다

패리티를 Even이나 Odd로 쓰는 장비를 만나면 그때 ErrorReceived를 붙인다. 그 전까지는 없어도 된다.

3가지 약속을 C# 코드로

위에 언급한 3개의 값을 모두 맞춰서 실제 포트를 여는 최소 코드는 C#기준으로 이렇게 생겼다.

using System.IO.Ports;

var port = new SerialPort
{
    PortName  = "COM4",
    BaudRate  = 19200,
    DataBits  = 8,
    Parity    = Parity.None,
    StopBits  = StopBits.One,
    Handshake = Handshake.None,
    ReadTimeout  = 500,
    WriteTimeout = 500,
};

port.Open();
port.Write(new byte[] { 0x05 }, 0, 1);
port.Close();

Handshake.None과 타임아웃 두 개가 추가로 붙었다. Handshake는 흐름 제어다. 옛날 프린터 같은 장비에서 RTS/CTS 핀으로 "나 지금 바쁘니까 잠깐 기다려"라고 신호하는 용도였는데, 요즘 산업 장비는 대부분 이것을 쓰지 않는다. None으로 두면 안전하다.

타임아웃은 네트워크 프로그래밍과 비슷한 의미다. 물리 계층이 죽어도 애플리케이션이 영원히 기다리지 않게 하는 장치다. 500ms 정도가 무난하다. 너무 짧으면 장비가 응답을 준비하는 시간도 기다려주지 못하고, 너무 길면 장애 감지가 늦는다.

이 코드가 예외 없이 Open()을 통과하면 물리 계층 단계는 넘은 것이다. 장비가 실제로 응답을 주는지는 또 다른 이야기지만, 포트와 세 가지 약속이 맞는 것은 확인된다.

현장 세팅이 안 맞을 때 보이는 증상들

현장에서 만나는 대표 증상을 표로 정리하면 진단이 빨라진다.

증상

가장 흔한 원인

FileNotFoundException / Could not find file 'COM4'

포트 이름 자체가 틀렸거나, USB 변환기가 빠져서 번호가 사라짐

UnauthorizedAccessException

다른 프로세스가 이미 포트 점유. 좀비 앱이나 터치스크린 드라이버 확인

바이트는 오는데 의미가 없음

보레이트 또는 데이터 비트 불일치

간헐적으로 한 두 바이트씩 깨짐

패리티 설정 차이, 또는 선 자체의 물리 문제

보낸 바이트가 그대로 돌아옴 (에코)

포트와 보레이트는 맞지만 물리 계층 종류가 틀림 (RS-232 ↔ RS-485)

포트는 열리는데 응답이 없음

선은 맞았지만 장비 쪽 주소·프로토콜이 다름

마지막 "에코가 돌아오는" 증상은 이 시리즈 2편의 핵심 주제다. 포트와 보레이트까지 맞췄는데도 장비가 대답을 안 하고 내가 보낸 바이트가 그대로 돌아온다면, 그건 RS-232와 RS-485라는 물리 계층 종류의 차이일 가능성이 매우 높다. 그 이야기는 다음 편에서 다룬다.

정리

시리얼 통신의 세 가지 약속을 한 번 더 정리하면 이렇다.

첫째, 포트는 어느 통로로 말할지 정한다. Windows에서는 COM 번호로 관리하고, 실제로 어떤 물리 커넥터인지는 장치 관리자의 DeviceID로 확인한다. 포트 점유 문제와 좀비 프로세스 문제에 미리 대비해야 한다.

둘째, 보레이트는 초당 몇 비트를 주고받을지 정한다. 9600, 19200, 115200이 관습적인 값이고, 이 값들의 계보는 초기 UART 칩의 분주 구조에서 왔다. 보레이트가 틀리면 바이트는 오지만 의미가 없다.

셋째, 데이터 비트·패리티·정지 비트는 한 바이트의 프레임 구조를 정한다. 산업 현장의 기본값은 8-N-1이고, 이 표기 하나로 세 값을 한 번에 읽을 수 있다.

이 3가지가 맞으면 물리 계층의 절반을 통과한 것이다. 나머지 절반은 전기 신호의 종류인데, 그것이 다음 편의 주제다. 같은 COM 포트에 같은 보레이트로 꽂았는데도 에코만 돌아온다면, 범인은 거기 있다.