Windows Serial Port Programming

http://blog.daum.net/pg365/51

Windows Serial Port Programming

Windows API를 사용한 시리얼 포트 통신 프로그래밍은 생각보다 간단하지만, 처음 시작할 때 접근하기가 어려운 면이 있습니다.그래서 여기에서 간단히 소개합니다시리얼 통신 프로그래밍이 어떻게 작성되는지와 이를 사용하기 위한 올바른 접근 법을 제시합니다.

1. 시리얼 포트 열기

시리얼 포트를 열기위해 Windows에서 제공하는 파일 입출력 함수를 사용합니다먼저, windows.h 헤더 파일을 인클루드 한 후 아래와 같이 CreateFile() 함수로 시리얼 포트를 오픈 합니다.

HANDLE hSerial;

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

if (hSerial==INVALID_HANDLE_VALUE){
    // 에러 발생
}

이 함수를 잠깐 살펴보면, HANDLE 타입의 변수를 선언하고 CreateFile() 함수를 불러 초기화 합니다. CreateFile() 함수의 첫 번째 인자는 열고자 하는 시리얼 포트의 이름을 지정하는데, "\\\\.\\COM1", "\\\\.\\COM2"와 같은 문자열을 사용합니다두 번째 인자는 시리얼 포트를 읽고(GENERIC_READ) 쓰기(GENERIC_WRITE)로 지정 합니다세 번째와 네 번째 인자는 대부분 0으로 두면 됩니다다섯 번째 인자 OPEN_EXISTING은 이미 존재하는 파일(시리얼 포트)을 열겠다는 것입니다파일과 달리 열고자 하는 시리얼 포트는 항상 존재해야 하기 때문에 OPEN_EXISTING은 항상 지정되어야 합니다여섯 번째 FILE_ATTRIBUTE_NORMAL은 일반적인 파일 속성을 가지도록 합니다마지막 인자는 0을 사용 하면 됩니다.

2. 파라메터 설정

앞에서 시리얼 포트를 열어 핸들을 얻어왔습니다이제부터는 핸들로 시리얼 포트를 제어하게 됩니다시리얼 포트는 파일과 달리 통신과 관련된 속성인 보레이트(baud rate), 바이트 크기스톱 비트패리티 등을 설정해야 하는데이때 사용하는 것이 DCB 구조체 입니다.

DCB dcbSerialParams = {0};

dcbSerial.DCBlength=sizeof(dcbSerialParams);

if (!GetCommState(hSerial, &dcbSerialParams)) {
    // 에러발생
}

dcbSerialParams.BaudRate=CBR_19200;
dcbSerialParams.ByteSize=8;
dcbSerialParams.StopBits=ONESTOPBIT;
dcbSerialParams.Parity=NOPARITY;

if (!SetCommState(hSerial, &dcbSerialParams)){
    // 에러발생
}

하나씩 설명해 보겠습니다먼저설정 값들을 저장하는 DCB 구조체 변수를 만들고 0으로 초기화 한 후, DCB 구조체의 크기를 지정합니다그리고 GetCommState() 함수를 사용하여 Windows에서 기본적으로 설정된 값들을 얻어옵니다.

이제우리가 원하는 설정으로 기본 설정 값의 일부를 바꾸어 줘야 합니다시리얼 통신에서 가장 중요한 속성들은 보레이트바이트 크기스톱 비트패리티 입니다이를 하나씩 차례대로 설정하면 됩니다만일 통신 속도가 19200 baud/sec. 라면 보레이트는CBR_19200으로 설정합니다이 외에 주로 사용되는 보레이트는 CBR_9600, CBR_19200, CBR_38400, CBR_57600, CBR_115200 이 있습니다바이트 크기는 통신으로 왔다갔다 하는 데이터 크기입니다보통 7 혹은 8과 같이 숫자를 직접 지정합니다스톱 비트는 1, 1.5, 2 bit가 사용되는데 이에 대하여 ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS가 정의되어 있습니다.  대부분 ONESTOPBIT를 사용합니다패리티는 주로 NOPARITY, ODDPARITY, EVENPARITY 중 하나를 사용합니다.
더 많은 설정 옵션들은 winbase.h 에 정의된 내용을 참고하거나 MSDN 도움말을 참고하시기 바랍니다.

