시리얼통신

Posted by 나에요임마
2017. 10. 2. 16:59 Program/C#

 개요

웹 개발자등과 같은 개발자들에겐 생소하거나 쓸일이 없는 녀석이 시리얼통신 입니다. 하지만 하드웨어 개발자이거나 하드웨어와 통신을 하는 개발자들에겐 시리얼통신은 통신의 기본이 되는 녀석이라 안쓸래야 안쓸 수가 없습니다. 하드웨어적으로 정말 구현하기도 쉽거니와 통신방법도 간단하기 때문에 디버깅 로그 출력등으로 자주 사용하지요. 하지만 관련 직종이 아니거나 처음 사용해보는 이들에겐 이게 도대체 무슨 개소리인가 싶을저도로 난해하게 느껴질 수 있습니다.

그래서 시리얼통신에 대해 원리부터 구현까지 이해를 돕도록 하기 위한 문서를 만들어보았습니다. 많은 도움이 되길 기대하겠습니다.



0. 시리얼통신이 무엇인가?

시리얼 + 통신 = 시리얼 통신 입니다. '시리얼'이란 우걱우걱 먹는 시리얼이 아니라 데이터를 주고 받는 통신의 한 방법을 의미합니다. 통신은 크게 시리얼(직렬)/페러럴(병렬) 이렇게 두가지 형태로 나뉘게 되는데 시리얼(직렬)은 하나의 회선을 가지고 긴 데이터를 하나씩 차례대로 보내는 방식이고, 페러럴(병렬)은 여러 회선을 통해 한번에 데이터를 보내는 방식입니다.



이해하기 쉽도록 위 이미지를 보시죠. 위의 시리얼 방식은 데이터를 하나의 선에 하나씩 차례대로 보내고 있고, 아래의 페러럴 방식은 여러 데이터를 여러 선을 통해 한번에 보내고 있습니다. 

그럼 각각의 장단점은 무엇일까요. 시리얼은 하나의 선만 쓰기 때문에 하드웨어적으로 매우 간편해서 만들기 쉽습니다. 가격이 싸기도 하구요. 하지만 데이터를 모으고 취합하는 과정을 구현해주어야 합니다. 반면에 페러럴은 여러선을 쓰기 때문에 한번에 대량의 데이터를 주고 받아 속도가 빠릅니다. 그러나 선이 많기 때문에 가격이나 하드웨적인 구현면에서 시리얼보다 불편한 편이죠.

이런 두개의 방식중 우리는 '시리얼'통신을 할 것입니다. 시리얼 통신도 여러 규격이 존재하는데 가장 많이 쓰는것이 RS-232 방식입니다. 이건 또 뭔가 싶으니 이것에 대해서도 알아봅시다.

0-1. RS-232 및 기타 규격

RS의 의미는 저도 정확히 모르겠습니다만 Receive(수신), Send(송신)의 첫글자가 아닌가 싶습니다. 즉 송신하고 수신하는 232 규격이라는 이야깁니다. 이외에도 유명한것이 RS-422, RS-485 등이 있습니다. RS 규격은 과거 컴퓨터와 인터페이스 기기(모뎀, 음향기기 등)와의 통신을 위해 만들어진 좀 오래된 규격입니다. 그래서 PC에선 이제 거의 사용하지 않게 되었지만(물리적 포트 자체가 없습니다. 그래서 요즘은 USB to Serial 컨버터를 사용합니다.) PC가 아닌 제품에서는 그 만들기 쉽다는 간편함 때문에 여전히 사용중에 있습니다.



연결 방법은 위 이미지를 보면서 설명 해보겠습니다. RS-232의 초기 규격은 25핀 커넥터를 사용하는 크고 아름다운 녀석이었습니다만 요즘에는 9핀만 사용하는 녀석이 대다수입니다. 그리고 그 9핀중 3핀만 연결하면 통신하는데는 전혀 문제가 없습니다.

