이제 그동안 진행해왔던 시리얼통신의 제1막을 내려야 할 때가 온것 같습니다. 실제 이 블로그를 기획하고 시작한 의도는 제 자신의 문제로 시작되었습니다.
실제로 해 보니, 역시 많은 부분에서 제대로 알지 못하고 사용하고 있었습니다. 다음에 또 기회가 된다면, 시리얼통신에 관련된 주제로 더 발전된 형태의 기술을 가지고 마음만 그렇다는 거지요. 시간이 허락할지는 모르겠습니다. 아직은 놀고 먹고 자는 시간도 1. 시리얼통신이란 ? 시리얼 통신은 7비트, 혹은 8비트 데이터를 비트별로 전송하는 방식입니다. 2. 동기식, 비동기식 방식이란 ? 동기식이란 어떤 신호에 맞춰서 데이터를 송.수신 하는 방식입니다. 이를 위해 동기신호가 3. 전이중, 반이중 방식이란 ? 송신과 수신이 동시에 되면 전이중, 한가지씩만 할 수 있는
것이 반이중 방식입니다. 4. 그렇다면, 그동안 진행해 왔던 232 통신방식은 ? 전이중 비동기 시리얼 통신방식 입니다. full-depluex asynchronous serial communication 5. 블로킹과 넌블로킹의 차이점은 ? blocking 함수는 그 함수가 완료될때(리턴될때)까지 해당 스레드의 어떤 코드도 실행되지 못하고 6. 오버랩이란 ? 입출력 함수인 WriteFile, ReadFile 함수는 블로킹 함수인데, 함수 인자로 오버랩 구조체를 전달 7. 시리얼 통신 프로그램 방법은 ? 7-1. 포트를 오픈합니다. 핸들을 얻는 것으로 오픈하게 됩니다. (HANDLE) m_hComm = CreateFile(_T("\\\\.\\COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, 장치명을
COM1으로 주었습니다. 앞에 \\\\.\\ 경로는 장치이름들이 있는 심볼릭 링크 경로 입니다.
if(m_hComm == INVALID_HANDLE_VALUE) return FALSE; CreateFile 함수 사용 후 리턴값을 확인 합니다. INVALID_HANDLE_VALUE이면 장치가 없거나, 이미 오픈되었거나 몇가지 이유로 핸들을 얻지 못하는 상황입니다. 통신을 할 수 없으므로 더 이상의 작업을 하지 않고 리턴하였습니다. 장치 핸들을 얻었으면, 그 다음으로 장치 설정을 해 줍니다. DCB dcb; GetCommState(m_hComm, &dcb); dcb.BaudRate = 9600; SetCommState(m_hComm, &dcb); data control block이라고 불리워지는 DCB 구조체를 하나 선언하고, GetCommState 함수로 해당 장치의 현재값을 가져옵니다. 특별히 리턴값을 점검하지 않는 이유는 m_hComm 장치 핸들값이 정상이면 거의 성공하는 함수이기 때문입니다.
COMMTIMEOUTS CommTimeout; CommTimeout.ReadInvervalTimeout = 0; SetCommTimeouts(m_hComm, &CommTimetous); 송수신 타임아웃 값을 설정해 줍니다. 사실, 이 부분은 처음 설명드리는 것인데, 실험적으로 알아내는게 가장 좋기 때문입니다. 이 경우에는 ReadTotalTimeoutConstant 값만 10을 주었는데, 10 밀리초 동안 입력이 없으면 타임아웃하라는 뜻입니다. 한번에 보내는 데이터 길이에 따라 조금씩 다르게 해 주면
됩니다.
이 타임아웃 값이 너무 짧으면 연속된 데이터임에도 불구하고 계속 수신 이벤트가 발생하게 됩니다. SetupComm(m_hComm, 1024, 1024); 송.수신할때 버퍼의 크기를 지정해 줍니다. 아시다시피 이 크기를 넘는 데이터는 한번에 전송되지
않습니다. PurgeComm(m_hComm, PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR); 통신 포트를 리셋합니다. (char*) m_recvData = new char[1024]; 수신할 데이터 버퍼와 길이를 초기화 합니다. m_hWnd = GetSafeHwnd(); 이 두 변수는 메세지를 발생시키기 위한 변수입니다. 데이터가 수신 되었을때 스레드에서 PostMessage나 m_ovSend.hEvent = CreateEvent(NULL, FALSE, FLASE, NULL); 오버랩 데이터 구조의 이벤트 객체를 생성합니다. DWORD err; GetLastError() 함수로 읽을 수 있는 에러를 클리어 합니다. 굳이 필요없지만 예방 차원에서 넣었습니다. SetCommMask(m_hComm, EV_RXCHAR); 수신된 데이터가 있으면 이벤트를 받겠다고 설정합니다. 이 외에도 송신완료, 흐름제어 문자, 수신 버퍼 풀등 여러 이벤트가 있지만 예제에서 사용하는건 수신 이벤트 뿐이므로 이렇게 설정했습니다. m_bRunEventThread = true; 스레드를 생성합니다. 이 스레드는 이벤트를 받는 수신 스레드 입니다. 스레드는 무한 루프를 돌면서 7-2. 송.수신이 끝난 후 포트를 닫는 함수를 만듭니다. 앞서 말씀드렸다시피 스레드부터 종료시킵니다. if(m_bRunSendThread) m_bRunSendThread라는 변수에 false를 주고나서 m_bEndSendThread 라는 변수가 true가 될때까지 if(m_bRunEventThread) 예제 코드는 송신 스레드와 이벤트 스레드 2개를 생성하는데, 송신 스레드는 데이터를 송신 할 때만 if(m_hComm != INVALID_HANDLE_VALUE) { if(m_ovSend.hEvent != INVALID_HANDLE_VALUE) { if(m_nSendLength > 0) delete [] m_recvData; 그 뒤는 일반적인 핸들과 할당했던 메모리를 해제하는 작업입니다. 7-3. 데이터 송신 데이터 송신은 "SERIAL COMMUNICATION TEST...!" 라는 데이터를 보내는 것으로 가정하고 설명하겠습니다. 우선 변수에 데이터를 할당 합니다. char* data = "SERIAL COMMUNICATION TEST...!"; (char*) m_sendData = new char[nSize];
설명드린것처럼, GetLastError()로 리턴되는 에러코드를 해제합니다.
BOOL bRet = WriteFile(m_hComm, m_sendData, nSize, &dwWritten, &m_ovSend);
m_sendData는 멤버변수인데
overlapped 방식으로 데이터를 보내므로 WriteFile 함수는 곧 리턴됩니다. if(bRet) 만일, WriteFile 함수가 TRUE를 리턴하면, 두고 볼것도 없이 송신이 다 완료 된 것입니다. else 대부분의 경우 오버랩 타입의 WriteFile은 FALSE를 리턴합니다.
7-4. 데이터 수신 데이터 수신은 애매한게 언제 데이터가 올지 모른다는 점 입니다. 그래서 이벤트를 설정했고 스레드에서 항상 감시하도록 하였습니다.
CJSerial* parent = (CJSerial*) pParam; pParam는 스레드를 생성할때 AfxBeginThread 함수의 2번째 인수로 보낸 객체입니다. 때문에 이 인스턴스가 절대로 스레드보다 먼저 종료되는 일이 없어야 합니다. 그럴 필요가 있다면 전역 변수로 필요한 데이터를 보내야 합니다. while(parent->m_bRunEventThread) 루프의 기본 구조 입니다. CJSerial 객체의 m_bRunEventThread 변수가 true 인 동안 계속 이 루프를 돌게 됩니다. 앞에서 이 스레드를 종료시킬때 m_bRunEventThread = false 라고 한 것을 기억하실 겁니다. 계속해서 수신 함수 부분을 체크해 보겠습니다. DWORD err; 먼저 송신과 마찬가지로 에러를 미리 해제해 둡니다. DWORD dwRead; (BOOL) bRet = ReadFile(parent->m_hComm, parent->m_recvData + parent->m_nRecvLength, 1024, &parent->m_ovRecv); 데이터를 수신합니다. 대부분의 구조는 WriteFile과 같습니다. if(bRet) TRUE가 리턴되면 Read과정이 종료이지만,... 대부분의 경우 FALSE가 리턴됩니다. else GetLastError() 함수를 호출해서 수신중인지 확인 합니다. 수신이 완료되면 GetOverlappedResult 함수는 TRUE가 리턴됩니다. 자, 핵심적인 부분은 다 했습니다. 혹시 더 궁금하신 분이 계시다면, 예제 코드를 참고하시면 될 것입니다. |