DCB 구조체 변수에 우리가 원하는 대로 속성 설정을 완료 하였다면, SetCommState() 함수를 호출하여설정 값을 시리얼 포트에 적용합니다.

3. 타임아웃 설정

시리얼 통신에서 또다른 중요한 요소는 타임아웃 시간의 설정입니다. ‘만일 입력 버퍼에 아무런 데이터가 없는데 읽기를 시도한다면 데이터가 들어올 때까지 얼마나 기다릴 것인가와 같은 문제에서 기다리는 시간을 타임아웃으로 설정할 수 있습니다여기서 타임아웃 시간이 올바르게 설정되지 않았을 경우는 시리얼 포트에서 데이터를 읽기 위해 많은 시간을 소비할 수 있습니다어떨 때는 기다리는 시간을 너무 짧게 설정하여 데이터를 읽지 못하는 경우도 있습니다.

COMMTIMEOUTS timeouts={0};

timeouts.ReadIntervalTimeout=50;
timeouts.ReadTotalTimeoutConstant=50;
timeouts.ReadTotalTimeoutMultiplier=10;
timeouts.WriteTotalTimeoutConstant=50;
timeouts.WriteTotalTimeoutMultiplier=10;

if (!SetCommTimeouts(hSerial, &timeouts)){
    // 에러발생
}

COMMTIMEOUTS 구조체의 멤버는 다음과 같습니다.
w  ReadIntervalTimeout은 문자를 받은 후 다음 문자를 받기까지 얼마나 기다릴 것인지를 밀리 초로 설정합니다.
w  ReadTotalTimeoutConstant는 읽기에서 총 기다리는 시간을 밀리 초로 설정합니다.
w  ReadTotalTimeoutMultiplier는 읽기에서 읽은 문자 수에 비례하여 총 기다리는 시간을 늘여 줍니다.
w  WriteTotalTimeoutConstant는 쓰기에서 총 기다리는 시간을 밀리 초로 설정합니다.
w  WriteTotalTimeoutMultiplier는 쓰기에서 쓴 문자 수에 비례하여 총 기다리는 시간을 늘여 줍니다.

특별한 케이스로 ReadIntervalTimeout MAXDWORD로 설정하고 ReadTotalTimeoutConstant ReadTotalTimeoutMultiplier0으로 설정하는 경우입니다이 경우읽기 동작은 이미 버퍼에 수신된 데이터만 읽어서 즉시 리턴하게 됩니다만일 읽기 버퍼에 아무 데이터도 없는 경우에도 즉시 리턴합니다.

COMMTIMEOUTS 구조체를 적절한 시간으로 설정하였다면, SetCommTimeouts() 함수로 시리얼 포트에 적용합니다.

4. 데이터 읽고 쓰기

앞서 시리얼 포트를 열고통신 파라메터를 설정하고타임아웃을 설정했습니다이제 데이터를 읽고 쓰면 됩니다데이터를 읽고 쓰는데는 파일과 같이 ReadFile(), WriteFile() 함수를 사용합니다만일 우리가 시리얼 포트로부터 n 바이트의 데이터를 읽으려고 한다면 다음과 같습니다.

char szBuff[n] = {0};
DWORD dwBytesRead = 0;

if (!ReadFile(hSerial, szBuff, n, &dwBytesRead, NULL)){
    // 에러발생
}

먼저 읽은 데이터가 저장될 버퍼를 할당합니다최대 n 바이트를 읽어야 하기 때문에 버퍼의 크기는 n바이트로 잡습니다. ReadFile()함수에 핸들과 버퍼 포인터버퍼의 크기읽은 문자의 수를 저장할 변수의 포인터를 넘겨줍니다만일 버퍼에 n보다 많은 데이터가 들어 있다면 ReadFile() 함수는 n개만 읽고 즉시 리턴합니다하지만 버퍼에 n보다 적거나 데이터가 들어있지 않은 경우는 타임아웃의 설정에 따라 즉시 리턴하거나 데이터를 더 읽기 위해 기다리게 됩니다. 함수가 리턴할 때 dwBytesRead 변수에 읽은 문자 수가 기록됩니다.