DTE와 DCE가 보이시죠? 이는 통신 네트워크 상의 용어로 네트워크의 종단(데이터의 최종 수신부나 송신부)의 물리적인 디바이스를 의미합니다. 주로 PC와 같은 제어용 컴퓨터가 주를 이루게 되지요. DCE는 네트워크의 종단이거나 중간에 존재하며 데이터를 처리하는 녀석입니다. 즉 타겟이 되는 디바이스(모뎀이나 음향기기 또는 기타 디바이스)를 의미하게 되죠. (참조: http://mintnlatte.tistory.com/452 )

다시 위 이미지로 돌아거서 주로 쓰는 선은 3선입니다. 2번, 3번, 5번 핀이죠.

2번pin : Receive data line(또는 rx). 즉 DCE를 기준으로 물리적으로 실제 데이터를 받는 선입니다.
3번pin : Tansmit(send 또는 tx) data line. DCE를 기준으로 물리적으로 실제 데이터를 보내는 선입니다.
5번pin : 데이터의 기준(ground)이 되는 선입니다.

2, 3번은 송,수신이라는 건 이해가 가는데 '기준'은 또 뭔 소린가?!

0번 항목에서 이야기 했듯이 RS-232 통신은 '시리얼' 통신 입니다. 한정된 회선에서 데이터를 '차례대로' 보내고 받는 방식이란 이야깁니다.

원론적으로 들어가서 데이터란게 거창해보이지만 결국 데이터는 0 과 1 두가지 상태의 조합입니다. 데이터가 있거나 없거나의 의미가 아니라 현재 상태가 높으냐(High) 낮냐(Low)의 의미입니다. 이는 전자관련 전공을 배우지 않으면 사실 이해하기 힘들긴 합니다만 최대한 간단히 설명해보겠습니다.

수학에서 2진수를 생각해봅시다. 십진수 숫자 6은 2진수로 표시하면 110 이죠? 몇번째 bit에 1이냐 0이냐에 따라 값이 달라지게 됩니다. 그럼 시리얼 통신으로 이 6이란 값을 보낸다고 한다면 우리는 1, 1, 0을 차례대로 보낸뒤에 수신해서 이를 다시 110으로 합치면 6이란 데이터를 받게 된다는 겁니다. 그런데 0 과 1을 물리적으로 어떻게 구분할 것이냐 라는 문제가 생기게 되는데 결론부터 말씀드리자면 '전기의 세기'로 구분합니다.

우리가 흔히 전압(Volts)을 이야기 하죠? 220v다 110v다 하는거 말입니다. 이 전압이 전기의 세기입니다. 그런데 이 전압으로 어떻게 0과 1을 구분하느냐를 놓고 많은 사람들이 고민한 결과 RS-232에선 2.4V 또는 2.8V를 기준으로 이보다 높으면 1, 이보다 낮으면 0으로 하기로 결정합니다(이런걸 '규격'이라고 하지요).

즉. 현재 선에 5V가 흐르면  2.8V보다 높기 때문에 1. 1V가 흐르면 2.8V보다 낮기 때문에 0이라고 인식하게 되는거죠. 하지만 전압은 어떤 기준점을 필요로 합니다. 기준이 되는 녀석에서 + / - 하여 전압을 표시하게 되는거죠. 만약 이 기준이 설정되지 않았다고 생각해봅시다. A라는 기기에서는 기준이 10V입니다. 그럼 5V가 인가되었다는 소리는 실제 기준에서 5V 높다는 의미입니다(즉 15V). B라는 기기에서는 기준이 0V입니다. 그럼 5V가 인가되었다는 소리는 실제 기준에서 5V라는 의미입니다. 기준이 다르니 같은 5V라도 실제 전압이 다르게 나오게 되는겁니다. 그럼 데이터 주고 받을때 어떤게 기준이 될지 모호해지는겁니다.

그래서 RS-232에서는 기준(Ground) 핀을 서로 연결합니다(즉 5번핀). 이렇게 서로를 연결하여 같은 전압을 기준으로 삼자는 선이 이녀석이 하는 일입니다. 기준점의 전압으로 2, 3번 핀에 걸린 전압의 상태에 따라 높냐(1), 낮냐(0)가 결정되어 그 상태에 따라 데이터를 인식하게 되는거죠.

이해 가시나요? 어렵습니다 ㅎㅎㅎ. 하지만 이보다 쉽게 설명하기란 좀 어렵네요. 물리적으로 실제로 데이터가 움직이는게 아닌 RX(수신), TX(송신)선에 걸린 전압에 따라 데이터를 분석하는것이기 때문에 엄밀히 말하면 송수신이라기보단 상태를 확인(Check)하는데 가깝습니다만 통신이란게 애초에 주고 받는다는 개념이니 그냥 그런거라고 생각해두시면 편합니다.(포기하면 편해요 여러분)



1. 실제로 구현해보자.

하드웨어가 준비부터 해보죠. 일단 DTE가 되는 PC와 DCE가 되는 타겟 디바이스(PC라도 상관없습니다)를 준비하시고(한대의 PC로도 가능합니다), 연결 Cable을 준비합시다.(시중에 파는것을 쓰셔도 되고 직접 만드셔도 됩니다. 애초에 이 게시물을 보신다면 그정도는 알고 있다는 가정을 합니다)

하지만 요즘 PC에는 시리얼 커넥터가 없기 때문에 우리는 아래와 USB to Serial 컨버터를 사용합니다.


요즘은 그냥 꼽기만 하면 자동으로 드라이버가 잡히기 때문에 별도로 설치하실 드라이버는 없습니다. 두개의 USB 포트에 두개의 USB to Serial 컨버터를 꽂아서 서로 통신하게 만들 생각입니다. 즉 개념도를 그려보면 아래와 같습니다.

그리고 우리가 이제 앞으로 할것이 컴퓨터(PC)에서 C#으로 시리얼통신하는 프로그램을 제작하여 USB to Serial을 통해 시리얼통신을 해보는것입니다.


1-1. 프로젝트 생성



일단 사용한 Tool은 Visual Studio 2015 Community 입니다. 개인 개발자라면 무료로 사용하기 때문에 인터넷 검색하시면 받으실 수 있습니다. 그중에서 C# - Windows Forms 응용 프로그램을 선택하여 프로젝트를 하나 만들어줍시다.



그리고 나서 폼 디자인은 이런식으로 해둡시다. 일단 해당 폼에서 사전에서 설정 및 인지 해두실것은 각 컴포넌트의 Name과 좌측 Settings의 항목들입니다.

일단 Port의 콤보박스 아이템은 비어두시고,

BaudRate 에는
115200

19200
38400
57600
9600

data 에는
8
7
6

parity 에는
none
even
mark
odd
space
 

handshake 에는

none
Xon/Xoff
request to send
request to send Xon/Xoff

을 넣어두시면 됩니다. 각 항목의 의미를 설명해보죠.


Port 는 Com port 또는 Port name 이라고도 하는데 일종의 '대문'이름입니다. 지하철 역을 상상해봅시다. 지하철역에는 여러개의 출입구가 있는데 1번 2번 3번 4번 출입구는 위치만 다를뿐이지 다 지하철역으로 이어지죠? Port는 그것과 비슷합니다. 같은 역할을 하는 녀석들이지만 입구마다 이름이 붙어서 각자 정해진 입구로만 지나다닐 수 있습니다. 일반적으로 COM1, COM2 이런식으로 표시되는데 뒤의 숫자가 출입구 번호입니다. 예를 들어 COM1로 연결한 시리얼 통신은 무조건 COM1을 통해서만 데이터를 주고 받을 수 있습니다.

BaudRate는 '통신 속도'입니다. 시리얼 통신은 0번에서 언급했듯이 데이터를 한개씩 차례대로 보낸다고 말씀드렸는데 이때 데이터가 차례대로 들어가는 속도를 BaudRate라고 합니다. 단위는 bps(bit per second)이며 초당 몇 bit를 보내는지에 대해 표기합니다. 9600이라면 초당 9600비트 즉 1200바이트(9600 / 8)를 보낼 수 있는겁니다. 아주 빠른거 같지만 사실 옛날에나 그렇지 요즘 같은 시대에 1200바이트가 1초에 들어간다고 하면 엄청 느린겁니다. 앞서 이야기 한것처럼 시리얼은 간단한 대신 속도가 느리다 라는거 알고 계시죠? 간단한 데이터 주고 받는데는 문제가 없지만 아주 빠르게 대량의 데이터를 주고 받는데는 적합하지 않습니다. 이때는 이더넷 통신, 1932 통신등 다른 방식을 쓰셔야 합니다.

data(data size)는 packet(데이터의 모음 또는 틀)의 크기를 정의합니다. 시리얼은 한번에 하나의 비트를 보내지만 그건 물리적인 통신의 개념이고 실제로 구현할땐 여러개의 비트의 모음을 짜서 보냅니다. 이를 패킷(packet)이라고 하는데, 8로 설정하면 data를 8bit 씩 보낸다는 겁니다.

>>>1, 1, 1, 0, 1, 0, 1, 1 >>>1, 1, 0, 0, 1, 1, 1, 1 >>> 0, 1, 1, 1, 0, 1, 1, 1 이런식으루요.


왜 이렇게 하냐하면...규격이라서 그렇습니다. 시리얼 통신은 간편한 만큼 데이터의 무결성이 보장되지 않습니다. 즉 외부적인 요소로 중간에 데이터가 변질 또는 사라지더라도 송수신부는 알 방법이 없는겁니다. 그래서 애초에 패킷을 만들어서 정해진 틀(패킷)로 보내면 이를 확인 할 수 있게 됩는 겁니다. 아래 그림을 보시죠.



이것이 시리얼 통신의 기본 패킷 규격입니다. 프레임(Packet)이 Start와 Data와 Parity와 Stop으로 이루어진게 보이시죠?

물리적으로 Start 신호를 받으면 수신측에서 수신대기에 들어갑니다. 그리고 이어서 오는 Data를 DataSize 만큼 받은뒤에 Parity를 확인하고 Stop 신호를 받으면 하나의 패킷이 종료되었다는걸 알게되는겁니다. 예를 들자면 군대에서 전화를 한다고 해봅시다. 전화를 받은측에서 '통신보안'이라고 이야기 하잖아요? 즉 보안상태에서 전화를 받을 준비가 되었다는 의미로 씁니다(사실 그런 생각을 안하는거 같지만...) 이게 안되면 전화상으로 말을 해서는 안되죠. 송신측도 마찬가지구요. 이런 양방간에 정해진 규칙을 지켜서 보안을 지키는것처럼 시리얼통신도 정해진 규칙을 지켜서 데이터의 무결성을 보장하는겁니다.

parity는 data의 올바른지 확인하는 값입니다. 여러가지 방식이 있습니다. 홀수 비트의 합계의 마지막 비트가 동일한지 확인하거나 짝수 비트만 확인하거나 전체 비트를 확인하거나 뭐 이렇게 계산해서 나온 데이터가 Parity 비트에 들어가게 되는것이죠. 우리는 그런거 안합니다 None 합시다 =_=. 옛날에는 워낙 하드웨어가 구려서 데이터가 사라지는 문제가 많았는데 요즘은 그런거 잘 없습니다. 아주 노후되었거나 강력한 외부 요인이 존재하는 곳 아니면(공장이라거나) 별 문제없습니다. 

handshake는 만약 패킷이 잘못된 상태(패리티가 계산했는데 안맞는 상황)에서 사용되는 녀석으로 다시 보내거나 현재 상태를 파악하기 위해서 사용하는 녀석입니다. 뭐...이놈도 요즘 안씁니다. 왜냐하면 우리는 2, 3, 5번 핀 3선으로 사용하는데 handshake를 쓸려면 0번에서 RS-232 9핀 규격 전체를 써야 하기 때문입니다.

그리고 위에 언급되지 않았지만 또 하나가 Stop bits 란게 있습니다. Stop bit가 몇 비트로 이루어졌냐에 대한 설정입니다. 기본값은 1bit인데 원하면 2bit로 설정할 수도 있습니다.


1-2 프로그래밍(첨부한 프로젝트 참조)

1-2-1
가장 먼저 FormLoad 이벤트를 하나 추가해봅시다. Form을 더블클릭하면 FormLoad event가 생성됩니다. 해당 이벤트에서 우리는 폼에 올려져 있는 컨트롤들을 초기화할려 합니다.(90번 line 참조)

private void SerialComm_Load(object sender, EventArgs e)
{
            // 시리얼포트 목록 갱신
            cbComPort.DataSource = SerialPort.GetPortNames();
            // 기타 셋팅 목록 기본값 선택
            cbBaudRate.SelectedIndex = 0;
            cbDataSize.SelectedIndex = 0;
            cbParity.SelectedIndex = 0;
            cbHandShake.SelectedIndex = 0;
}


cbComport 는 ComPort 목록을 넣어두는 콤보박스를 의미합니다. 해당 컨트롤에 SerialPort.GetPortNames()를 통해 현재 활성화된 포트 목록(COM1, COM2와 같은 출입구 기억하시죠?)을 가져와서 등록해줍니다.
아래 SelectedIndex = 0 항목들은 각각의 설정 콤보박스들의 기본값을 첫번째 항목으로 하라는 의미입니다.

1-2-2
두번째로 SerialPort를 관리할 객체를 생성하는 겁니다. C#은 친절하게도 시리얼통신 관련 클래스를 제공하기 때문에 매우 쉽게 구현 가능합니다. (17번 line 참조)

private SerialPort _Port;
/// <summary>
/// 시리얼포트 컨트롤 객체
/// </summary>
private SerialPort Port
{
    get
    {
        if (_Port == null)
        {
            _Port = new SerialPort();
            _Port.PortName = "COM1";
            _Port.BaudRate = 9600;
            _Port.DataBits = 8;
            _Port.Parity = Parity.None;
            _Port.Handshake = Handshake.None;
            _Port.StopBits = StopBits.One;
            _Port.DataReceived += Port_DataReceived;
        }
        return _Port;
    }
}


_Port라는 객체를 선언하고 Port라는 속성(Property)를 구현하였습니다. Port를 호출했을때 _Port Null 상태면 객체를 생성하고 초기화해준뒤에 전달해줍니다. Null 상태가 아니면 _Port 객체를 그냥 전달해주구요. 이렇게 속성(Property)는 객체의 초기화에 자주 사용됩니다. 이중에 유의깊게 보실것이 _Port.DataReceived += Port_DataReceived 입니다. 이는 시리얼 포트를 통해 데이터가 수신되면 발생하는 이벤트로 저렇게 등록을 해주셔야 해당 이벤트가 발생하게 됩니다.


1-2-3
객체를 생성했다면 연결 버튼을 이용해서 실제로 연결 해보기로 하죠. 연결 버튼(btConnectControl)을 더블 클릭하면 클릭 이벤트가 생성됩니다. 하지만 그전에 상태 표시를 위한 속성들을 구현하였습니다.

/// <summary>
/// 시리얼포트 상태 및 컨트롤 제어
/// </summary>
private Boolean IsOpen
{
    get { return Port.IsOpen; }
    set
    {
        if (value)
        {
            Strings = "연결 됨";
            btConnectControl.Text = "연결 끊기";
            tbRecvMessage.Enabled = true;
            tbSendMessage.Enabled = true;
            btSendMessage.Enabled = true;
            gbSettings.Enabled = false;
        }
        else
        {
            Strings = "연결 해제됨";
            btConnectControl.Text = "연결";
            tbRecvMessage.Enabled = false;
            tbSendMessage.Enabled = false;
            btSendMessage.Enabled = false;
            gbSettings.Enabled = true;
        }
    }
}
/* 로그 제어 */        
private StringBuilder _Strings;
/// <summary>
/// 로그 객체
/// </summary>
private String Strings
{
    set
    {
        if (_Strings == null)
            _Strings = new StringBuilder(1024);
        // 로그 길이가 1024자가 되면 이전 로그 삭제
        if (_Strings.Length >= (1024 - value.Length))
            _Strings.Clear();
        // 로그 추가 및 화면 표시
        _Strings.AppendLine(value);
        tbRecvMessage.Text = _Strings.ToString();
    }
}


IsOpen 속성과 Strins 속성을 구현하였습니다. IsOpen은 시리얼포트가 열렸는지 닫혔는지에 대한 상태를 가져오거나 또는 현재 상태를 집어넣음으로서 화면상의 컨트롤들의 상태를 변경 할 수 있습니다. Strings 속성은 로그를 저장하고 화면에 표시하는 역할을 합니다. TextBuilder 를 사용함으로서 String + String 보다 나은 성능을 보여주고 길이 체크를 통해 자동 Clear 기능도 추가하였습니다.

private void btConnectControl_Click(object sender, EventArgs e)
{
    if(!Port.IsOpen)
    {
        // 현재 시리얼이 연결된 상태가 아니면 연결.
        Port.PortName = cbComPort.SelectedItem.ToString();
        Port.BaudRate = Convert.ToInt32(cbBaudRate.SelectedItem);
        Port.DataBits = Convert.ToInt32(cbDataSize.SelectedItem);
        Port.Parity = (Parity)cbParity.SelectedIndex;
        Port.Handshake = (Handshake)cbHandShake.SelectedIndex;

        try
        {
            // 연결
            Port.Open();
        }
        catch (Exception ex) { Strings = String.Format("[ERR] {0}", ex.Message); }
    }
    else
    {
        // 현재 시리얼이 연결 상태이면 연결 해제
        Port.Close();
    }

    // 상태 변경
    IsOpen = Port.IsOpen;
}


연결 버튼에 대한 이벤트입니다. 현재 연결되지 않은 상태면 화면의 셋팅값들을 가져와 시리얼 포트에 연결합니다. 연결중에 예외가 발생하면(이미 사용중이거나 하는 문제) 로그를 화면에 표시합니다. 이미 연결된 상태면 연결을 해제 합니다. 그 이후에 IsOpen에 현재 상태를 넣음으로서 컨트롤들의 상태를 변화시킵니다.


1-2-4
마지막으로 데이터를 주고 받는 부분을 구현하겠습니다. (106 line)

private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    String msg = Port.ReadExisting();

    this.Invoke(new EventHandler(delegate
    {
        Strings = String.Format("[RECV] {0}", msg);
    }));
}


