본문 바로가기

프로그래밍/C#

C# 시리얼통신을 뚫어보자(송신/수신, SerialPort, ReceiveData, Invoke)

반응형

 

C# 시리얼통신을 뚫어보자(송신/수신, SerialPort, ReceiveData, Invoke)

 

PC와 외부 장치를 연결하기 위해 GUI를 만드는 중에
시리얼통신 수신 부분이 막혀서
인터넷을 검색하고 검색하여 이해한 내용을 정리하려고 한다.

 

 

 

위와 같이 시리얼통신을 위한 폼을 하나 만들었다.
COM 포트를 설정할 수 있는 콤보박스.
연결 버튼, 해제 버튼, 상태표시 레이블.
수신창.
송신창, 보내기 버튼.
serialPort1 컨트롤.

 

 

 

중요한 부분들을 하나씩 살펴보자.

 

1. using System.IO.Ports; 를 추가해준다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  //시리얼통신을 위해 추가해줘야 함

 

 

2. COM포트 이름을 가져온다.

private void Form1_Load(object sender, EventArgs e)  //폼이 로드되면
{
	comboBox_port.DataSource = SerialPort.GetPortNames(); //연결 가능한 시리얼포트 이름을 콤보박스에 가져오기 
}

DataSource, GetPortNames를 통해서 연결 가능한 시리얼포트 이름을 COM포트설정 콤보박스에 가져온다.

 

 

3. 연결하기 버튼을 눌렀을 때, 시리얼통신 설정 및 포트 연결

private void Button_connect_Click(object sender, EventArgs e)  //통신 연결하기 버튼
{
    if (!serialPort1.IsOpen)  //시리얼포트가 열려 있지 않으면
    {
        serialPort1.PortName = comboBox_port.Text;  //콤보박스의 선택된 COM포트명을 시리얼포트명으로 지정
        serialPort1.BaudRate = 9600;  //보레이트 변경이 필요하면 숫자 변경하기
        serialPort1.DataBits = 8;
        serialPort1.StopBits = StopBits.One;
        serialPort1.Parity = Parity.None;
        serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived); //이것이 꼭 필요하다

        serialPort1.Open();  //시리얼포트 열기

        label_status.Text = "포트가 열렸습니다.";
        comboBox_port.Enabled = false;  //COM포트설정 콤보박스 비활성화
    }
    else  //시리얼포트가 열려 있으면
    {
        label_status.Text = "포트가 이미 열려 있습니다.";
    }
}

연결하기 버튼을 클릭하면 위 코드가 실행되는데,

if(포트가 열려 있지 않으면(연결되어 있지 않으면))

    시리얼포트 설정에 필요한 것들을 지정해 주고,

    포트를 열고,

    포트가 열렸다는 문구를 출력하고,

    COM포트설정 콤보박스를 비활성화 한다.

else(반대로 포트가 열려 있으면(연결되어 있으면))

    포트가 이미 열려 있다는 문구를 출력한다.

 

시리얼포트 설정에 필요한 것들이다.

serialPort1.PortName = comboBox_port.Text;  //콤보박스의 선택된 COM포트명을 시리얼포트명으로 지정
serialPort1.BaudRate = 9600;  //보레이트 변경이 필요하면 숫자 변경하기
serialPort1.DataBits = 8;
serialPort1.StopBits = StopBits.One;
serialPort1.Parity = Parity.None;
serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived); //이것이 꼭 필요하다

PortName은 이미 COM포트설정 콤보박스에서 선택된 이름이다.

나머지 BaudRate, DataBits, StopBits, Parity 등도 콤보박스를 만들어 지정해도 되지만 최대한 간단히 하려고 콤보박스는 생략했다.

그리고 중요한 것, 마지막 줄의 serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived); 이 부분이 들어가야 시리얼 데이타 수신 시 수신 처리를 할 수 있게 된다. 수신 데이타가 들어오면 serialPort1_DataReceived 함수?에서 처리를 할 것이다.

 

 

4. 시리얼통신 수신 시 쓰레드 충돌을 피하기 위해 Invoke를 사용한다.

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) 
{            
    this.Invoke(new EventHandler(MySerialReceived));  
}

시리얼통신 수신 버퍼에 데이타 들어오면, serialPort1_DataReceived 함수?가 실행된다. 이 함수는 먼저 알아보았던 serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived); 에 연결되어 있는데, 잘 보면 serialPort1_DataReceived 명칭이 똑같다.

여러 블로그와 사이트를 참조해 보니 Invoke가 없으면 메인쓰레드와 시리얼통신 쓰레드가 충돌해 오류가 난다고 한다.

Invoke를 사용하여 MySerialReceived로 넘어가 수신데이타를 처리한다.

 

 

5. 수신된 데이타를 손질해서 원하는 대로 처리한다.

private void MySerialReceived(object s, EventArgs e)  //여기에서 수신 데이타를 사용자의 용도에 따라 처리한다.
{            
    int ReceiveData = serialPort1.ReadByte();  //시리얼 버터에 수신된 데이타를 ReceiveData 읽어오기
    richTextBox_received.Text = richTextBox_received.Text + string.Format("{0:X2}", ReceiveData);  //int 형식을 string형식으로 변환하여 출력
}

ReadByte로 수신 버퍼에 있는 데이타를 읽어서 ReceiveData라는 integer 변수에 저장을 했다.

