HOWTO · C++
C++에서 직렬 포트 연결
이 기사는 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);