로그인

검색

MoA
조회 수 7119 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 게시글 수정 내역 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 게시글 수정 내역 댓글로 가기 인쇄

Visual C++ 시리얼 통신(RS-232) 강좌 (1)

 

먼저 강좌를 하기 전에 몇 가지만 말씀 드리겠습니다일단 처음으로 강좌라는 것을 시도해보네요저는 10년(?)째 프로그래머의 길을 걷고 있는 사람입니다 10년 전에 C언어를 처음 접했습니다무작정 VC++6.0 깔아놓고 그 두꺼운 비주얼 c++ 바이블을 놓고 밤새 일일이 코딩을 하던 안 좋은 기억이 생각나네요초보 때는 무식하게 프로그램을 개발했었지만 요즘엔 그 동안 했던 많은 소스들이 있기에 거의 copy & paste 로 프로그램의 80% 이상을 한다는… (많은 고수 분들이 그럴 것이라 생각되지만.. 나만 그런가^^;)

그런데 제가 프로그램을 하면서 제일 많이 느낀 것은 절대 실무에서 돈되는 테크닉은 공짜로는 거의 안 가르쳐 준다.(학원을 안 다녀봐서 모르겠지만 학원은 가르쳐 주나?)” 이것 입니다그렇다고 그런 것을 욕할 필요는 없지요자기 밥줄 끊으면서 남에게 자기 기술을 오픈 할 아무 이유도 없으니까요.

그래서제가 이 얇고 넓은(OTL) 지식으로 충분히 상용으로 쓸 수 있는 지금도 우리회사에서 쓰고 있는 시리얼통신에 관한 강좌를 하고자 합니다일단 시리얼통신이 무엇인지는 알고 계시다는 가정하에 소스코드와 함께 설명을 드리겠습니다.

그럼 이제 슬슬 한번 해~~봅시다[모든 코드는 직접 타이핑 해 보시길 바랍니다.]

 

먼저 시리얼 통신을 전담하는 클래스를 만들겠습니다먼저 CCmdTarget을 부모 클래스로 하는 클래스를 하나 생성합니다전 클래스 이름을 CPYH_Comm 이라고 하겠습니다.

그런 다음 생성자 함수에서 Serial Port, Baudrate, Parity bit, Data bit, Stop bit를 인자로 받게끔 하겠습니다.

헤더파일에 생성자 함수는 다음과 같이 되겠고

 

class CPYH_Comm : public CCmdTarget

{

             DECLARE_DYNCREATE(CPYH_Comm)

 

                           CPYH_Comm() {};           // protected constructor used by dynamic creation

