https://www.instructables.com/Serial-Port-Programming-With-NET/
직렬 포트는 다양한 유형의 하드웨어와 컴퓨터 간에 통신할 수 있는 쉬운 방법을 제공합니다.
비교적 사용이 간편하며 주변 장치, 특히 DIY 프로젝트에서 매우 일반적입니다.
Arduino와 같은 많은 플랫폼에는 직렬 통신이 내장되어 있어 설정 및 사용이 정말 쉽습니다.
멋진 대화형 출력, 컴퓨터에 데이터를 전달하는 깔끔한 센서
또는 상상할 수 있는 다른 모든 것을 얻기 위해 프로젝트가 컴퓨터와 통신하기를 원하는 경우가 많습니다.
이 튜토리얼에서는 Microsoft .net 프레임워크를 사용하여 시리얼포트 인터페이스를 하는 것을 배우게될겁니다
코드 예제는 C#에 있지만 Visual Basic 또는 Visual C++로 쉽게 포팅될 수 있습니다.
우리는 주로 System.IO.Ports.SerialPort 클래스를 사용할 것이므로
클래스 의 나머지 부분을 확인하려는 경우 여기 에서 MSDN의 전체 설명서 링크를 찾을 수 있습니다.
Step 1: Set-up and Open the Serial Port
SerialPort 클래스를 사용하려면 두 개의 네임스페이스를 포함해야 합니다.
using System.IO.Ports;
using System.IO;
이제 SerialPort 개체를 인스턴스화해야 합니다.
다양한 프레임 형식을 지정하기 위해 선택할 수 있는 여러 생성자가 있지만
일반적으로 가장 사용하기 쉬운 생성자는 다음과 같습니다.
// SerialPort(string portName, int baudRate)
SerialPort mySerialPort = new SerialPort( "COM3", 9600) ;
여기서는 9600 보드에서 COM3을 사용하고 있습니다.
위의 링크에서 생성자의 전체 목록을 찾을 수 있습니다.
이제 SerialPort 개체를 만들었으므로 Open() 메서드를 사용하여 포트를 열어야 합니다.
완료되면 Close() 메서드를 사용하여 닫을 수 있습니다.
mySerialPort.Open();
mySerialPort.Close();
몇 가지 참고 사항:
직렬 포트를 사용하는 작업을 사용하면 오류가 발생할 가능성이 높습니다.
이러한 이유로 우리는 try – catch 블록 내에서 직렬 포트에 대한 코드를 작성하려고 합니다.
예를 들어 존재하지 않는 포트를 열려고 시도하는 경우 프로그램이 충돌하는 것을 방지할 수 있습니다.
try – catch 블록 내에서 개체를 인스턴스화할 필요는 없지만 그 안에서 열고 닫고 읽고 쓰고 싶습니다.
// 이것은 단순히 SerialPort 개체를 만든 다음 포트를 열고 닫습니다.
SerialPort mySerialPort = new SerialPort( "COM3", 9600);
try {
mySerialPort.Open();
mySerialPort.Close();
}
catch (IOException ex) {
Console.WriteLine(ex);
}
이것이 포트 설정의 전부입니다!
다음 단계에서는 직렬 포트에서 읽는 방법을 소개합니다.
Step 2: Reading From the Port
이제 직렬 포트 객체를 생성하고 포트를 열었으므로 이제 직렬 포트에서 읽으려고 합니다.
기본 읽기 함수는 다음과 같습니다.
(몇 가지 다른 함수가 있지만 가장 간단하고 대부분의 응용 프로그램에서 작동합니다.)
int readChar() // 입력 버퍼에서 다음 문자를 반환합니다.
int readByte() // 입력 버퍼에서 다음 바이트를 반환합니다.
string readLine() // 입력 버퍼에서 개행 문자('\n')까지 모든 것을 반환합니다.
string readExisting() // 입력 버퍼의 모든 것을 반환합니다.
각각 문자와 바이트. 해당 유형으로 변환하려면 해당 유형으로 유형 변환해야 합니다.
char nextChar = (char)mySerialPort.readChar();
byte nextByte = (byte)mySerialPort.readByte();
다음 단계에서는 포트에서 읽는 방법에 대해 좀 더 자세히 살펴보겠습니다.
몇 가지 참고 사항:
ReadLine(), ReadExisting() 모두
입력 버퍼에서 디코딩된 바이트를 기반으로 문자열을 반환한다는 점은 주목할 가치가 있습니다.
그게 무슨 뜻이야?
예를 들어 바이트 0x48, 0x69 및 0x0A를 수신한 경우
ASCII 인코딩을 기반으로 'H' , 'I' 및 '\n'으로 디코딩됩니다.
이는 하드웨어가 숫자 값 65(0x41)를 전송하도록 하고
ReadExisting()을 사용하여 반환 값을 콘솔 창에 출력하면 "65"가 아니라 "A"가 출력되기 때문에 중요합니다.
0x41을 디코딩하고 'A'로 변경했습니다.
실제 숫자 값을 읽으려면 디코딩되지 않은 정수 값을 반환하므로
readByte() 또는 readChar()를 사용해야 합니다.
SerialPort 클래스는 SerialPort.Encoding 속성을 통해 기본 ASCII 이외의 여러 인코딩을 지원합니다.
Step 3: Ways to Read From the Port
예를 들어,직렬 포트에서 계속 읽고 싶은 경우
콘솔 창에서 읽은 모든 내용을 표시하는 가장 간단한 방법은
루프를 만들고 read method를 반복적으로 호출하는 것입니다.
이 방법이 작업을 완료하는 동안 몇 가지 중요한 단점이 있습니다.
첫째, 동일한 메서드를 계속해서 반복해서 호출해야 하고 루프 내에 갇혀 있기 때문에 매우 제한적입니다.
읽기 메서드에서 발생하는 또 다른 문제는
호출할 때 입력 버퍼에 데이터가 없으면
읽을 유효한 데이터가 있을 때까지 프로그램 실행을 중단한다는 것입니다
(이는 Console.ReadLine( ) 메서드와 비슷하다. 프로그램은 사용자가 Enter 키를 누를 때까지 계속되지 않습니다)
특정 지연 후에 메서드가 강제로 반환되도록 설정할 수 있는 속성이 있지만
일반적으로 프로그램이 예상보다 느리게 실행되는 것을 원하지 않습니다.
지속적으로 읽는 더 좋은 방법은
SerialPort.BytesToRead 속성을 사용하여
입력 버퍼에 읽을 데이터가 있는지 확인하는 것입니다.
이 속성은 읽어야 하는 입력 버퍼의 바이트 수를 반환합니다.
이를 통해 입력 버퍼에 아무것도 없으면
읽기 코드를 건너뛰는 루프를 설정할 수 있습니다. 예를 들면 다음과 같습니다.
while (true)
{
try
{
if (mySerialPort.BytesToRead > 0) //버퍼에 데이터가 있는 경우
{
mySerialPort.ReadByte(); //바이트 읽기
}
//읽기 메서드에 의해 유지되지 않고 실행할 수 있는 다른 코드입니다.
}
catch (IOException ex)
{
//오류 처리 로직
}
}
이 절차는 확실히 이전 방법보다 더 효율적이며
실제로 수행하는 모든 작업이 포트에서 계속해서 읽는 많은 간단한 상황에서 작동합니다.
다른 시나리오를 살펴보겠습니다.
많은 작업을 처리하고 무한 루프의 범위 내에서 작업할 수 없는 크고 복잡한 프로그램을 만들고 있다면 어떨까요?
다행스럽게도 SerialPort 클래스는
입력 버퍼에 새 데이터가 있을 때마다 발생하는 이벤트를 생성했습니다.
이벤트가 무엇인지 모르는 사람에게 이벤트는 중요한 일이 발생할 때
프로그램을 중단하고 이벤트를 처리하기 위해
메서드를 호출한 다음 프로그램이 중단된 위치로 돌아가는 것입니다.
우리의 경우 입력 버퍼에 의해 데이터가 수신되면
이벤트는 프로그램을 중지하고 데이터를 처리할 가능성이 가장 높은 메서드를 호출합니다.
그런 다음 프로그램이 중단된 위치로 돌아갑니다.
다음 단계에서 이에 대해 자세히 살펴보겠습니다.
Step 4: Reading Using Events
가장 먼저 해야 할 일은
데이터를 수신할 때 호출할 메서드를 직렬 포트에 알리는 것입니다.
이것은 다음 행으로 수행됩니다.
mySerialPort.DataReceived += new SerialDataEventHandler(mySerialPort_DataRecieved);
mySerialPort.DataReceived는 이벤트를 처리하기 위해 호출되는 메서드를 나타냅니다.
다음 부분 += new SerialDataEventHandler(mySerialPort_DataRecieved)로 해당 메서드를 지정합니다.
이벤트가 발생할 때 mySerialPort_DataRecieved 메서드를 사용한다고 합니다.
다음으로 메소드를 실제로 생성해야 합니다.
public static void mySerialPort_DataRecieved(object sender, SerialDataReceivedEventArgs e)
{
//원하는 모든 논리 및 읽기 절차
}
그게 전부입니다.
참고 사항:
이벤트 핸들러를 포함한 여러 메서드에서 사용할 수 있도록
SerialPort 개체를 클래스 수준 필드로 선언하는 이벤트를 사용할 때 확인해야 합니다.
다른 방법과 절차는 상황에 따라 다르므로 작동하고 사용하고 싶은 것을 찾아야 합니다.
저는 개인적으로 이벤트가 가장 효율적이고 프로그램이 다른 일을 할 수 있도록 자유롭게 남겨두기 때문에
가능할 때마다 이벤트를 사용하는 것을 좋아하지만 모든 사람이 선호하는 것이 있습니다.
다음 단계에서는 직렬 포트에 쓰는 방법에 대해 이야기하겠습니다.
Step 5: Writing to the Port
포트에 쓰는 것은 매우 쉽습니다!
사용할 수 있는 쓰기 방법은 다음과 같습니다.
Write (String data)
WriteLine(String data)
Write (byte[] data, int offset, int length)
Write (char[] data, int offset, int length)
처음 두 메서드는
WriteLine()이 데이터를 쓴 후 개행 문자('\n')를 쓴다는 점을 제외하면 거의 동일합니다.
다른 두 쓰기 방법도 비슷합니다.
유일한 차이점은 보낼 데이터의 데이터 유형입니다.
이를 사용하려면 직렬 포트에 기록될 바이트 또는 문자 배열을 제공합니다.
오프셋 매개변수는 시작할 배열 요소를 지정합니다.
즉, 0을 전달하면 배열의 맨 처음부터 시작합니다.
1을 전달하면 두 번째 요소에서 시작됩니다. 길이 매개변수는 단순히 배열의 길이입니다.
오류를 쉽게 발생시키므로 try-catch 블록 내에서 이러한 쓰기 작업을 수행해야 합니다.
몇 가지 참고 사항:
STEP 2 의 인코딩을 기억하십니까?
Write()와 WriteLine()에도 동일한 개념이 적용됩니다.
WriteLine("Hi") 호출은 0x41, 0x61, 0x0A를 기록합니다(0x0A는 WriteLine()을 사용했기 때문에 추가된 '\n'입니다).
하드웨어 측에서 문자로 인식하려면 자체 디코딩 로직이 있어야 합니다.
C#에서 직렬 포트와 통신하기
https://www.c-sharpcorner.com/uploadfile/eclipsed4utoo/communicating-with-serial-port-in-C-Sharp/
이 문서에서는
C# 및 .NET의 직렬 포트에 연결된 장치에서 데이터를 쓰고 받는 방법을 보여줍니다.
받은 데이터를 양식의 TextBox에 쓸 것이므로 스레딩도 처리합니다.
과거에는 .NET 1.1을 사용하여 직렬 포트와 통신하려면
Windows API 또는 타사 컨트롤을 사용해야 했습니다. .
NET 2.0 이상에서 Microsoft는
SerialPort 클래스를 System.IO.Ports 네임스페이스의 일부로 포함하여 이 지원을 추가했습니다.
SerialPort 클래스의 구현은 매우 간단합니다.
SerialPort 클래스의 인스턴스를 만들려면 SerialPort 옵션을 클래스 생성자에 전달하기만 하면 됩니다.
// all of the options for a serial device
// ---- can be sent through the constructor of the SerialPort class
// ---- PortName = "COM1", Baud Rate = 19200, Parity = None,
// ---- Data Bits = 8, Stop Bits = One, Handshake = None
SerialPort _serialPort = new SerialPort("COM1", 19200, Parity.None, 8, StopBits.One);
_serialPort.Handshake = Handshake.None;
데이터를 수신하려면 "SerialDataReceivedEventHandler"에 대한 EventHandler를 생성해야 합니다.
// "sp_DataReceived" is a custom method that I have created
_serialPort.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
ReadTimeout 및 WriteTimeout과 같은 다른 옵션을 설정할 수도 있습니다.
직렬 포트를 사용할 준비가 되면 다음과 같이 열어야 합니다.
// milliseconds _serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;
// Opens serial port
_serialPort.Open();
이제 데이터를 받을 준비가 되었습니다.
그러나 양식의 TextBox에 이 데이터를 쓰려면 delegate를 만들어야 합니다.
.NET은 스레드 간 작업을 허용하지 않으므로 delegate를 사용해야 합니다.
delegate는 UI가 아닌 스레드에서 UI 스레드에 쓰는 데 사용됩니다.
// delegate is used to write to a UI control from a non-UI thread
private delegate void SetTextDeleg(string text);
이제 직렬 포트를 통해 데이터를 수신할 때 실행될 "sp_DataReceived" 메서드를 생성하고,
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(500);
string data = _serialPort.ReadLine();
// Invokes the delegate on the UI thread, and sends the data that was received to the invoked method.
// ---- The "si_DataReceived" method will be executed on the UI thread which allows populating of the textbox.
this.BeginInvoke(new SetTextDeleg(si_DataReceived), new object[] { data });
}
이제 "si_DataReceived" 메서드를 생성합니다.
private void si_DataReceived(string data) { textBox1.Text = data.Trim(); }
이제 직렬 포트 장치에서 데이터를 수신하여 양식에 표시할 수 있습니다.
일부 장치는 프롬프트 없이 데이터를 전송합니다.
그러나 일부 장치는 특정 명령을 보내야 하며 명령이 요구하는 데이터로 응답합니다.
이러한 장치의 경우 직렬 포트에 데이터를 쓰고 이전 코드를 사용하여 다시 보낼 데이터를 가져옵니다.
내 예에서는 저울과 통신할 것입니다.
이 특정 저울의 경우 "SI\r\n" 명령을 보내면 저울에 있는 모든 항목의 무게를 강제로 반환합니다.
이 명령은 이 저울에만 적용됩니다.
수신할 명령을 찾으려면 직렬 장치의 설명서를 읽어야 합니다.
직렬 포트에 쓰기 위해 양식에 "시작" 버튼을 만들었습니다. Click_Event에 코드를 추가했습니다.
private void btnStart_Click(object sender, EventArgs e)
{
// Makes sure serial port is open before trying to write
try
{
if(!(_serialPort.IsOpen))
_serialPort.Open();
_serialPort.Write("SI\r\n");
}
catch (Exception ex)
{
MessageBox.Show("Error opening/writing to serial port :: " + ex.Message, "Error!");
}
}
그리고 그것이 당신이 해야 할 전부입니다.
com 포트에서 데이터를 받는 방법
using System;
using System.IO.Ports;
using System.Threading;
public class PortChat
{
static bool _continue;
static SerialPort _serialPort;
public static void Main()
{
string name;
string message;
StringComparer stringComparer = StringComparer.OrdinalIgnoreCase;
Thread readThread = new Thread(Read);
// Create a new SerialPort object with default settings.
_serialPort = new SerialPort();
// Allow the user to set the appropriate properties.
_serialPort.PortName = SetPortName(_serialPort.PortName);
_serialPort.BaudRate = SetPortBaudRate(_serialPort.BaudRate);
_serialPort.Parity = SetPortParity(_serialPort.Parity);
_serialPort.DataBits = SetPortDataBits(_serialPort.DataBits);
_serialPort.StopBits = SetPortStopBits(_serialPort.StopBits);
_serialPort.Handshake = SetPortHandshake(_serialPort.Handshake);
// Set the read/write timeouts
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;
_serialPort.Open();
_continue = true;
readThread.Start();
Console.Write("Name: ");
name = Console.ReadLine();
Console.WriteLine("Type QUIT to exit");
while (_continue)
{
message = Console.ReadLine();
if (stringComparer.Equals("quit", message))
{
_continue = false;
}
else
{
_serialPort.WriteLine(
String.Format("<{0}>: {1}", name, message) );
}
}
readThread.Join();
_serialPort.Close();
}
public static void Read()
{
while (_continue)
{
try
{
string message = _serialPort.ReadLine();
Console.WriteLine(message);
}
catch (TimeoutException) { }
}
}
public static string SetPortName(string defaultPortName)
{
string portName;
Console.WriteLine("Available Ports:");
foreach (string s in SerialPort.GetPortNames())
{
Console.WriteLine(" {0}", s);
}
Console.Write("COM port({0}): ", defaultPortName);
portName = Console.ReadLine();
if (portName == "")
{
portName = defaultPortName;
}
return portName;
}
public static int SetPortBaudRate(int defaultPortBaudRate)
{
string baudRate;
Console.Write("Baud Rate({0}): ", defaultPortBaudRate);
baudRate = Console.ReadLine();
if (baudRate == "")
{
baudRate = defaultPortBaudRate.ToString();
}
return int.Parse(baudRate);
}
public static Parity SetPortParity(Parity defaultPortParity)
{
string parity;
Console.WriteLine("Available Parity options:");
foreach (string s in Enum.GetNames(typeof(Parity)))
{
Console.WriteLine(" {0}", s);
}
Console.Write("Parity({0}):", defaultPortParity.ToString());
parity = Console.ReadLine();
if (parity == "")
{
parity = defaultPortParity.ToString();
}
return (Parity)Enum.Parse(typeof(Parity), parity);
}
public static int SetPortDataBits(int defaultPortDataBits)
{
string dataBits;
Console.Write("Data Bits({0}): ", defaultPortDataBits);
dataBits = Console.ReadLine();
if (dataBits == "")
{
dataBits = defaultPortDataBits.ToString();
}
return int.Parse(dataBits);
}
public static StopBits SetPortStopBits(StopBits defaultPortStopBits)
{
string stopBits;
Console.WriteLine("Available Stop Bits options:");
foreach (string s in Enum.GetNames(typeof(StopBits)))
{
Console.WriteLine(" {0}", s);
}
Console.Write("Stop Bits({0}):", defaultPortStopBits.ToString());
stopBits = Console.ReadLine();
if (stopBits == "")
{
stopBits = defaultPortStopBits.ToString();
}
return (StopBits)Enum.Parse(typeof(StopBits), stopBits);
}
public static Handshake SetPortHandshake(Handshake defaultPortHandshake)
{
string handshake;
Console.WriteLine("Available Handshake options:");
foreach (string s in Enum.GetNames(typeof(Handshake)))
{
Console.WriteLine(" {0}", s);
}
Console.Write("Handshake({0}):", defaultPortHandshake.ToString());
handshake = Console.ReadLine();
if (handshake == "")
{
handshake = defaultPortHandshake.ToString();
}
return (Handshake)Enum.Parse(typeof(Handshake), handshake);
}
}
'C#' 카테고리의 다른 글
C# 시리얼 통신 클래스 만들기 (0) | 2023.01.31 |
---|---|
Find The Difference Of Day, Hour, Minutes, Seconds, In C# (0) | 2021.08.30 |
C#의 스레드 (0) | 2021.08.30 |