먼저 수신부입니다. Port.ReadExistring() 메소드를 이용하여 현재 수신된 데이터를 버퍼에서 몽땅 가져옵니다. 물리적으로 정상적으로 수신된 데이터들은 버퍼에 쌓이게 되는데 해당 버퍼는 각각의 포트들이 별도로 가지고 있으며 수신측에서 가져가기 전까지는 사라지지 않고 있습니다(단 버퍼 크기를 넘어서면 이전 데이터는 삭제 됩니다). ReadExistring 메소드는 이 버퍼안의 모든 데이터를 String 형태로 가져옵니다. 만약 수신된 데이터가 String 이 아니라면 Read() 메소드나 ReadByte() 와 같은 메소드로 하나씩 가져올 수도 있습니다.(나중에 추가 하겠습니다)

this.invoke(new EventHandler(delegate{  ...  })); 

항목은 대리자를 통해 현재 폼의 상태를 변경 시킬때 주로 씁니다. 왜 바로 컨트롤의 상태를 바꿔주면 안되냐 하면 다른 쓰레드(Thread)끼리는 서로의 데이터 안전성을 위해 바로 접근 할 수 없습니다.

즉 폼을 관리하는 메인 쓰레드와 데이터의 수신을 관리하는 수신 쓰레드는 서로 다른 쓰레드이기 떄문에 수신 쓰레드에서 메인 쓰레드의 폼의 상태를 바로 변경하지 못하도록 막아둔겁니다. 이를 해결하기 위해서 생긴것이 대리자로 다른 쓰레드에게 "야 너 네 차례가 오면 이 일을 해" 라고 시키는 겁니다. 수신 쓰레드에서 폼의 상태를 변경하라고 대리자를 통해 시켜두면, 메인 쓰레드 차례가 돌아왔을때 이 일을 처리하게 되는것이죠.