                           ~CPYH_Comm();

Cpp 파일의 생성자 함수는 다음과 같이 하면 되겠죠?

 

CPYH_Comm::CPYH_Comm(CString port,CString baudrate,CString parity,CString databit,CString stopbit)

{

             m_sComPort = port;                                                     // Comport

             m_sBaudRate = baudrate;                                            // Baud Rate

             m_sParity = parity;                                                      // Parity Bit

             m_sDataBit = databit;                                                   // Data Bit

             m_sStopBit = stopbit;                                                   // Stop Bit

             m_bFlowChk = 1;                                                        // Flow Check

             m_bIsOpenned = FALSE;                                             // 통신포트가 열려 있는지

             m_nLength = 0;                                                           // 받는 데이터의 길이

             memset(m_sInBuf,0,MAXBUF*2);                                  // Receive Buffer 초기화

             m_pEvent = new CEvent(FALSE,TRUE);                        // 쓰레드에서 사용할 이벤트

}

 

위와 같이 인자로 받은 파라메타로 멤버변수의 값을 설정합니다생성자 함수에서 각 파라메타를 설정합니다변수는 벌써 헤더파일에 선언이 되어 있어야 함은 물론 이구요.

자 그럼 이제 통신 포트를 실제로 여는 함수를 만들어 보겠습니다.

함수는 BOOL CPYH_Comm::Create(HWND hWnd) 이렇게 선언을 하겠습니다여기서 hWnd 는 나중에 클래스 객체를 사용하는 클래스에 메시지를 전달하기 위하여 클래스의 핸들을 인자로 받습니다그럼 함수의 구현부로 들어가 보지요.

 

BOOL CPYH_Comm::Create(HWND hWnd)

{

             m_hWnd = hWnd;                                  // 메시지를 보낼때 사용

 

             m_hComDev = CreateFile(m_sComPort, GENERIC_READ | GENERIC_WRITE,

                                        0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,

                                        NULL);                    // 시리얼 포트 오픈

            

             if (m_hComDev!=INVALID_HANDLE_VALUE)    // 포트가 정상적으로 열리면 

                           m_bIsOpenned = TRUE;                 // 성공

             else                                                          // 아니면

                           return FALSE;                              // 실패하고 빠져나감

 

             ResetSerial();                                            // 시리얼 포트를 설정값대로 초기화

 

             m_OLW.Offset = 0;                                      // Write OVERLAPPED structure 초기화

             m_OLW.OffsetHigh = 0;

             m_OLR.Offset = 0;                                      // Read OVERLAPPED structure 초기화

             m_OLR.OffsetHigh = 0;

             // Overlapped 구조체의 이벤트를 생성

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

             if (m_OLR.hEvent == NULL) {

                           CloseHandle(m_OLR.hEvent);

                           return FALSE;

             }

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

             if (m_OLW.hEvent == NULL) {

                           CloseHandle(m_OLW.hEvent);

                           return FALSE;

             }

             // 시리얼 데이터를 받기위한 스레드 생성

             AfxBeginThread(CommThread,(LPVOID)this);

             // DTR (Data Terminal Ready) signal 을 보냄

             EscapeCommFunction(m_hComDev, SETDTR);   // MSDN 참조

             return TRUE;

}

 

위 내용을 종합해서 설명하면 시리얼포트를 열고 정상적으로 통신 포트가 열리면 시리얼포트를 설정값에 따라 초기화 한 다음 데이터를 보내고 받을 overlapped i/o를 초기화하고 각각의 이벤트를 생성하는 것입니다그런 다음 통신포트로 들어오는 데이터를 받기 위해 쓰레드를 생성하고 DTR 시그널을 set 하는 것이 이 Create 함수의 동작입니다.

위 함수 내용 안에 있는 ResetSerial() 함수를 만들어 보겠습니다말 그대로 시리얼 포트를 초기화하는 것입니다.

그럼 함수를 구현해 볼까요?

 

void CPYH_Comm::ResetSerial()

{

             DCB                    dcb;

             DWORD   DErr;

             COMMTIMEOUTS CommTimeOuts;

            

             if (!m_bIsOpenned)

                                        return;

            

             // 통신포트의 모든 에러를 리셋

             ClearCommError(m_hComDev,&DErr,NULL);

             // 통신포트의Input/Output Buffer 사이즈를 설정

             SetupComm(m_hComDev,InBufSize,OutBufSize);

             // 모든 Rx/Tx 동작을 제한하고 또한 Buffer의 내용을 버림

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

 

             // set up for overlapped I/O (MSDN 참조)

             // 통신 선로상에서 한바이트가 전송되고 또한 바이트가 전송되기까지의 시간

             CommTimeOuts.ReadIntervalTimeout = MAXDWORD ;

             // Read doperation 에서 TimeOut을 사용하지 않음

             CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;

             CommTimeOuts.ReadTotalTimeoutConstant = 0 ;

                          

             // CBR_9600 is approximately 1byte/ms. For our purposes, allow

             // double the expected time per character for a fudge factor.

             CommTimeOuts.WriteTotalTimeoutMultiplier = 0;

             CommTimeOuts.WriteTotalTimeoutConstant = 1000;

             // 통신포트의TimeOut을설정

             SetCommTimeouts(m_hComDev, &CommTimeOuts);       

 

 

             memset(&dcb,0,sizeof(DCB));

             dcb.DCBlength = sizeof(DCB);

             // 통신포트의 DCB를 얻음

             GetCommState(m_hComDev, &dcb);

             // DCB를 설정(DCB: 시리얼통신의 제어 파라메터, MSDN 참조)

             dcb.fBinary = TRUE;

             dcb.fParity = TRUE;

 

             if (m_sBaudRate == "300")

                           dcb.BaudRate = CBR_300;

             else if (m_sBaudRate == "600")

                           dcb.BaudRate = CBR_600;

             else if (m_sBaudRate == "1200")

                           dcb.BaudRate = CBR_1200;

             else if (m_sBaudRate == "2400")

                           dcb.BaudRate = CBR_2400;

             else if (m_sBaudRate == "4800")

                           dcb.BaudRate = CBR_4800;

             else if (m_sBaudRate == "9600")

                           dcb.BaudRate = CBR_9600;

             else if (m_sBaudRate == "14400")

                           dcb.BaudRate = CBR_14400;

             else if (m_sBaudRate == "19200")

                           dcb.BaudRate = CBR_19200;

             else if (m_sBaudRate == "28800")

                           dcb.BaudRate = CBR_38400;

             else if (m_sBaudRate == "33600")

                           dcb.BaudRate = CBR_38400;

             else if (m_sBaudRate == "38400")

                           dcb.BaudRate = CBR_38400;

             else if (m_sBaudRate == "56000")

                           dcb.BaudRate = CBR_56000;

             else if (m_sBaudRate == "57600")

                           dcb.BaudRate = CBR_57600;

             else if (m_sBaudRate == "115200")

                           dcb.BaudRate = CBR_115200;

             else if (m_sBaudRate == "128000")

                           dcb.BaudRate = CBR_128000;

             else if (m_sBaudRate == "256000")

                           dcb.BaudRate = CBR_256000;

             else if (m_sBaudRate == "PCI_9600")

                          dcb.BaudRate = 1075;

             else if (m_sBaudRate == "PCI_19200")

                           dcb.BaudRate = 2212;

             else if (m_sBaudRate == "PCI_38400")

                           dcb.BaudRate = 4300;

             else if (m_sBaudRate == "PCI_57600")

                           dcb.BaudRate = 6450;

             else if (m_sBaudRate == "PCI_500K")

                           dcb.BaudRate = 56000;

            

 

             if (m_sParity == "None")

                           dcb.Parity = NOPARITY;

             else if (m_sParity == "Even")

                           dcb.Parity = EVENPARITY;

             else if (m_sParity == "Odd")

                           dcb.Parity = ODDPARITY;

 

             if (m_sDataBit == "7 Bit")

                           dcb.ByteSize = 7;

             else if (m_sDataBit == "8 Bit")

                           dcb.ByteSize = 8;              

 

             if (m_sStopBit == "1 Bit")

                           dcb.StopBits = ONESTOPBIT;

             else if (m_sStopBit == "1.5 Bit")

                           dcb.StopBits = ONE5STOPBITS;

             else if (m_sStopBit == "2 Bit")

                           dcb.StopBits = TWOSTOPBITS;

 

             dcb.fRtsControl = RTS_CONTROL_ENABLE;

             dcb.fDtrControl = DTR_CONTROL_ENABLE;

            dcb.fOutxDsrFlow = FALSE;

 

             if (m_bFlowChk) {

                           dcb.fOutX = FALSE;

                           dcb.fInX = FALSE;

                           dcb.XonLim = 2048;

                           dcb.XoffLim = 1024;

             }

             else {

                           dcb.fOutxCtsFlow = TRUE;

                           dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;

             }

             // 설정된 DCB로 통신포트의 제어 파라메터를 설정

             SetCommState(m_hComDev, &dcb);

             // Input Buffer에 데이터가 들어왔을 때 이벤트가 발생하도록 설정

             SetCommMask(m_hComDev,EV_RXCHAR);

}

 

이렇게 해서 통신포트를 하나 열고 그 포트를 초기화 했습니다그럼 이제 데이터를 보내는 함수와 받는 함수를 만들어야 겠지요그럼 먼저 데이터를 보내는 함수를 만들어 봅시다.

함수는 다음과 같습니다.

 

BOOL CPYH_Comm::Send(LPCTSTR outbuf, int len)

{

             BOOL                  bRet=TRUE;

             DWORD                ErrorFlags;

             COMSTAT            ComStat;

 

             DWORD                BytesWritten;

             DWORD                BytesSent=0;

 

             // 통신 포트의 모든 에러를 리셋

             ClearCommError(m_hComDev, &ErrorFlags, &ComStat);

             // overlapped I/O를 통하여 outbuf의 내용을 len길이 만큼 전송

             if (!WriteFile(m_hComDev, outbuf, len, &BytesWritten, &m_OLW)) {

                           if (GetLastError() == ERROR_IO_PENDING){

                                        if (WaitForSingleObject(m_OLW.hEvent,1000) != WAIT_OBJECT_0)

                                                     bRet=FALSE;

                                        else

                                                     GetOverlappedResult(m_hComDev, &m_OLW, &BytesWritten, FALSE);

                           }

                           else /* I/O error */

                                        bRet=FALSE; /* ignore error */

             }

             // 다시 한번 통신포트의 모든 에러를 리셋

             ClearCommError(m_hComDev, &ErrorFlags, &ComStat);

            

             return bRet;

 

}

 

이번에는 통신 포트를 통하여 들어온 데이터를 받는 함수를 구현해 보겠습니다함수는 다음과 같습니다. Inbuf에 len 만큼 길이의 데이터를 받는 것입니다별도의 설명은 드리지 않겠습니다.

 

int CPYH_Comm::Receive(LPSTR inbuf, int len)

{

             CSingleLock lockObj((CSyncObject*) m_pEvent,FALSE);

             // argument value is not valid

             if (len == 0)

                           return -1;

             else if  (len > MAXBUF)

                           return -1;

 

             if (m_nLength == 0) {

                           inbuf[0] = '';

                           return 0;

             }

             // 정상적이라면 본루틴으로 들어와 실제 들어온 데이터의 길이 만큼 Input Buffer에서 데이터를 읽음

             else if (m_nLength <= len) {

                           lockObj.Lock();

                           memcpy(inbuf,m_sInBuf,m_nLength);

                           memset(m_sInBuf,0,MAXBUF*2);

                           int tmp = m_nLength;

                           m_nLength = 0;

                           lockObj.Unlock();

                           return tmp;

             }

             else {

                           lockObj.Lock();

                           memcpy(inbuf,m_sInBuf,len);

                           memmove(m_sInBuf,m_sInBuf+len,MAXBUF*2-len);

                           m_nLength -= len;

                           lockObj.Unlock();

                           return len;

             }

}

 

이제 거의 끝나가는 것 같군요. Resetserial 함수에서 생성한 쓰레드는 뭘하기 위한걸까요눈치빠른 분들은 아셨겠지만 통신 포트를 통하여 데이터가 들어오면 EV_RXCHAR  이벤트가 발생하도록 설정하였습니다따라서 우리가 생성한 쓰레드에서는 그 이벤트를 기다리다가EV_RXCHAR 이벤트가 발생하면 overlapped i/o 동작을 통하여 input buffer 에 들어오는 데이터를 복사하고 Create 함수에서 인자로 받은 핸들에 데이터가 들어왔다고 메시지를 보내 줍니다그러면 우리는 그 메시지를 전달받아 Receive 함수를 사용하여 데이터를 받으면 되는 것이죠이 쓰레드의 함수는 다음과 같습니다역시 별도의 설명은 하지 않겠습니다.

 

UINT CommThread(LPVOID lpData)

{

             extern short          g_nRemoteStatus;

             DWORD                ErrorFlags;

             COMSTAT            ComStat;

             DWORD                EvtMask ;

             char                    buf[MAXBUF];

             DWORD                Length;

             int                       size;

             int                       insize = 0;

            

             // 통신클래스의 객체포인터를 얻음

             CPYH_Comm* Comm = (CPYH_Comm*)lpData;

            

             // 통신포트가 열려 있다면

             while (Comm->m_bIsOpenned) {

                           EvtMask = 0;

                           Length = 0;

                           insize = 0;

                           memset(buf,'',MAXBUF);

                           // 이벤트를 기다림

                           WaitCommEvent(Comm->m_hComDev,&EvtMask, NULL);

                           ClearCommError(Comm->m_hComDev, &ErrorFlags, &ComStat);

                           // EV_RXCHAR에서 이벤트가 발생하면

                           if ((EvtMask & EV_RXCHAR) && ComStat.cbInQue) {

                                        if (ComStat.cbInQue > MAXBUF)

                                                     size = MAXBUF;

                                        else

                                                     size = ComStat.cbInQue;

                                        do {

                                                     ClearCommError(Comm->m_hComDev, &ErrorFlags, &ComStat);

                                                     // overlapped I/O를 통해 데이터를 읽음

                                                     if (!ReadFile(Comm->m_hComDev,buf+insize,size,&Length,&(Comm->m_OLR))) {

                                                     // 에러

                                                                  TRACE("Error in ReadFilen");

                                                                  if (GetLastError() == ERROR_IO_PENDING)       {

                                                                                if (WaitForSingleObject(Comm->m_OLR.hEvent, 1000) != WAIT_OBJECT_0)

                                                                                             Length = 0;

                                                                                else

                                                                                             GetOverlappedResult(Comm->m_hComDev,&(Comm->m_OLR),&Length,FALSE);

                                                                  }

                                                                  else

                                                                                Length = 0;

                                                     }

                                                     insize += Length;

                                        } while ((Length!=0) && (insize<size));

                                        ClearCommError(Comm->m_hComDev, &ErrorFlags, &ComStat);

 

                                        if (Comm->m_nLength + insize > MAXBUF*2)

                                                     insize = (Comm->m_nLength + insize) - MAXBUF*2;

 

                                        // 이벤트 발생을 잠시 중단하고 input buffer에 데이터를 복사

                                        Comm->m_pEvent->ResetEvent();

                                        memcpy(Comm->m_sInBuf+Comm->m_nLength,buf,insize);

                                        Comm->m_nLength += insize;

                                        // 복사가 끝나면 다시 이벤트를 활성화 시키고

                                        Comm->m_pEvent->SetEvent();

                                        LPARAM temp=(LPARAM)Comm;

                                        // 데이터가 들어왔다는 메시지를 발생

                                        SendMessage(Comm->m_hWnd,WM_MYRECEIVE,Comm->m_nLength,temp);

                           }

             }

             PurgeComm(Comm->m_hComDev, PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);

             LPARAM temp=(LPARAM)Comm;

             // 쓰레드가 종료될 때 종료 메시지를 보냄

             SendMessage(Comm->m_hWnd,WM_MYCLOSE,0,temp);

             return 0;

}

 

마지막으로 통신 포트를 닫는 함수인 Close 함수, overlapped i/o close 하는 HandleClose함수,

통신 포트의 버퍼를 클리어하는 Clear 함수만 구현하면 끝이 납니다.

별도의 설명이 없어도 이해 하실 줄 믿습니다그렇죠쉽네~머 시리얼이 별건가?

 

void CPYH_Comm::Close()

{

             if (!m_bIsOpenned)

                           return;

 

             m_bIsOpenned = FALSE;

             SetCommMask(m_hComDev, 0);

             EscapeCommFunction(m_hComDev, CLRDTR);

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

             Sleep(500);

 

}

 

void CPYH_Comm::HandleClose()

{

             CloseHandle(m_hComDev);

             CloseHandle(m_OLR.hEvent);

             CloseHandle(m_OLW.hEvent);

 

}

 

void CPYH_Comm::Clear()

{

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

             memset(m_sInBuf,0,MAXBUF*2);

             m_nLength = 0;

 

}

 

이렇게 시리얼 통신 클래스의 모든 구현이 끝났습니다아주 특수한 경우가 아니라면 제 경험상 거의 대부분 무리 없이 잘 돌아가는 클래스입니다.


1차 출처 : http://blog.naver.com/loginpark/120089678114

2차 출처 : http://roter.pe.kr/80

?

  1. Programming 게시판 관련

  2. Visual C++ 시리얼 통신(RS-232) 강좌 (2)

  3. Visual C++ 시리얼 통신(RS-232) 강좌 (1)

  4. DLL 이란

  5. UpdateData(TRUE) or UpdateData(FALSE) 구분

  6. InvalidateRect 함수, flag성 메세지

  7. UpdateWindow 함수에 대하여..

  8. 다이얼로그 resize 시 child control의 그래픽 깨짐 해결

  9. Property Sheet의 버튼 속성 변경하기

  10. CPaneDialog 초기화 (OnInitDialog)

  11. CDockablePane 버튼 비활성화 문제

  12. CDockablePane 배경화면 회색으로 변경

  13. CPaneDialog의 context menu 안뜨게 하기

  14. 리스트 컨트롤 클릭 이벤트

  15. Which Font is the default for MFC Dialog Controls

  16. 세마포어를 이용한 생산/소비자

  17. stdafx.h 사용 (미리 컴파일된 헤더)

  18. 윈도우 프로그램의 종료 메시지 순서

  19. Deploying a Visual C++ Application (배포 방법)

  20. MFC 클래스간 통신

  21. 모달리스 대화상자

Board Pagination Prev 1 2 3 4 Next
/ 4