C++에서 직렬 포트 연결
Windows API를 사용하여 직렬 포트 연결을 열고, 읽고, 쓰고, 관리하는 기본 사항은 이 문서에서 다룹니다.
이 자습서의 주요 목적은 프로그래밍에서 직렬 통신이 작동하는 방식에 대한 기본적인 이해를 제공하고 올바른 방향으로 시작하는 것입니다.
이 기사에서는 사용자가 C/C++에 대한 기본 지식이 있고 프로그램을 컴파일하고 실행할 수 있으며 개발 환경이 Windows API 호출을 사용하도록 설정되어 있다고 가정합니다.
C++에서 직렬 포트 연결
C++에서 데이터를 읽거나 직렬 포트에 쓰기 위한 6단계가 있습니다.
- 직렬 포트 열기
- 몇 가지 기본 속성 설정
- 타임아웃 설정
- 데이터 읽기 또는 쓰기
- 포트 정리
- 일부 고급 기능
직렬 포트 열기
첫 번째 단계는 포트를 여는 것입니다. 이것은 특히 Windows 파일 I/O를 이미 알고 있는 경우 가장 쉽고 간단한 단계 중 하나입니다.
먼저 필요한 헤더 파일(예: windows.h
)을 파일에 포함했는지 확인해야 합니다. 그런 다음 다음 코드를 사용합니다.
HANDLE h_Serial;
h_Serial = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
if (h_Serial == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
// serial port not found. Handle error here.
}
// any other error. Handle error here.
}
첫 줄에 HANDLE
유형 변수를 만든 다음 CreateFile
함수를 호출하여 초기화했습니다. 이 함수의 첫 번째 인수는 열어야 하는 파일의 이름입니다. 이 경우 직렬 포트를 열어야 하므로 해당 포트의 이름, 즉 COM1
을 사용했습니다.
두 번째 인수는 데이터를 읽어야 하는지 아니면 써야 하는지 지정하는 것입니다. 수행할 작업이 없으면 이 인수를 그대로 둘 수 있습니다.
다음 두 인수는 항상 0으로 유지됩니다. 다음 인수는 기존 파일을 열어야 하는지 아니면 새 파일을 만들어야 하는지 지정하는 것입니다.
이 경우 포트이므로 OPEN_EXISTING
을 사용했습니다. 다음 인수는 우리가 필요로 하는 것이 아니라 규칙적인 읽기 또는 쓰기를 Windows에 지정하는 것입니다.
마지막 인수도 항상 0으로 유지됩니다.
기본 속성 설정
포트의 HANDLE
을 가져온 후 대역 속도, 바이트 크기, 정지 비트 등과 같은 기본 속성 중 일부를 설정해야 합니다. 이것은 구조체 DCB
를 사용하여 수행됩니다.
DCB dcbSerialParam = {0};
dcbSerial.DCBlength = sizeof(dcbSerialParam);
if (!GetCommState(h_Serial, &dcbSerialParam)) {
// handle error here
}
dcbSerialParam.BaudRate = CBR_19200;
dcbSerialParam.ByteSize = 8;
dcbSerialParam.StopBits = ONESTOPBIT;
dcbSerialParam.Parity = NOPARITY;
if (!SetCommState(h_Serial, &dcbSerialParam)) {
// handle error here
}
위의 코드 스니펫에서 코드의 첫 번째 줄에 DCB
개체를 만들고 값을 지우기 위해 0으로 초기화했습니다. 다음 줄에서 Windows의 필수 단계인 이 구조체의 길이를 설정했습니다.
그런 다음 GetCommState
함수를 호출하고 여기에 두 개의 매개변수, 즉 포트 HANDLE
및 DCB
개체를 전달하여 현재 사용 중인 매개변수를 채웠습니다.
이 정보가 있으면 BaudRate
, ByteSize
, StopBits
및 Parity
와 같은 중요한 매개변수를 설정해야 합니다.
Windows에서는 특수 상수를 사용하여 BaudRate
를 제공해야 합니다. 예를 들어, CBR 19200
은 19200보, CBR 9600
은 9600보, CBR 57600
은 57600보를 나타냅니다.
ByteSize
를 직접 정의할 수 있지만 StopBits
및 Parity
에는 추가 변수가 필요합니다. ONESTOPBIT
, ONE5STOPBITS
및 TWOSTOPBITS
는 StopBits
가능성입니다.
‘EVENPARITY’, ‘NOPARITY’ 및 ‘ODDPARITY’는 ‘Parity’에 대해 가장 널리 사용되는 대안입니다. 다른 것들은 존재하지만 잘 알려지지 않았습니다.
자세한 내용은 MSDN 라이브러리 항목(DCB 검색)을 참조하십시오.
우선 순위에 따라 DCB 구조체를 구성한 후 이러한 설정을 직렬 포트에 적용해야 합니다. 이를 위해 SetCommState
기능이 사용됩니다.
시간 초과 설정
직렬 포트에서 데이터를 읽으면 직렬 포트로 들어오는 데이터가 없는 경우(예: 직렬 포트 장치가 꺼져 있거나 연결이 끊어진 경우) 데이터가 나타나기를 기다리는 동안 응용 프로그램이 중단될 수 있습니다. 이 상황을 처리하는 방법에는 두 가지가 있습니다.
첫째, 응용 프로그램에서 멀티스레딩을 사용할 수 있습니다. 한 스레드는 직렬 포트 문제를 처리하고 다른 스레드는 실제 처리를 처리합니다. 이는 번거롭고 복잡해질 수 있으며 필수 사항은 아닙니다.
대안은 훨씬 더 간단합니다. 데이터가 나타날 때까지 기다리지 않도록 Windows에 지시하십시오! 이는 다음을 통해 수행됩니다.
COMMTIMEOUTS timeout = {0};
timeout.ReadIntervalTimeout = 60;
timeout.ReadTotalTimeoutConstant = 60;
timeout.ReadTotalTimeoutMultiplier = 15;
timeout.WriteTotalTimeoutConstant = 60;
timeout.WriteTotalTimeoutMultiplier = 8;
if (!SetCommTimeouts(h_Serial, &timeout)) {
// handle error here
}
COMMTIMEOUTS
구조는 위에 나열된 필드만 포함하는 비교적 단순합니다. 요약:
ReadIntervalTimeout
- 시간이 초과되기 전에 수신 문자 사이에 통과해야 하는 시간(밀리초)을 지정합니다.ReadTotalTimeoutConstant
- 반환하기 전에 대기할 시간(밀리초)을 제공합니다.ReadTotalTimeoutMultiplier
- 읽기 작업에서 요청된 각 바이트에 대해 응답하기 전에 대기하는 시간(밀리초)을 지정합니다.WriteTotalTimeoutConstant
및WriteTotalTimeoutMultiplier
- 둘 다ReadTotalTimeoutConstant
및WriteTotalTimeoutMultiplier
와 동일한 작업을 수행하지만 읽기 대신 쓰기를 수행합니다.
ReadIntervalTimeout
을 MAXDWORD
로 설정하고 ReadTotalTimeoutConstant
및 ReadTotalTimeoutMultiplier
를 0
으로 설정하면 버퍼에 있는 문자(즉, 이미 수신된 문자)가 없는 경우에도 모든 읽기 작업이 즉시 반환됩니다.
COMMTIMEOUTS
구조를 구성한 후 SetCommTimeouts
메서드를 사용하여 변경 사항을 직렬 포트에 적용해야 합니다.
데이터 읽기/쓰기
필요한 매개변수 및 시간 제한을 사용하여 열린 직렬 포트 후에 데이터 읽기를 시작할 수 있습니다. 이들은 이해하기 쉽습니다.
직렬 포트에서 n
바이트를 읽는 시나리오를 고려하십시오. 그런 다음 다음 단계를 따르기만 하면 됩니다.
char sBuff[n + 1] = {0};
DWORD dwRead = 0;
if (!ReadFile(h_Serial, sBuff, n, &dwRead, NULL)) {
// handle error here
}
파일(직렬 포트)에 대한 HANDLE
, 데이터가 저장되는 버퍼, 읽을 바이트 수, 읽을 바이트 수로 설정할 정수에 대한 참조 및 NULL
이 모두 ReadFile
에 전달됩니다. . ReadFile
작업에서 읽은 바이트 수는 dwRead
에 저장됩니다.
데이터 쓰기도 같은 절차입니다. 유일한 차이점은 WriteFile
함수가 데이터를 쓸 수 있다는 것입니다.
포트 닫기
직렬 포트 사용을 마치면 핸들을 닫습니다. 그렇지 않으면 재부팅할 때까지 아무도 직렬 포트를 사용할 수 없는 등 이상한 일이 발생할 수 있습니다.
어쨌든 달성하기가 더 쉽기 때문에 다음 사항을 염두에 두십시오.
CloseHandle(h_Serial);
오류 처리
보시다시피 각 시스템 호출 후에 오류를 처리해야 한다는 메모를 삽입했습니다.
이것은 항상 훌륭한 프로그래밍 방법이지만 특히 더 자주 실패하는 I/O 기능에 중요합니다. 다음 함수는 어쨌든 실패하면 0
을 반환하고 성공하면 0
이외의 것을 반환합니다.
무엇이 잘못되었는지 알아내려면 오류 코드를 DWORD
로 반환하는 GetLastError()
함수를 사용하십시오. FormatMessage
메서드를 사용하여 이를 의미 있는 문자열로 바꿀 수 있습니다.
char lastErr[1020];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
lastErr, 1020, NULL);
Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.
LinkedIn