메뉴 건너뛰기

OBG

Programming

MoA
조회 수 6716 추천 수 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

?

List of Articles
번호 분류 제목 글쓴이 날짜 조회 수
공지 Tool/etc Programming 게시판 관련 2 MoA 2014.11.01 1709
286 LLM PEFT: Parameter-Efficient Fine-Tuning of Billion-Scale Models on Low-Resource Hardware OBG 2024.04.15 15
285 LLM [VESSL AI] 뉴욕주민의 프로젝트플루토 — LLM, LLMOps를 활용한 금융 미디어의 혁신 OBG 2024.04.21 17
284 LLM Anthropic, LLM의 내부를 이해하는데 있어 상당한 진전을 보임 OBG 2024.06.03 17
283 LLM A Beginner's Guide to Prompt Engineering with GitHub Copilot OBG 2024.04.04 19
282 LLM ChatGPT의 강력한 경쟁 언어모델 등장!, Mixtral 8x7B OBG 2024.04.14 20
281 LLM How LLMs Work ? Explained in 9 Steps — Transformer Architecture OBG 2024.04.11 21
280 LLM Mixture of Experts - Part 2 OBG 2024.04.14 22
279 LLM llama3 implemented from scratch OBG 2024.05.24 24
278 Graphic ASCII 3D 렌더러 만들기 OBG 2024.06.03 24
277 LLM 만능 프롬프트 OBG 2024.04.07 25
276 LLM The difference between quantization methods for the same bits OBG 2024.04.14 25
275 LLM Getting Started with Sentiment Analysis using Python OBG 2024.04.11 27
274 LLM Real-Time Stock News Sentiment Prediction with Python OBG 2024.04.11 28
273 Deeplearning [ifkakao] 추천 시스템: 맥락과 취향 사이 줄타 OBG 2024.01.10 30
272 Tool/etc HuggingFace 공동창업자가 추천하는 AI 분야 입문 서적 OBG 2024.05.24 31
271 LLM [12월 1주] 떠오르는 '미스트랄 7B'...'라마 2' 이어 한국어 모델 세대교체 주도 OBG 2024.03.05 34
270 Deeplearning Using Machine Learning to Predict Customers’ Next Purchase Day OBG 2024.02.27 38
269 서버 멀티-플레이어 게임 서버와 레이턴시 보상 테크닉 OBG 2024.01.16 41
268 Deeplearning 마이크로소프트가 공개한 무료 AI 코스들 OBG 2023.11.28 55
267 LLM LLM 출력 속도 24배 높여주는 라이브러리 등장했다 OBG 2023.06.30 65
Board Pagination Prev 1 2 3 4 5 6 7 8 9 10 ... 15 Next
/ 15
위로