이 ReceiveData는 int형식이기 때문에 텍스트박스에 출력하기 위해 string으로 형식 변환을 한 뒤 출력한다.

이 부분은 수신 데이타를 사용자가 원하는 목적에 따라 마음대로 코드를 바꾸면 된다.

 

 

6. 시리얼통신 송신 부

private void Button_send_Click(object sender, EventArgs e)  //보내기 버튼을 클릭하면
{
    serialPort1.Write(textBox_send.Text);  //텍스트박스의 텍스트를 시리얼통신으로 송신
}

보내기 버튼을 클릭하면 텍스트박스에 있는 문자를 Write을 사용하여 전송하게 된다.

 

 

7. 연결끊기 버튼을 눌렀을 때

private void Button_disconnect_Click(object sender, EventArgs e)  //통신 연결끊기 버튼
{
    if (serialPort1.IsOpen)  //시리얼포트가 열려 있으면
    {
        serialPort1.Close();  //시리얼포트 닫기

        label_status.Text = "포트가 닫혔습니다.";
        comboBox_port.Enabled = true;  //COM포트설정 콤보박스 활성화
    }
    else  //시리얼포트가 닫혀 있으면
    {
        label_status.Text = "포트가 이미 닫혀 있습니다.";
    }
}

if(포트가 열려 있으면(연결되어 있으면))

    포트를 닫고

    닫혔다고 알려주고

    COM포트설정 콤보박스를 활성화하여 포트이름을 선택할 수 있게 한다.

else(포트가 열려 있지 않으면(연결되어 있지 않으면))

    이미 닫혔다고 알려준다.

 

 

 

정리.

시리얼통신은 송신은 간단하지만 수신은 다소 복잡한 절차를 거치게 된다.

아래 세가지 항목들을 잘 작성한다면 시리얼통신 수신에 대한 큰 벽은 넘으리라 생각된다.

using System.IO.Ports;  //시리얼통신을 위해 추가해줘야 함
serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived); //이것이 꼭 필요하다
this.Invoke(new EventHandler(MySerialReceived));  //메인 쓰레드와 수신 쓰레드의 충돌 방지를 위해 Invoke 사용. MySerialReceived로 이동하여 추가 작업 실행.

 

 

추가로 변수명, 버튼명, 콤보박스명, serialPort1, MySerialReceived 등 임의로 작성한 이름들이 있으니 잘 확인하며 참조해야 한다.

 

 

 

전체 코드이다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;  //시리얼통신을 위해 추가해줘야 함

namespace Serial_Communication
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)  //폼이 로드되면
        {
            comboBox_port.DataSource = SerialPort.GetPortNames(); //연결 가능한 시리얼포트 이름을 콤보박스에 가져오기 
        }
               
        private void Button_connect_Click(object sender, EventArgs e)  //통신 연결하기 버튼
        {
            if (!serialPort1.IsOpen)  //시리얼포트가 열려 있지 않으면
            {
                serialPort1.PortName = comboBox_port.Text;  //콤보박스의 선택된 COM포트명을 시리얼포트명으로 지정
                serialPort1.BaudRate = 9600;  //보레이트 변경이 필요하면 숫자 변경하기
                serialPort1.DataBits = 8;
                serialPort1.StopBits = StopBits.One;
                serialPort1.Parity = Parity.None;
                serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived); //이것이 꼭 필요하다
                
                serialPort1.Open();  //시리얼포트 열기

                label_status.Text = "포트가 열렸습니다.";
                comboBox_port.Enabled = false;  //COM포트설정 콤보박스 비활성화
            }
            else  //시리얼포트가 열려 있으면
            {
                label_status.Text = "포트가 이미 열려 있습니다.";
            }
        }
                
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)  //수신 이벤트가 발생하면 이 부분이 실행된다.
        {            
            this.Invoke(new EventHandler(MySerialReceived));  //메인 쓰레드와 수신 쓰레드의 충돌 방지를 위해 Invoke 사용. MySerialReceived로 이동하여 추가 작업 실행.
        }

        private void MySerialReceived(object s, EventArgs e)  //여기에서 수신 데이타를 사용자의 용도에 따라 처리한다.
        {            
            int ReceiveData = serialPort1.ReadByte();  //시리얼 버터에 수신된 데이타를 ReceiveData 읽어오기
            richTextBox_received.Text = richTextBox_received.Text + string.Format("{0:X2}", ReceiveData);  //int 형식을 string형식으로 변환하여 출력
        }

        private void Button_send_Click(object sender, EventArgs e)  //보내기 버튼을 클릭하면
        {
            serialPort1.Write(textBox_send.Text);  //텍스트박스의 텍스트를 시리얼통신으로 송신
        }

        private void Button_disconnect_Click(object sender, EventArgs e)  //통신 연결끊기 버튼
        {
            if (serialPort1.IsOpen)  //시리얼포트가 열려 있으면
            {
                serialPort1.Close();  //시리얼포트 닫기

                label_status.Text = "포트가 닫혔습니다.";
                comboBox_port.Enabled = true;  //COM포트설정 콤보박스 활성화
            }
            else  //시리얼포트가 닫혀 있으면
            {
                label_status.Text = "포트가 이미 닫혀 있습니다.";
            }
        }
    }
}

 

 

프로젝트 파일 첨부(Visual Studio 2019)

Serial_Communication.zip
0.22MB

 

 

 

 

 

 

 

 

반응형