본문 바로가기
프로그래밍/C/C++/C#

VC++을 이용한 Serial 통신 프로그램 만들기 [고급]

by choies1 2009. 12. 25.

참고 사이트:
[1] http://crazystone.tistory.com/entry/VC-Serial-%ED%86%B5%EC%8B%A0
[2] http://blog.paran.com/mk3358/13401376
[3] http://wwwi.tistory.com/215
[4] http://www.codeguru.com/cpp/i-n/network/serialcommunications/article.php/c2503/

앞에서 VC++을 이용한 Serial 통신 프로그램 만들기[기초] 에 대해서 살펴보았다.
이제 조금더 세밀한 설정이 필요할 경우에 이용하는 고급 기능에 대하여 살펴보자.

1. TIMEOUTS 설정
Serial Port의 값을 읽어 올 때, 아무런 설정도 하지 않으면 지정된 수신 buffer에 값이 모드 들어올 때까지 무한정 기다리게 된다. 하지만 경우에 따라서는 일정 시간이 지나면 수신 동작에 Timeouts 을 설정할 필요가 있는데, 그 때 필요한 것이 아래의 함수와 COMMTIMEOUTS 구조체이다. 구체적으로는 아래와 같다.

 

 

BOOL GetCommTimeouts(
  HANDLE hFile,                  // handle to comm device
  LPCOMMTIMEOUTS lpCommTimeouts  // time-out values
);


BOOL SetCommTimeouts(
  HANDLE hFile,                  // handle to comm device
  LPCOMMTIMEOUTS lpCommTimeouts  // time-out values
);

 

typedef struct _COMMTIMEOUTS { 
  DWORD ReadIntervalTimeout;
  DWORD ReadTotalTimeoutMultiplier;
  DWORD ReadTotalTimeoutConstant;
  DWORD WriteTotalTimeoutMultiplier;
  DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

 

타임 아웃을 설정하는 방법은 COMMTIMEOUTS 구조체에 수신과 관련된 타임아웃 파라미터를 설정하면 된다. 여기서 말하는 시간의 단위는 모두 milliseconds(msec)이다.

1) ReadIntervalTimeout는 byte와 byte 사이에 도착 시간을 설정하는 것으로서 설정한 시간 안에 다음 byte가 도착하지 않으면 타임 아웃이 발생한다. 통상 MAXDWORD란 값을 설정한다. 만약 설정하고 싶지 않으면 0을 입력한다.
 
2) ReadTotalTimeoutMultiplier는 한 캐릭터를 수신하는데 걸리는 시간이다. 수신버퍼를 4KB로 잡았다고 하면 총 수신 시간은 (ReadTotalTimeoutMultiplier) x (수신버퍼사이즈 [4096]) 가 되겠다.
 
3) ReadTotalTimeoutConstant는 총 수신 시간 이외의 잉여 시간으로서 해당 시간만큼 더 기다렸는데도 데이터가 수신완료되지 않으면 타임 아웃이 발생한다. 따라서, 읽기동작에서 지정된 수신버퍼를 채우기 위한 총 수신 시간은 (ReadTotalTimeoutMultiplier) x (수신버퍼사이즈) + ReadTotalTimeoutConstant 가 될 것이다.

만약 20ms 마다 Serial data를 수신하여 처리하는 코드의 경우, 20ms가 지났는데도 수신된 data가 없을 경우에 timeout이 발생하도록 하고 싶다면(즉, data가 수신될 때까지 계속 이상 기다리지 않고 코드의 다음 라인을 진행하게 하고 싶다면) , ReadIntervalTimeout = 0 (혹은 MAXDWORD), ReadTotalTimeoutMultiplier = 0, ReadTotalTimeoutConstant  = 20 (혹은 Windows 시스템이 실시간성을 보장하지 않으므로 여유를 위해서 100)으로 설정하면 된다.

좀더 구체적인 것은 위의 참고 사이트 [3]과 http://msdn.microsoft.com/en-us/library/aa363190(VS.85).aspx 를 참조하면 된다.

2. 통신포트의 Input/Output buffer의 사이즈를 설정
통신포트의 Input/Output buffer의 사이즈를 설정하기 위해서는 SetupComm(); 함수를 이용한다. 기본적으로 Windows에서는 Input buffer가 4096으로 설정이 되어있다. 

BOOL WINAPI SetupComm(
  __in  HANDLE hFile,
  __in  DWORD dwInQueue,
  __in  DWORD dwOutQueue
);


SetupComm(m_hComDev, InBufSize, OutBufSize);
와 같이 이용하여 buffer 사이즈를 설정하면 된다.
(하지만 실제로 해보면 buffer 사이즈가 변경이 안된다. 4096으로 고정되어 있는 것 같다. 혹시 이것에 대한 해결책을 알고 계신분은 답변 달아주시면 좋겠습니다.)

3. Buffer값 Clear
어떠한 상황에서는 송수신 버퍼의 내용을 모두 버려야 할 때가 있다. 이때 사용하는 함수가 PurgeComm()이다. 상황에 따라서 다양한 Parameter들을 조합할 수 있는데, 모든 Tx,Rx 동작을 제한하고 또한 버퍼의 내용을 버리려면 아래의 예처럼 하면 된다.

PurgeComm(m_hComDev,
                   PURGE_TXABORT | PURGE_RXABORT |  PURGE_TXCLEAR | PURGE_RXCLEAR);

4. 수신 버퍼에 읽지 않고 남은 Byte 수 알아내기
Serial 통신을 이용하여 외부에서 byte 단위로 값을 받게 되면 수신 버퍼에 data가 쌓이게 된다. 만약 수신 buffer의 값을 읽어가는 속도가 수신되는 data의 속도보다 느리면 수신 버퍼에는 읽지 않은 data가 점점 증가하게 된다. 
실시간으로 어떤 것을 처리해야 할 경우 읽지 않고 남은 data가 몇 byte인지 아는 것은 중요하다. 실시간으로 Serial로 수신된 값을 처리해야 할 경우에 읽지 않은 data의 개수를 알아서 수신 buffer의 data를 읽는 속도를 빠르게 하거나 송신쪽에서 data를 보내는 속도는 낮추는 방법을 택하게 된다. 

COMSTAT 을 이용하면 송수신 버퍼에 남아있는 읽지 않은 data의 byte 수를 알 수 있다. 구체적인 것은
http://msdn.microsoft.com/en-us/library/aa363200(VS.85).aspx 를 참조하면 된다.

이것을 이용해서 함수를 만들면 아래와 같다 (구체적인 코드는 참고사이트[4]를 참조하면 된다).
송신 data의 경우는 cbOutQue를 이용하면 된다.

int CSerial::ReadDataWaiting( void )
{
	if( !m_bOpened || m_hIDComDev == NULL ) return( 0 );

	DWORD dwErrorFlags;
	COMSTAT ComStat;

	ClearCommError( m_hIDComDev, &dwErrorFlags, &ComStat );

	return( (int) ComStat.cbInQue );
}
마치며...
지금까지 VC++ 을 이용한 Serial 통신 프로그램 만드는 방법에 대해서 [기초], [고급]으로 나누어서 설명하였습니다. VC++을 이용하여 Serial 통신 프로그램을 만들고자 하는 분들에게 많은 도움이 되었으면 합니다. 혹, 잘못된 설명이나 도움이 될만한 것이 있으면 코멘트 남겨주시기 바랍니다.