private void btSendMessage_Click(object sender, EventArgs e)
{
    // 보낼 메시지가 없으면 종료
    String text = tbSendMessage.Text.Trim();
    if (String.IsNullOrEmpty(text)) return;

    try
    {
        // 메시지 전송
        Port.WriteLine(text);
        // 표시
        Strings = String.Format("[SEND] {0}", text);
    }
    catch (Exception ex) { Strings = String.Format("[ERR] {0}", ex.Message); }
}


송신 이벤트(btSendMessage를 더블클릭해서 생성합시다)를 구현하였습니다. 전송 버튼 좌측의 텍스트박스에 입력된 데이터를 시리얼포트로 전송하는 겁니다. Port.WriteLine 을 통해 String 텍스트를 일괄 전송하게 되는것이죠. Port에는 여러가지 Send 메소드들이 존재하는데 Write와 WriteLine입니다. WriteLine은 Write와 유사하나 마지막에 라인Feed 바이트가 추가되서 전송됩니다. String 데이터를 보낼땐 WriteLine을 일단 data를 보낼땐 Write를 주로사용합니다.


1-3
프로그램 코드는 사실상 이게 끝입니다. 로그 처리 부분을 빼면 정말 정말 몇줄 안됩니다. 이제 테스트를 해보죠.



