Windows C언어 환경에서 시리얼 통신하기 (Visual Studio)

Visual Studio를 사용하여 개발하는 환경에서, USB 포트를 사용한 시리얼 통신 방법을 알아봅니다.


윈도우는 시리얼 통신의 구현을 위해서 다양한 구조체와 함수들을 지원합니다. 그것들을 사용하기 위해서 먼저 Windows.h를 include 합니다. 시리얼 핸들을 열고 포트를 지정해서 시리얼 포트를 오픈합니다. COM 시리얼 포트로 통신하는 것은 동명의 파일로 입출력을 시키는 작업입니다.

hSerial = CreateFile("COM5", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

위의 코드는 COM5 포트를 읽고 쓰기 가능한 모드로 열고 있습니다. 통신하고 싶은 포트를 알맞게 지정하면 됩니다.

그리고 원도우에서 시리얼 포트를 열 때 주의해야 할 부분이 있습니다. COM10 이상의 시리얼 포트들은, \\.\ 다음에 포트 이름을 붙여 주어야 접근이 가능합니다. 예를 들어 COM16에 연결할 때는 다음과 같이 해주어야 합니다.

hSerial = CreateFile("\\\\.\\COM16", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

다음으로, 해당 시리얼 포트와 현재 통신할 수 있는지 확인합니다. 시리얼 포트에 통신 가능한 장치가 연결되어 있지 않다면 해당 COMx 포트는 열리지 않습니다.

if (hSerial == INVALID_HANDLE_VALUE) {
	if (GetLastError() == ERROR_FILE_NOT_FOUND) {
		// 해당 시리얼 포트가 존재하지 않음
		printf("Serial port not found!\n");
	}else{
        // 해당 시리얼 포트가 존재함
}

시리얼 포트가 열렸는지 확인했다면, 통신 데이터의 크기, 보드레이트 등 변수 설정을 진행합니다. 이 작업은 DCB라는 이름의 구조체를 이용해서 진행합니다.

DCB dcbSerialParams = { 0 };
dcbSerialParams.DCBlength = sizeof(dcbSerialParams);

그 후, 해당 COM 시리얼 포트의 상태를 체크합니다.

if (!GetCommState(hSerial, &dcbSerialParams)) {
	// Error getting COM port state
	printf("Error : Getting COM port state\n");
}

이제 시리얼 통신을 설정해줍니다. 보드레이트, 데이터 사이즈, STOP 비트 갯수, 패리티 비트 여부를 설정해줍니다. 시리얼 통신을 설정할 때 기본이 되는 설정들입니다. DCB 구조체에 값을 등록한 후, SetCommState 함수로 시리얼 핸들에 적용합니다.

dcbSerialParams.BaudRate = CBR_9600;      // 보드레이트 설정
dcbSerialParams.ByteSize = 8;             // 데이터 사이즈 설정
dcbSerialParams.StopBits = ONESTOPBIT;    // 스탑비트 설정
dcbSerialParams.Parity   = NOPARITY;      // 패리티 설정

if (!SetCommState(hSerial, &dcbSerialParams)) {     // 설정 적용
	// Error setting COM port state
	printf("Error : Setting COM port state\n");
}

다음으로 연결의 Timeout을 설정해줍니다. 통신에서 Timeout을 설정하는 이유는, 둘 중 어디선가 통신에 문제가 생겼을 경우를 대비하기 위해서입니다. Read 동작의 Timeout과 Write 동작의 Timeout을 모두 설정해줍니다. 설정에는 COMMTIMEOUTS 구조체가 사용됩니다. 모든 값의 단위는 ms(밀리세컨드)입니다.

COMMTIMEOUTS timeouts = { 0 };
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;

timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;

if (!SetCommTimeouts(hSerial, &timeouts)) {
	// Error set timeouts
	printf("Error : Setting timeouts\n");
}

Read와 Write 작업의 Timeout을 50ms로 설정해주었습니다.

여기까지 해서 초기화 작업이 종료되었습니다. 이제 열린 Serial 포트 파일을 통해 데이터를 읽고 써주면 됩니다. 먼저 Read입니다.

DWORD dwBytesRead = 0;
int readSize = 10;
unsigned char buff[10];

if (ReadFile(hSerial, buff, readSize, &dwBytesRead, NULL)) {
    printf("%X ", buff[0]);    // Print data sample
}

읽는 작업은 단순합니다. 10바이트를 시리얼 포트에서 읽어온 후, buff 배열에 저장하고 있습니다.

Write 작업도 비슷합니다.

DWORD dwBytesWrite = 0;
int writeSize = 10;
unsigned char str[10] = "MagmaTart";

if (WriteFile(hSerial, str, writeSize, &dwBytesWrite, NULL)) {
    // Write Complete
}

통신이 끝난 시리얼 포트는, CloseHandle 함수를 통해 닫을 수 있습니다.

CloseHandle(hSerial);