C++에서 직렬 포트 연결

Muhammad Husnain 2023년10월12일
C++에서 직렬 포트 연결

Windows API를 사용하여 직렬 포트 연결을 열고, 읽고, 쓰고, 관리하는 기본 사항은 이 문서에서 다룹니다.

이 자습서의 주요 목적은 프로그래밍에서 직렬 통신이 작동하는 방식에 대한 기본적인 이해를 제공하고 올바른 방향으로 시작하는 것입니다.

이 기사에서는 사용자가 C/C++에 대한 기본 지식이 있고 프로그램을 컴파일하고 실행할 수 있으며 개발 환경이 Windows API 호출을 사용하도록 설정되어 있다고 가정합니다.

C++에서 직렬 포트 연결

C++에서 데이터를 읽거나 직렬 포트에 쓰기 위한 6단계가 있습니다.

  1. 직렬 포트 열기
  2. 몇 가지 기본 속성 설정
  3. 타임아웃 설정
  4. 데이터 읽기 또는 쓰기
  5. 포트 정리
  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 함수를 호출하고 여기에 두 개의 매개변수, 즉 포트 HANDLEDCB 개체를 전달하여 현재 사용 중인 매개변수를 채웠습니다.

이 정보가 있으면 BaudRate, ByteSize, StopBitsParity와 같은 중요한 매개변수를 설정해야 합니다.

Windows에서는 특수 상수를 사용하여 BaudRate를 제공해야 합니다. 예를 들어, CBR 19200은 19200보, CBR 9600은 9600보, CBR 57600은 57600보를 나타냅니다.

ByteSize를 직접 정의할 수 있지만 StopBitsParity에는 추가 변수가 필요합니다. ONESTOPBIT, ONE5STOPBITSTWOSTOPBITSStopBits 가능성입니다.

‘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 - 읽기 작업에서 요청된 각 바이트에 대해 응답하기 전에 대기하는 시간(밀리초)을 지정합니다.
  • WriteTotalTimeoutConstantWriteTotalTimeoutMultiplier - 둘 다 ReadTotalTimeoutConstantWriteTotalTimeoutMultiplier와 동일한 작업을 수행하지만 읽기 대신 쓰기를 수행합니다.

ReadIntervalTimeoutMAXDWORD로 설정하고 ReadTotalTimeoutConstantReadTotalTimeoutMultiplier0으로 설정하면 버퍼에 있는 문자(즉, 이미 수신된 문자)가 없는 경우에도 모든 읽기 작업이 즉시 반환됩니다.

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);
Muhammad Husnain avatar Muhammad Husnain avatar

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