비동기 OVERLAPPED 읽기 수행하기

 

일단 비동기 read부터 알아보자. 역시나 백문이 불여일견이니 코드부터 보자

 

DWORD CSerialz::ReadData(BYTE *pBuff, DWORD nToRead)
{
 DWORD dwRead;
 DWORD dwError;

 //COMSTAT은 현재 읽기, 쓰기 버퍼등 통신에 관련된 상태를 담는 구조체
 DWORD dwErrorFlags;
 COMSTAT comstat;

 

//장치를 사용 할 수 있도록 오류플래그를 받아오고 오류 상태를 해제한다.

//그리고 장치 상태를 얻어온다.
ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
 
 // 읽기버퍼에 들어와있지만 아직 읽지 못하고 남아있는 바이트 수
 dwRead = comstat.cbInQue;

 

//읽기 버퍼에 아직 읽지 못하고 남아있는 바이트가 남아 있는 경우
 if(dwRead > 0)
 {
    //읽기 버퍼를 nToRead만큼 읽어 들여온다. 그리고 읽기 성공한 바이트 수를 

    //dwRead에 다시 저장 비동기 함수라 바로 FALSE 반환
    if(!ReadFile(m_hIDComDev, pBuff, nToRead, &dwRead, &m_OverlappedRead))
   {
       //정상적으로 읽기 작업이 작업 큐에 저장
       if(GetLastError() == ERROR_IO_PENDING)
      { 
          // timeouts에 정해준 시간만큼 기다려준다.

          //COMMTIMEOUTS에 읽기는 기다리지 않도록 설정한 상태
          while (! GetOverlappedResult(m_hIDComDev,

                                                    &m_OverlappedRead,

                                                    &dwRead,

                                                    TRUE))
          {
               //읽을 때까지 기다렸는데 미리 설정한 타임아웃 이내에 읽지 못한 경우 
               dwError = GetLastError();
               if (dwError != ERROR_IO_INCOMPLETE)
               {

                   //장치의 에러 상태를 해제한다.
                   ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
                   break;

                }
           }
       }

       //작업큐에 읽기 작업이 정상적으로 저장되지 못한 경우
       else
       {
            dwRead = 0;

             //에러상태를 해제한다.
            ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
        }
     }
 }

 

 //실제로 읽은 바이트 수 반환
 return(dwRead); 

 

뭐 딱히 별다른 설명이 필요하지 않다. 읽는 것 자체는 Overlapped write와 크게 다르지 않다. 다르다고 할 만한게 있다면 ClearCommError의 사용 정도가 있겠다. ClearCommError 함수는 파일디스크립터 (핸들) 이 가리키는 IO Device를 사용 할 수 있도록 에러 상황을 받아오고서는 에러 상태를 해제한다. 그리고 IO Device관련된 정보를 받아오는 기능을 한다.

 

이제 우리는 읽기 버퍼로부터 바이트를 읽어 올 수 있게 되었다. 그렇다면 이제 남아있는 문제는 "어떻게 읽기 버퍼로 읽을 수 있는 바이트가 저장되는 것을 알아 차릴 수 있는 가?"이다. 방법은 쓰레드를 이용해 읽기 버퍼에 데이터가 들어올 때 발생하는 이벤트를 감시하고 있는 것인데 쓰레드로 돌릴 함수는 아래와 같다.

 

//COM 포트에서 이벤트가 일어나는지 감시하는 스레드 함수 DWORD CSerialz::THWatchEvPort(CSerialz *pComm)
{
   DWORD  dwEvent;
   OVERLAPPED os;
   BOOL  bOk = TRUE;  
   BYTE  buff[4096]; 
   DWORD  dwBufferCounter;   

 

   // Overap Structure 설정
   memset(&os, 0, sizeof(OVERLAPPED));
   if(!(os.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) bOk = FALSE;

 

   // m_hIDComDev COM 포트의 읽기 버퍼에 데이터가 들어왔을 때 발생하는

   //이벤트및 몇가지 에러 상황을 감지하도록 설정
   if(! SetCommMask(pComm->m_hIDComDev, 

                             EV_RXCHAR  |  EV_BREAK  |  EV_ERR )) bOk = FALSE;

 

   // OVERLAPPED 구조체 설정및 COM포트의 감지 이벤트 설정 중에 문제가

   //발생했으면 return FALSE
   if(!bOk) { 

      AfxMessageBox(_T("Error while Receive Waiting") + pComm->szPort); 

      return FALSE; 

   }

 

   // port가 열려 있는 동안.....포트가 PortClose에 의해 닫히면 m_bPortOnOff

   // 가 FALSE가 되어 종료.
   while(pComm->m_bPortOnOff)  
   {         
      dwEvent = 0;
    
       //이미 이벤트가 발생해 있는 상태라면 TRUE 반환, 이벤트 미발생 상태로

       //펜딩되면 FALSE 반환. OVERLAPPED의 이벤트는 함수 호출시 자동 RESET   
       BOOL result = WaitCommEvent(pComm->m_hIDComDev, &dwEvent, &os);  
  
       //WaitCommEvent함수 호출시 이벤트 발생된 상태로 바로 이벤트 감지
       if(result)
       {
          //DoNothing
        }
        else
       {
           //WaitCommEvent가 PENDING된 경우
           if(GetLastError() == ERROR_IO_PENDING)
          {

              //이벤트가 발생하면 다음단계로 진행
              WaitForSingleObject(os.hEvent,INFINITE);
           }  

          //에러로 인한 이벤트 발생시 스레드 종료
          else
          {
                break;
           }
      }     

      

      //발생 이벤트가 읽기 버퍼에 데이터 수신으로 인한 이벤트인 경우
      if((dwEvent & EV_RXCHAR) == EV_RXCHAR)
      {   // 포트에서 읽을 수 있는 만큼 읽는다.
          do

          {

                //읽기 버퍼에서 4096 (전체 버퍼 싸이즈) 만큼 읽어 들여와서 읽기

                //성공한 바이트 수를 dwBufferConter에 저장한다.
                dwBufferCounter = pComm->ReadData(buff, 4096);    // 데이터 읽기. 
                pComm->FrmTHRcvData(buff, (BYTE)dwBufferCounter);  //data 사용   
           }while (dwBufferCounter); // 읽을 수 있는 data가 없을 때까지 읽어온다.   
       }
       else
      {
          //에러 상황으로 인해 이벤트가 발생한 경우는 그냥 이 스레드를 종료시킴
          break;
       }
    }

 //OVERLAPPED 구조체의 이벤트 핸들을 닫고 스레드 포인터를 NULL로 한다.
 CloseHandle(os.hEvent);
 pComm->m_ThrdEvPort = NULL;

 return(TRUE);
}

 

주석만으로 전체 코드 구성을 이해 하는데 충분하다고 생각한다. 위의 함수를 CreateThread함수나 AfxBeginThread를 이용하여 돌리면, 읽기 버퍼에 데이터가 들어오는 경우 읽기 버퍼에서 더이상 읽을 버퍼가 없을 때까지 읽어들여 온다.

 

이상 RS232를 이용한 시리얼 통신 코드는 링크를 통해 볼 수 있다.(비공개)

 

Posted by 굿쟌
,

OVERLAPPED 구조체를 이용한 비동기 쓰기

 

시리얼 COM 포트 통신을 이용해 데이터를 읽고 쓰는 방법에 대해 알아보자. 이미 이전포스트에서 주욱 설명해 오고 있는것과 마찬가지로 비동기(Overlapped) 읽기, 쓰기에 대해 다룰 것인데 먼저 쓰기부터 알아보려 한다. 왜냐하면 읽기가 좀더 어렵기 때문이다. ㅎㅎ

 

쓰기는 내가 데이터를 모두 가지고 있고 어디부터 어디까지 언제 쓸 것인지를 선택 할 수 있다. 그러나 쓰기에 비해 읽기는 언제 상대방이 나에게 데이터를 전송할지 알 수 없다. 따라서 상대방이 데이터를 전송하여 내가 읽을만한 데이터가 읽기 버퍼에 들어오는 이벤트를 감지하는 쓰레드를 만들어 돌려야 하기때문에 상대적인 난이도가 쓰기에 비해 높다.

 

어찌됐든, 쓰기는 아래와 같은 WriteFile함수를 이용한다.

 

 BOOL WINAPI WriteFile(
  __in         HANDLE hFile,
  __in         LPCVOID lpBuffer,
  __in         DWORD nNumberOfBytesToWrite,
  __out_opt    LPDWORD lpNumberOfBytesWritten,
  __inout_opt  LPOVERLAPPED lpOverlapped
);

 

쓰기가 성공하면 TRUE를 반환하고, 지금 설명하고 있는 비동기 쓰기의 경우에는 무조건 FALSE를 반환한다. 그러나 GetLastError()를 통해 얻어낸 에러가 ERROR_IO_PENDING이면 함수가 실패 한 것은 아니다.

 

인자를 설명 하자면 처음부터, 파일디스크립터 (핸들), 쓸 데이터가 들어있는 버퍼, 몇바이트를 쓸지, 실제로 쓴 바이트 수, OVERLAPPED 구조체의 주소 이다.

 

이전전 포스트에서 설명한 OVERLAPPED가 여기서 쓰이는 것이다.

백문이 불여일견이니 1바이트를 시리얼 통신으로 쓰는 함수를 살펴보자

 

 

BOOL CSerialz::WriteCommByte(unsigned char ucByte)
{
 BOOL bWriteStat;
 DWORD dwBytesWritten;

 

//1바이트를 쓰고 다 쓰면 m_OverlappedWirte.hEvent를 set시킨다. 

bWriteStat = WriteFile(m_hIDComDev,

                              (LPSTR) &ucByte,

                              1,

                              &dwBytesWritten,

                              &m_OverlappedWrite);  

 

//overlapped write는 FALSE를 바로 반환하고 LASTERROR가 ERROR_IO_PENDING

if(!bWriteStat && (GetLastError() == ERROR_IO_PENDING))
{
      if(WaitForSingleObject(m_OverlappedWrite.hEvent, 1000))
          dwBytesWritten = 0;
      else
     {
          GetOverlappedResult(m_hIDComDev,

                                        &m_OverlappedWrite,

                                        &dwBytesWritten,

                                        FALSE);

       

           //사실 이 부분은 잘못된 예이다. 시리얼 통신을 비롯한 모든

           //통신 디바이스 에서는 overlapped.offset이 0으로 설정되어야 한다. 빼도좋다.
           m_OverlappedWrite.Offset += dwBytesWritten;   

      }
 }

 

//의미있는 반환값을 가지려면 쓰기성공한 바이트 수가 0 이상인지 체크해야 한다.

 return(TRUE);

 

 

위 코드에서 쓰인 GetOverlappedResult함수는 write 명령어를 수행할 때 넣었던 OVERLAPPED 구조체를 인수로 하여 해당 write명령어의 결과를 얻을 수 있게 해주는 함수이다.

 

 BOOL WINAPI GetOverlappedResult(
  _In_   HANDLE hFile,
  _In_   LPOVERLAPPED lpOverlapped,
  _Out_  LPDWORD lpNumberOfBytesTransferred,
  _In_   BOOL bWait
);

 

순서별로 인수는 COM 포트 파일핸들, OVERLAPPED 구조체 포인터, 더블워드(4바이트) 값으로 실제로 쓰기 된 바이트 수를 받을 변수에 대한 포인터, 그리고 write가 종료되기까지 기다릴지 말지에 대한 결정을 위한 BOOL 변수이다.

 

마지막 변수 bWait는 아래와 같은 규칙으로 쓰인다.

 

 

1. bWait == TRUE  && OVERLAPPED.internal == STATUS_PENDING

 

   bWait가 TRUE인 경우에 미리 COMMTIMEOUTS에 설정한 타임아웃 값

   만큼 기다린다. 기다리는 도중 완료되면 TRUE 반환 TIMEOUT되면 FALSE 반환

   GetLastError() 함수는 ERROR_IO_INCOMPLETE 반환

 

2. bWait == FALSE : && OVERLAPPED.internal == STATUS_PENDING

 

   bWait가 FALSE인 경우에 완료되길 기다리지 않고 바로 쓰기한 바이트를 넣고 반환된다.

   아직 쓰기작업이 끝나지 않았다면 FALSE를 반환하고 GetLastError() 함수는

   ERROR_IO_INCOMPLETE 반환

 

저 쓰기 구조를 요약해 보자면 아래와 같다.  

 

1. Overlapped로 비동기 write명령을 호출한다.

2. write명령어의 반환이 FALSE이고 GetLastError()의 반환이 ERROR_IO_PENDING이다.

3. write명령어에 쓰인 OVERLAPPED의 이벤트가 SET되길 1000ms 동안 기다린다.

   (참고로 wrtie명령어를 호출 할 때 OVERLAPPED의 이벤트 핸들은 자동으로 RESET됨)

4. write작업이 완료되고 이벤트가 SET되어 다음 단계로 넘어간다.

5. GetOverlappedResult를 이용하여 실제로 쓰기된 바이트 수를 얻는다.

6. 불필요한 통신 IO 이용 파일 핸들에서 OVERLAPPED의 offset 재조정

 

마지막으로 6번이 중요한데 위 코드에서 빨간색으로 색을 칠한 구문은 지난번 포스트에서도 언급한바와 같이 파일핸들이 열고 있는 IO Device가 통신 디바이스 일때는 사실상 쓸 필요도 없거니와 써서도 안된다.

 

어쨋든 요런식으로 1byte를 시리얼 통신으로 쓸 수 있으며, 이 1바이트 쓰기를 반복하여 d아래와 같이 원하는 데이터를 전송 할 수가 있다.

 

// 버퍼로부터 설정된 데이터를 사이즈만큼 쓴다.

int CSerialz::SendData(char *buffer, int size) 

{

    // port가 제대로 설정되어 있지 않을 때 - 쓰기 실패
    if(!m_bPortOnOff || m_hIDComDev == NULL) return(0); 

 

    DWORD dwBytesWritten = 0;

    for(int i=0; i<size; i++)
    {  

        // 여러 바이트를 쓰기 위하여 한 바이트씩 쓰는 함수를 중복 호출
        WriteCommByte(buffer[i]); // 쓰기 위한 함수 호출

 

        //요부분에 writeCommByte의 TRUE FALSE를 분간하는 코드가 있음좋다.
        dwBytesWritten++;
    }

    return((int)dwBytesWritten);  // 쓰기에 성공한 바이트 수 리턴

코드는 지금 공부하고 있는 코드를 복사해서 올리다 보니깐 좀 틀린 부분이 있긴 하지만

일단은 틀린 부분과 추가해야 할 사항들을 추가 하기위해 틀린 코드도 그대로 올리고 있으니 참고하여 코드를 봐야 한다.

 

 

Posted by 굿쟌
,

 이전에 올린 포스트에서 어떻게 시리얼 COM 포트를 여는지, 또 어떻게 OVERLAPPED 구조체를 만드는지와 포트를 이용한 읽기 쓰기 작업에 타임아웃을 거는 방법에 대해 알아보았다. 그런데 뭔가 허전하지 않은가? 맞다. 시리얼 COM 포트를 열긴 열었는데 실제로 이 시리얼 통신이 어떤 특성을 가질 것인지는 설정하지 않았다.

 

그래서 요번에는 DCB (Device Control Block)를 이용한 COM 포트의 시리얼 통신 특성을 설정해 볼 것이다.

 

먼저 시리얼 통신 특성 설정에는 DCB 구조체가 사용된다. 시리얼 통신에 사용되는 여러가지 통신특성들이 설정되어 있는 구조체로 자세한 사항은 아래를 참조하면 되겠다.

 

 

매우 많은 필드와 설명이 있지만, 알아야 되는 필드는 몇개 되지 않는다. 대부분의 설정값은 시스템이 초기에 설정해 놓은 값을 그대로 사용한다. 사용자는 GetCommState함수를 이용해 시스템이 설정해 놓은 COM 포트의 설정값을 가지고 오고 몇몇 변수를 고친다음에 SetCommState로 다시 COM 포트의 설정값을 변경하면 된다.

 

 BOOL GetCommState (HANDLE hFile, LPDCB lpDCB)

 BOOL SetCommState (HANDLE hFile, LPDCB lpDCB)

 

실제 사용은 아래와 같이 한다.

 

 

 

 

 

Get으로 가지고 와서 버드레이트, 패리티, 바이트로 뭉쳐서 쓰기 위해 받는 비트 수, 그리고 스톱 비트를 몇개 사용할 것인지를 결정하여 Set으로 설정했다.

(코드를 그대로 복사하다보니..-_- 위에 접어놓은 DCB 설명을 참조하여 설정할 것)

 

이제 거의 모든 설정이 다 끝났다. 마지막으로 아래 SetupComm 함수를 이용하여 읽기 쓰기에 사용할 큐의 크기를 지정하고 각 큐를 깔끔하게 비워낸다.

 

//읽기 쓰기 큐 4096바이트설정 

SetupComm(m_hIDComDev, 4096, 4096)

//COM 포트를 , 2번째 인자 왼쪽부터 현재 전송작업 취소, 쓰기버퍼 비움, 현재

//읽기 작업 취소, 읽기 버퍼 비움 한다. 라는 의미 

PurgeComm(m_hIDComDev, PURGE_TXABORT | PURGE_TXCLEAR | 

                    PURGE_RXABORT | PURGE_RXCLEAR);

 

비로소 COM 포트를 이용한 시리얼 통신을 할 수 있는 상태가 되었다.

Posted by 굿쟌
,

OVERLAPPED 구조체 설정

 

 오버랩, 비오버랩에 관한 이야기는 이전 포스트에서 한번 언급을 했었다. 요번에는 overlapped 읽기 쓰기에 사용되는 OVERLAPPED 구조체가 무엇인지, 그리고 어떤 의미를 가지고 있는지에 대해 알아 보겠다.

 

 그런데 구조체 내부 구조와 사용 방법에 대해 알아보기에 앞서, 먼저 OVERLAPPED 구조체를 왜 쓰는 것인가에 대해 생각해 볼 필요가 있다. 이전 포스트에서 overlapped 방식의 통신에서는 read와 write가 실제 작업 종료여부와는 상관 없이 호출하자 마자 끝나버린다고 했었다.

 

 그럼 자연히 드는 한가지 의문점은 언제 끝나는지 어떻게 아냐 라는 것이다. 일단 호출해 버리면 바로 반환되어 버리기 때문에, 언제 읽기 쓰기 작업이 종료되었는지 그리고 얼만큼을 성공적으로 썻는지에 대한 정보를 알기가 어렵다.

 

이에 대한 해답으로 나온 것이 바로 OVERLAPPED 구조체로, C#으로 따지자면 비동기 메소드의 ASyncResult 객체와 쓰임새가 비슷하다고 생각해도 좋다.

 

그럼이제 OVERLAPPED 구조체에 대해 살펴보자. OVERLAPPED 구조체는 아래와 같다.

 

typedef struct _OVERLAPPED{

 ULONG_PTR internal;

 ULONG_PTR internalHigh;

 DWORD Offset;

 DWORD OffsetHigh;

 HANDLE hEvent;

}OVERLAPPED;

 

여러가지 인자가 있지만, 시리얼 통신에서 알아 두어야만 하는 인자는 hEvent 뿐이다. 이 이벤트는 overlapped 읽기 쓰기 작업이 종료됨과 동시에 set상태가 되어 이 이벤트 객체를 waitForSingleObject와 같은 함수로 기다릴 수도 있다. (이 이벤트는 bManualReset을 TRUE로 설정하여 생성해도 read, write함수에 인자로 넣는 순간 자동으로 Reset되는 특이성(?) 이 있다.)

 

참고로 시리얼 통신을 공부하면서 가장 헷갈렸던게 바로 OVERLAPPED 구조체의 Offset 인자인데 결론적으로 이야기 하자면 통신 Device에서는 전혀 몰라도 되긴 하다.

 

설명을 좀 하자면.. 먼저 이 OVERLAPPED 구조체가 쓰이는 read write 함수가 어디에 쓰이는지에 대해 알아야 한다. 바로 파일의 비동기 입출력에 쓰인다. 앞서 올린 포스트에서 CreateFile함수가 이름에 충실하게 실제 파일, 그리고 통신 Device까지도 우리가 가지고 작업 할 수 있도록 파일디스크립터 ( 핸들 )을 제공 한다고 했었다.

 

만약 내가 실제로 하드디스크에 100KB 짜리 파일을 쓴다고 생각해 보자. 동기 (NonOverlapped) 쓰기 작업이라고 하면 항상 내가 파일에 쓴만큼 파일포인터는 앞으로 전진할테고 계속해서 나는 그냥 쓰기만 하면 된다. 예를 들어 넓은 논에 모내기를 하는데 나 혼자 심게 되면 어디서 부터 심을지 생각하지 않고, 그냥 처음부터 모를 심을 때 마다 바로 이전에 마지막으로 심었던 모자리 바로 앞에다가 심으면 그만이다.

 

그러나 비동기 (Overlapped) 쓰기 작업은 다르다. 여러 쓰레드가 동시에 읽거나 쓰기 때문에 현재 파일 포인터를 기준으로 쓰거나 읽으려고 하면 차질이 생긴다. 모내기를 예로 들면 2명이서 모내기를 하려고 하는 것과 같다. "미리 나는 0부터 심어 나갈 테니까 너는 50부터 심어나가" 라고 심어나갈 초기 위치를 협의 해야 100짜리 논에 모를 심어 낼 수 있다.

 

따라서 진짜로 파일 쓰기나 읽기에서는 직접 수동으로 읽은만큼 offset을 변경해야 한다.

 

그러나 통신장치에서는 다르다. 그냥 내가 쓰기버퍼에 쓰면 바로바로 FIFO(First In First Out)으로 상대방 읽기 버퍼로 전송되어 쌓이게 된다. (QEUE)  따라서 항상 통신 장치를 Overlapped방식으로 읽기 쓰기 할 때는 이 offset이 0이어야 하며, 0이 아닌 값이 들어가 있어도 무시된다. (0으로 놔두는 것이 좋다.)

 

마지 막으로 OVERLAPPED 구조체의 hEvent를 만들어 주는 것을 보도록 하겠다.

 

 m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

 

참고로 CreateEvent는 이벤트 핸들을 반환하며 아래와 같은 함수원형을 가진다.

 

HANDLE WINAPI CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
                                          BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName);
 

 

보통 인자중에 처음과 마지막 인자는 null을 넣어서 사용한다. 첫 인자는 자식 프로세스에 이 이벤트가 상속되지 않음을 명시하는 거라는데, 다른 프로세스까지 이 이벤트가 영향을 미치지 않도록 하는 의미인것 같다. 보통 다중 프로세스 프로그램은 안짜고 다중 스레드만 짜기 때문에 난 그냥 무시하고 NULL을 넣어서 쓴다. 마지막 인자는 이 이벤트의 이름을 의미하는 인자로 그냥 이름 없는 이벤트를 쓰기위해 NULL을 넣는다.

 

두번째 인자 bManualReset은 좀 중요하다. 이벤트는 상태가 Set, Reset 두 가지가 있는데 WaitForSingleObject와 같은 함수로 이 이벤트를 기다리면 Set상태가 될 때까지 블록 상태가 된다. 그리고 Set 상태가 되면 블록이 해제되고 WaitForSingleObject 이후 단계의 코드로 넘어 가는데 이때 bManualReset이 TRUE인 이벤트는 자동으로 다시 Reset상태가 되지 않는다. FALSE인 이벤트는 지가 자동으로 다음 코드로 넘어갈때 다시 Reset상태로 변경된다.

 

세번째 인자는 이 이벤트를 만들 때, 초기 상태를 SET으로 만들꺼냐 말꺼냐를 묻는 인자다 TRUE이면 초기 상태가 SET이 된다.

Posted by 굿쟌
,

타임아웃 설정하기

 

 시리얼 통신 포트에 읽기 또는 쓰기 작업을 수행하려고 하는데 무언가 문제가 생겨서 작업이 진행되지 않는다면 어떻게 해야 할까? 마냥 하염없이 기다리다보면 프로그램은 영영 돌아오지 못하게 된다. 그래서 시리얼 통신에서는 COMMTIMEOUTS구조체를 제공하여 언제까지 작업을 시도해 볼 것인지를 정 할 수 있게 해놓았다.

 

일단 COMMTIMEOUTS 구조체는 아래와 같다.

 

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

 

인수를 맨 위에꺼 부터 설명하자면,

 

ReadIntervalTimeout은 한바이트를 읽고 다음 바이트를 읽는데 기다릴 시간으로 ms단위

 

ReadTotalTimeoutMultiplier & ReadTotalTimeoutConstant 는 묶어서 설명 하자면

기다릴 시간=읽기요청 바이트 수*ReadTotalTimeoutMultiplier+ ReadTotalTimeoutConstanct

위와 같은 계산식을 따라 기다릴 시간을 설정한다.

 

WrtieTotalTimeoutMultiplier & TotalTimeoutConstant도 묶어서 설명하자면,

기다릴 시간=쓰기요청 바이트 수*WriteTotalTimeoutMultiplier+ WriteTotalTimeoutConstanct

위와 같은 계산식을 따라 기다릴 시간을 설정한다.

 

참고로 여기서 아주 중요한 것이 하나 있는데 ReadIntervalTimeout이 0xFFFFFFFF 즉, MAX_DWORD이고 ReadTotalTimeoutMultiplier와 Constant가 0인 경우에는 재밋게도 현재 읽으력고 하는 버퍼에서 그냥 있는 만큼 읽고 바로 그냥 작업을 종료해 버리게 된다.

 

예를 들어 아래와 같이 타임아웃 구조체를 설정했다고 하면

 

 

 

Read에는 없으면 없는데로 있으면 있는데로 읽기 버퍼에 작업을 수행하는 그때에 읽고 작업을 마친게 된다. 그리고 쓰기 작업에는 얼마나 많이 쓰든지 상관 없이 5000ms 즉, 5초까지는 계속 쓰기 작업을 시도한다.

 

그럼 이제 남은 것은 이 타임아웃 구조체를 시리얼 통신 포트에 적용 하거나, 이미 적용되어 있는 정보를 가져 오는 것인데 이 작업에는 아래의 두가지 함수가 사용된다.

 

 BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeOuts )

 BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts )

 

인자는 뭐 딱 봐도 통신에 사용할 시리얼 포트의 파일디스크립터 (핸들)과 앞서 설명한 COMMTIMEOUTS객체의 포인터 이다. 실패시 0을 반환한다.

 


    지정된 통신 장치에서 모든 읽기, 쓰기 작업에 대해 타임아웃 매개변수를 설정

 

Posted by 굿쟌
,

시리얼 포트를 열어보자.

 

 리눅스에서 입출력은 모두 파일로 간주되어 처리된다. 키보드 입력 화면 출력도 0,1 번 파일디스크립터를 이용하고, 통신 IO인 소켓도 아래와 같이 파일디스크립터인 int로 받아 동작을 제어한다. 소켓의 파일 디스크립터를 printf로 출력해보면 해당 파일디스크립터가 몇번인지 출력할 수도 있다. 어디에서 쓰고 읽을 것인지를 int로 처리하고 있다고 볼 수 있다.

 

int sock = socket(PF_INET,SOCK_STREAM,0); // 소켓도 그냥 int 값의 파일 디스크립터다

 

즉, 유닉스 기반 OS에서 입출력에 관련된 모든 작업은 파일을 통해 가능하다. 이 입출력장치에는 네임드 파이프, 소켓같은 통신 IO와 진짜 파일로 저장하기 위한 IO 장치 등이 포함 된다. 시리얼 통신 포트도 예외는 아니다. 다만 윈도우에서는 파일 디스크립터와 유사한 핸들이라는 것을 사용하는 것만 다를 뿐, 위에서 설명한 것과 동일하게 시리얼 통신포트를 열 수 있도록 CreateFile이라는 함수를 제공한다.

 

CreateFile 함수의 원형은 아래와 같다.

 

 

각 인자들에 대한 설명을 하자면..

 

lpFileName : 파일 이름. 시리얼 COM포트 같은 경우 \\.\COM1과 같은 형식을 따른다.

dwDesiredAccess : 파일 권한이다. 읽고쓰기가 가능하게 하려면 아래와 같이 쓰면 된다.

                             GENERIC_READ | GENERIC_WRITE

dwshareMode : 시리얼 통신에서는 포트를 공유할 수 없으니까 0이다.

lpSecurityAttributes : 정확히는 모르겠으나 사용하지 않는다.

dwCreateDisposition : 파일의 생성 방식이다. 시리얼 통신은 이미 있는 포트를 여는 거니까

                                OPEN_EXISTING 을 사용한다.

dwFlagsAndAttributes : 파일 속성에 대한 의미다. 우리는 Overlapped 읽기 쓰기를 사용 하

                                  기때문에 FILE_FLAG_OVERLAPPED를 넣으면 된다.

hTemplateFile : 뭔지 모르겠지만 사용하지 않아 0을 넣는다.


 

 

그런데, 사실 FILE_ATTRIBUTE_NORMAL은 단독으로 사용되는 것 이외에는 효력이 없다고 하니 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED 는FILE_FLAG_OVERLAPPED와 같은 말이다. 

 

어찌 됐든 위와 같이 타이핑하면 일단 가지고 놀 시리얼 포트를 열 수 잇다.

(물론 COM 포트 번호는 제어판 -> 장치관리자 에서 확인해서 넣어야 한다.)

 

자세한 함수 인자들에 대한 명세는 아래를 참조하면 알 수 있다.

 

 

 

 

 

 

 

Posted by 굿쟌
,

Overlapped 시리얼 통신은 비동기 통신을 의미한다. 일반적인 함수와 같이 시리얼 통신에 파일쓰기 읽기 명령을 내리면 함수가 내부 처리과정을 마칠 때까지 전체 시스템은 블록되며 처리과정을 마치면 처리결과가 return 된다. 반면에 Non-Overlapped 시리얼 통신의 경우 호출 즉시 쓰기 혹은 읽기 함수는 return 되며 전체 프로그램은 진행된다. 대신 함수 내부 과정이 모두 마치게 되면 미리 지정해 놓은 Event가 set된다.

 

Overlapped - Syncronous Function

Non-Overlapped - Asyncronous Function

Posted by 굿쟌
,

보 레이트는 초당 의미있는 정보의 전송갯수를 의미한다. 반면 비트레이트는 초당 전송되는 비트수를 의미한다. 예를 들어 9600 bps의 비트레이트로 아스키코드(1 byte = 8 bit)정보를 전송하는 경우 9600 bit / 8 bit = 1200 즉, 1200개의 아스키 코드를 표현 할 수 있는 정보 단위를 전송 할 수 있다. 따라서 9600 bps는 1200 baud rate라고 할 수 있다. 반면에 9600 bps의 전송속도로 bit 를 전송하는 경우에는 보레이트가 비트레이트와 같은 9600 baud rate가 된다.

 

보 레이트 - 초당 의미있는 정보의 전송량

비트레이트 - 초당 비트 전송량

Posted by 굿쟌
,