WriteFile() 함수는 ReadFile() 함수와 인자가 동일하기 때문에설명은 생략합니다.

5. 닫기

시리얼 포트의 사용이 끝나면 핸들을 닫아 주어야 합니다다음과 같이 CloseHandle() 함수에 핸들을 인자로 주어 호출하면 됩니다.

CloseHandle(hSerial);

6. 에러

상기 과정에서 각각의 시스템 콜을 하는 도중 함수가 실패(0)를 리턴할 수 있습니다이때 어떤 에러가 발생하였는지 알 수 있다면 대처가 쉬울 것입니다. GetLastError() 함수를 호출 함으로 에러 코드를 얻어오고이를 읽을 수 있는 문장으로 번역하기 위해FormatMessage() 함수를 사용합니다.

char lastError[1024];

FormatMessage(
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL,
    GetLastError(),
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    lastError,
    1024,
    NULL);

* 참조


윈도우즈 시리얼 포트 프로그래밍 1에서 시리얼 포트를 사용하기 위한 기본 API 들에 대하여 설명하였습니다 API들을 사용하여 아래와 같이 시리얼 통신 클래스의 헤더와 소스 파일을 작성하였습니다.


이제 이 두 파일을 프로젝트에 추가하여 시리얼 포트로 통신이 되는지 확인해 보겠습니다. Visual Studio 2008에서 C++ 언어로Console 프로젝트를 만듭니다그리고 위의 두 파일을 프로젝트에 추가하고 다음과 같이 편집합니다.

먼저 SerialPort.h 헤더 파일을 인클루드 합니다. 그리고 windows.h 헤더 파일도 인클루드 하는데, CBR_115200과 같은 정의들을 사용하기 위함입니다.
  
#include "SerialPort.h"
#include <windows.h>

main() 함수에 다음과 같이 코드를 작성 합니다. Open() 함수로 넘어가는 시리얼 포트 이름 "\\\\.\\COM1"의 경우 각 사용자의 컴퓨터에 설치된 적절한 시리얼 포트 이름을 주어야 합니다.

아래 코드는 콘솔에서 사용자가 입력한 문자열을 입력받아 COM1으로 보내고 다시 COM1으로 들어온 문자열을 읽어 콘솔에 출력하는 일을 합니다.

    CSerialPort com1;

    com1.Open ("\\\\.\\COM1", CBR_115200, 8, ONESTOPBIT, NOPARITY);
    com1.SetTimeout (10, 10, 1);

    int n;
    char buff[1024];

    while (1) {
        printf ("\nWRITE: ");
        scanf ("%s", buff);
        n = strlen(buff);

        com1.Write (buff, n);

        Sleep (100);

        n = com1.Read (buff, 1024);

        printf ("READ: %s (%d)", buff, n);
    };


아래는 완성된 프로젝트 파일입니다.


아직 시리얼 포트에 적절한 장치가 연결되어 있지 않을 것입니다. 그러므로 다음과 같이 컴퓨터의 시리얼 포트 커넥터에 2번 핀(RxD)와 3번 핀(TxD)를 단락시켜(그림의 붉은 선) 시리얼 포트로 나간 데이터가 에코 되어 다시 들어오도록 합니다.


그러면 다음과 같이 콘솔에 입력한 문자열이 시리얼 포트에서 에코 되어 그대로 읽히는 것을 볼 수 있습니다.
 
WRITE: qwer
READ: qwer (4)
WRITE:




댓글

이 블로그의 인기 게시물

4,5,6 띠 저항의 색띠를 읽는 법(띠저항 값)

수지에서 인천공항 리무진 버스 (인천공항버스정보)(2022년3월업데이트)

수지에서 김포공항 리무진 버스 ( 2022년 3월 업데이트 )