좌측 COM4로 연결하여 데이터를 전송하면 외부로 연결된 선을 통해(UsbConverter <-> UsbConverter) COM5로 데이터가 들어가게 되고 우측의 프로그램에서 수신받게 되는것이죠. 하지만 한글을 전송하면 받은측에서는 깨져서 나옵니다. 이는 텍스트 인코딩 문제로 영어는 한글자에 1byte이고, 한글은 한글자에 2byte인데 프로그램이 영어 기준인 1byte 기준으로 만들어져서 깨지는겁니다. 고쳐 보도록 하죠.

private SerialPort Port
{
    get
    {
        if (_Port == null)
        {
            _Port = new SerialPort();
            // 기본값
            _Port.PortName = "COM1";
            _Port.BaudRate = 9600;
            _Port.DataBits = 8;
            _Port.Parity = Parity.None;
            _Port.Handshake = Handshake.None;
            _Port.StopBits = StopBits.One;
            _Port.Encoding = Encoding.UTF8;
            // 이벤트 등록
            _Port.DataReceived += Port_DataReceived;
        }
        return _Port;
    }
}


시리얼포트 객체 속성(Property) 선언한 부분에 빨간색 부분을 추가합니다. 이는 기본값인 ASCII 인코딩이 아닌 UTF-8 인코딩으로 통신을 하겠다는 설정입니다. UTF-8은 2byte 기준의 인코딩방식입니다.



테스트 해보면 한글이 정상적으로 나오는것을 볼 수 있습니다.



기초적인 시리얼 통신 프로그램을 구현이 끝났습니다. 어렵지 않습니다 여러분. 하지만 실제 현장에서의 시리얼통신은 이것만으로 끝나지 않습니다. 통신 데이터의 검증, 빅 데이터에 대한 처리, 수신 이벤트의 최적화등 다양한 응용을 구현해주어야 원할하게 돌아가게 됩니다. 이부분은 뭐...하다보면 느는거라서 =_=;; 다음에 기회되면 응용편도 올려보겠습니다. 실제 실무에서(그래봐야 제가 사용하는 구린 코드지만) 사용하는 방식으로 구현해보고 주석을 설명하는 정도를 해보면 좋겠네요.