출처: http://blog.naver.com/choies1/120014759914
- 예전에 회사에 있을 때는 CommThread라는 클래스를 가져다가 만들었는데 이 역시 비슷한 방법으로 작성하신 듯하다.
1. 강좌 [2/2] 시작
이제부터 시리얼 통신 프로그램 만들기에 대해 본격적이고 보다 구체적인 내용으로 들어가겠습니다.
시리얼 통신 프로그램을 만들것이니 컴퓨터의 시리얼 포트(COM 포트) 의 콘트롤 부분에 대한 부분이 일단 확보되어야 할 것입니다.
대부분의 VC++ 책자들을 보면 COM 포트를 읽고 쓰는 방법과 원리에 대해 잘 나와있습니다. 그리고 예제로서 시리얼 동작에 관련한 간단한 클래스도 제공해 줍니다.
서적이나 기타 문서들을 참고하여 시리얼 포트를 제어하는 것에 대해 학습한 후 이에 대한 제어를 하는 클래스를 자신이 처음부터 직접 만들어 사용는 방법도 있겠지만 이런 예제 클래스를 사용한다면 우리의 시리얼 통신 프로그램 제작시 시리얼 제어 관련 부분은 이 클래스를 VC++ 프로젝트에 포함하는 것으로 간단히 해결되게 됩니다.
그리고 아주 기본적인 기능만 지원하는 책자의 예제보다는 여러 부가기능을 지원하며 많은 사람들이 사용해본 동작상 검증된 클래스를 구해서 적용해 보도록 하겠습니다.
그렇게 해서 찾은것이 아래 링크에 소개된 시리얼 클래스 입니다.
http://www.codeproject.com/system/serial.asp
www.codeproject.com 과 www.codeguru.com 이 두곳 사이트는 마이크로 소프트의 MSDN 사이트 를 비롯하여 VC++ 프로그래머가 자주 참조하는 사이트들 입니다.
VC++ API 및 MFC 에 대한 항목별 튜터가 아주잘 정리돼 있고 여러 프로그래머들이 자신이 만든 클래스들이나 예제의 소스를 공개하고 사용법도 자세히 정리하여 소개하고 있습니다.
지금 사용하려는 시리얼 클래스 뿐만 아니라 기능적으로나 시각적으로 보완, 향상시킨 윈도우 컨트롤 클래스(버튼, 프로그래스 등 비주얼 컨트롤 클래스) 들의 소스도 많이 구할수 있으며 대부분 소스단위의 공개를 하고 있으니 자신의 입맛에 맞는 클래스를 만드는데 참조한다면 많은 도움이 될 것입니다.
아래링크는 시리얼 통신부분을 소스가 아닌 컴파일된 DLL 형태의 API 로 제공해주는 사이트입니다.
http://www.sysbas.com/korean/html/data_01.htm
위에 소개한 시리얼 클래스들 말고도 위 사이트나 다른 VC++ 관련 사이트들 혹은 윈도우에서의 시리얼 통신을 다루는 서적에서 제공해 주는 클래스를 사용하거나 이것도 싫다면 자신이 직접 시리얼 통신을 제어하는 클래스들을 직접 만들어 사용할 수도 있을 것입니다.
그럼 처음 소개한 시리얼 클래스 예제에 대해 살펴보도록 하겠습니다.
처음 제시한 링크로 가서 가장 윗부분에 보시면 이 클래스와 이를 사용한 예제의 소스를 압축하여 다운받을 수 있는 링크를 찾을 수 있습니다.
우선 이 예제를 다운받아 적당한 폴더에 압축을 풀고 VC++ 워크스페이스 프로젝트 파일을 읽어들입니다. 그러면 아래와 같이 클래스 구조를 VC++ 의 ClassView 화면으로 볼 수 있을것입니다.
시리얼 포트를 감시해서 입력을 받고 컨트롤하거나 출력을 지원해주는 Serial classes 프로젝트 부분과 이 클래스를 사용하여 콘솔 및 윈도우 환경에서의 시리얼 통신을 하는 예제 프로젝트가 포함돼 있는것을 볼 수 있습니다.
각 예제를 컴파일 해서 실행을 시켜보면 SerialTestMFC 프로젝트의 경우 아래 그림과 같은 시리얼 통신프로그램 예제를 볼 수 있습니다.
위 그림은 COM 포트중 하나에 접속시키고 포트 설정 박스를 띄운 화면입니다.
이 예제는 윈도우 MDI (다중 다큐먼트 인터페이스) 로 만들어진 VC++ 프로젝트이며 COM 포트중 하나에 접속을 하면 새로운 뷰 클래스인 CChildView 를 가진 자식 프레임 윈도우 CChildFrame 가 생성되며 ChildView 에서 CSerial 클래스가 상속된 CSerialMFC 클래스가 변수선언에 의해 메모리에 실체화된 객체(오브젝트 인스턴스 : 클래스가 메모리에 생성된 객체) 를 가지고 사용자의 키 입력이나 Serial 클래스가 COM 포트에서 메세지를 받았을때에 대해 반응하여 CChildeView 클래스가 가진 리치에디트 컨트롤 화면에 통신하고 있는 정보를 표시하여 주게 만들어졌습니다.
이 예제처럼 우리도 CSerial 클래스와 이를 상속받은 클래스들을 우리가 만드는 시리얼 통신프로그램에 가져와 사용하여 시리얼 통신 부분을 담당시키게 할 것입니다.
아마도 이것이 OOP 의 큰 장점이랄수 있습니다. 이미 기능이 구현된 소스가 있다면 이렇게 클래스를 가져다 포함시키는 것 만으로 소스의 재사용성을 높이는 것이지요. 이렇게 소스단위의 기능별 독립적으로 부품화가 이루어지므로 만든것 또 만들고 할 일이 줄어드니 소프트웨어 생산성 향상에 큰 잇점이 있습니다..
이 CSeial 및 이를 상속받은 CSerialEx, CSerialWnd, CSerialMFC 를 사용하다 기능적 보완이 필요하다면 위 네가지중 적절한 것을 선택해 이를 상속받은 클래스를 만들어주고 새로운 기능을 넣기위해 새로운 멤버함수를 덧붙이거나 재정의, 중복정의를 통해 이전 기능에 대한 추가 및 보완을 해줌으로서 소스의 유지,보완적 측면에서의 잇점이 있습니다.
이 또한 OOP 가 소프트웨어 생산성 향상을 위해 설계된 프로그래밍 방식이라는 것을 잘 나타내 줍니다.
2. VC++ 프로젝트 생성
이제 우리의 시리얼 통신프로그램 제작에 들어가겠습니다.
VC++ 에서 아래 그림과 같이 MFC AppWizard(ext) 를 선택하여 새 프로젝트를 만들며 프로젝트 이름은 ComConstructor 라고 짓습니다. ^^;
그다음 아래 그림과 같이 Single Document 를 고르고 Next 를 누릅니다.
그리고 계속 Next 를 눌러 다음 마지막 스텝이 나왔을때 다음 그림과 같이 View 클래스의 베이스 클래스를 CEditView 로 바꿔줍니다. (CScrollView 가 아닙니다 ^^;)
그리고 Finish 버튼을 눌러 프로젝트 생성을 마치면 이제 코딩을 할 수 있습니다.
아래 화면은 프로젝트 생성을 마치고 바로 컴파일하여 실행시킨 ComConstructor.exe 의 모습입니다.
윈도우 MFC SDI(Single Document Interface) 구조로 프로젝트를 생성시켰으며 위에 기본적인 풀다운 메뉴와 툴바가 붙어서 만들어졌습니다.
우리는 여기에 이전에 소개한 CSerial 및 그 관련 클래스들을 포함시켜 시리얼 통신 제어부분을 담당시킬 것이며 접속/접속끊기, 통신설정, 키입력의 전송, 파일의 전송, 등의 기능도 넣고, HEX 파일의 전송이나 받은 데이터 분석에 유용할 수 있도록 Hexa 에디터도 추가할 것입니다.
3. 시리얼 통신 클래스 내장시키기
이전에 소개한 시리얼 통신 클래스를 내장시켜 이 프로그램에 시리얼 통신 기능을 부여하는 방법에 대해 설명하겠습니다.
이전글에서 시리얼 클래스의 예제를 다운로드 받아 압축을 풀었을 것입니다.
그 폴더를 찾아보면 Serial.cpp 와 Serial.h 및 Serial 관련 클래스가 들어있는 "Serial" 폴더를 찾을 수 있을것입니다.
이 폴더를 현재 프로젝트인 ComConstructor 폴더 바로밑에 통채로 복사한후
아래그림처럼 VC++ WorkSpace 화면의 FileView 탭에서 프로젝트 이름에 대고 마우스의 오른쪽버튼 클릭을 통해 "Add Files to Project" 를 통해 "Serial" 폴더내의 소스(3개의 .cpp 파일과 4개의 .h 파일) 를 추가시킵니다.
그리고 VC++ 에서 Alt+F7 키를 눌러 아래 그림과 같이 Project Settings 박스를 띄운 후 C/C++ 탭의 Project Options 에디트 박스에 /I ".Serial" 라고 추가시켜 줍니다.
이는 VC++ 에서 "Serial" 폴더내에 있는 소스들을 컴파일과 링킹시 참조하도록 해주는 역할을 합니다.
그리고 이 시리얼 클래스가 소개되어 있는 사이트에서 이 클래스를 자신의 프로젝트에 통합시키는 방법이 설명되어 부분을 찾으면 다음과 같은 문구가 나와있습니다.
Serial.cpp 과 SerialWnd.cpp 두 파일에서 아래의 네 줄을 주석처리합니다.
#define STRICT #include <crtdbg.h> #include <tchar.h> #include <windows.h> |
그리고 아래의 문장을 삽입합니다.
#include "StdAfx.h" |
그리고 컴파일 잘 되는지 컴파일(F7)을 실행합니다.
아마도 컴파일 중에
serialex.cpp(277) : fatal error C1010: unexpected end of file while looking for precompiled header directive
이런 컴파일 에러 메세지가 난다면 SerialEx.cpp 파일도 위와같이 수정해주고 다시 컴파일 합니다.
수정이 잘 됐다면 에러가 없어지고 컴파일이 잘 완료되었을 것입니다.
이제 CSerial 및 이를 상속받은 3개의 클래스가 우리의 VC++ 프로젝트에 내장 되었으므로 이를 사용하고 싶으면 이 클래스를 변수로 선언하고 이 시리얼 통신 클래스의 제작자가 권장하는 방식대로 사용하면 됩니다.
아래 그림은 이 부분에 대한 설명입니다.
이 시리얼 클래스를 사용하는 방법은 여러가지가 있으나 우리는 윈도우 프로그램을 제작하기 때문에(콘솔 프로그램이 아닌) 시리얼 클래스가 COM 포트로 부터 메세지를 받았을 때 이에 대한 처리를 담당하는 윈도우에게 사용자정의 메세지를 건네주끔 동작하는 CSerialWnd 나 CSerialMFC 클래스를 사용할 수 있습니다.
이 메세지를 받는 윈도우는 CMainFrame 이 담당하게 할 것이며 MFC 를 사용하여 프로그램을 만들므로 CSerialMFC 클래스를 사용하여 CMainFrame 의 멤버변수로서 m_Serial 이 만들어지게 할 것입니다.
위 그림에서 보시는 대로 CMainFrame 의 메세지 맵에 ON_WM_SERIAL 메세지 핸들러 함수를 만들어주면 시리얼 포트로부터 받은 데이터를 처리할 수 있습니다.
시리얼 포트의 사용은 사이트에 나와있는대로
m_Serial.Open(_T("COM1"));
과 같이 포트를 열어주고
m_Serial.Setup(CSerial::EBaud9600,CSerial::EData8,CSerial::EParNone,CSerial::EStop1);
과 같이 통신설정을 해주며
m_Serial.SetupHandshaking(CSerial::EHandshakeHardware);
과 같이 흐름제어를 설정하고
시리얼 포트를 통해 전송하고 싶을땐
m_Serial.Write("Hello world");
이라고 호출해주면 됩니다.
4. 통신기능 부여하기
CMainFrame 이 CSerialMFC 의 객체를 멤버변수로서 가지게 된다고 했으니 CMainFrame 의 멤버변수를 선언합니다.
아래 그림과 같이 VC++ Work Space 화면의 ClassView 탭에서 CMainFrame 클래스가 표시된 곳에 마우스를 위치시키고 오른쪽 마우스 버튼을 눌러 "Add Member Variable" 메뉴를 선택합니다.
위 그림처럼 ClassView 에서 각 클래스 이름에 대고 마우스 오른쪽버튼을 누르면 나오는 팝업메뉴에는 멤버함수를 추가시킬 수 있는 "Add Member Function", 멤버 변수를 추가시킬 수 있는 "Add Member Variable", 가상함수를 정의할 수 있는 "Add Virtual Function", 윈도우 클래스 (CWnd 로부터 상속되어진 클래스) 이고 소스에 메세지맵이 정의돼 있으면 나오는 "Add Windows Message Handler" 등이 보여집니다.
VC++ 에선 위와같이 Work Space 의 ClassView 탭 에서 팝업메뉴를 사용하거나 Class Wizard 를 직접 띄워(Ctrl+W) 메뉴를 사용하여 간편하게 멤버함수나 변수를 추가시킬 수 있습니다.
그리고 다음 그림과 같이 멤버변수를 Protected:(Public으로 하자) 로 선언해줍니다.
마찬가지 방식으로 int 형 변수를 m_nPort 란 이름으로 Protected: 로 선언해줍니다.
마찬가지 방식으로 CSerial::EBaudrate 형 변수를 m_eBaudrate 라는 이름으로 Protected: 로 선언해줍니다.
마지막의 m_eBardrate 선언시 위와같은 방식의 변수선언 위저드통한 변수 선언이 안될땐 CMainFrame 클래스가 정의된 헤더파일에 직접 변수선언 부분을 기입합니다.
그러면 아래와 같이 CMainFrame 클래스에 세개의 변수가 Protected: 로 새로 선언되었을 것입니다.
CSerialMFC m_Serial; |
m_Serial 변수는 CSerial 클래스가 상속되어져 정의된 CSerialMFC 클래스가 실제 메모리에 인스턴스화 된 것입니다.
m_nPort 와 m_eBaudrate 는 m_Serial 이 사용할 시리얼 포트 번호와 보레이트를 기억시키기 위하여 만들었습니다.
위에서 선언한 변수들을 초기화 시켜주기 위해 CMainFrame 의 OnCreate 함수의 return 0; 구문 바로 이전에 다음과 같이 기입하여 줍니다.
if(CSerial::CheckPort(_T("COM1")) |
CSerial::CheckPort 함수는 static 함수이므로 CSerial 을 오브젝트로 생성하지 않아도 호출할 수 있습니다.
이 함수는 매개변수로 넘겨진 시리얼 포트가 사용 가능한지 체크해 볼 수 있는 기능이 있습니다.
다음으로 Work Space 의 ResourceView 탭에서 Menu 를 선택해 하위 IDR_MAINMENU 를 클릭하면 나오는 메뉴에디터에서 아래 그림과 같이 "접속", "접속종료", "통신포트 설정" 세개의 메뉴 항목을 삽입하고 차례대로 "ID_CONNECT", "ID_DISCONNECT", "ID_COMSETTING" 라고 기입합니다.
새로운 메뉴를 삽입하였으므로 이 메뉴가 선택되었을 때 호출되는 함수들을 정의합니다.
Ctrl+W(Ctrl+Shift+X로 바뀜) 키를 눌러 클래스 위저드를 실행시키고 방금 삽입한 세개의 메뉴들에 해당하는 ID 값에 대한 COMMNAND 메세지 처리함수를 만들어줍니다.
아래 그림처럼 클래스 위저드가 이름지어주는대로 OnConnect, OnDisconnect, OnComsetting 함수들을 CMainFrame 에 만듭니다.
위와같은 방식으로 OnConnect 와 OnDisconnect 에 대한 UPDATE_COMMAND_UI 메세지 핸들러 함수도 만들어줍니다.
클래스 위저드가 OnUpdateConnect 와 OnUpdateDisconnect 라고 이름지어줄 것입니다.
이 함수들 안에서 위 두 메뉴의 시각적 효과를 특징지워줄 수 있습니다.
접속 메뉴를 클릭해서 포트를 열었다면 접속종료를 클릭해 포트를 닫을 때 까지 이 접속 메뉴를 비활성화 시키야 하고 접속종료 메뉴도 마찬가지의 시각효과를 주어야 하니까요.
OnConnect 와 OnDisconnect 멤버변수내의 코드를 아래와 같이 추가시켜줍니다.
void CMainFrame::OnConnect() |
void CMainFrame::OnDisconnect() |
그리고 OnUpdateConnect 와 OnUpdateDisconnect 멤버함수내의 코드를 다음과 같이 기입해줍니다.
void CMainFrame::OnUpdateConnect(CCmdUI* pCmdUI) |
void CMainFrame::OnUpdateDisconnect(CCmdUI* pCmdUI) |
CSerialMFC::Open() 함수 호출시 열기를 원하는 COM 포트와 함께 현재윈도우 포인터를 넘겨줌으로서 시리얼 포트에 데이터입력이 포착되었을때 시리얼 입력을 처리할 함수가 있는 윈도우가 현재윈도우(CMainFrame) 임을 알려줍니다.
그리고 시리얼 클래스가 이 시점에서 윈도우메세지를 보낼때 이를 받을 메세지 핸들러도 정의해줍니다.
MainFrm.cpp 의 메세지맵안에 다음과 같이 ON_WM_SERIAL(OnSerialMsg) 문구를 삽입합니다. 반드시 //}}AFX_MSG_MAP 구문 바깥에 기입되어야 합니다.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) |
MainFrm.h 파일에서 CMainFrame 클래스가 정의된 곳의 메세지 핸들러 함수를 선언해주는 곳에서 다음과 같이 afx_msg LRESULT OnSerialMsg (WPARAM wParam, LPARAM lParam); 문구를 삽입합니다.
//{{AFX_MSG(CMainFrame) |
이제 MainFrm.cpp 에 다음과 같이 OnSerialMsg 멤버함수를 정의해줍니다.
LRESULT CMainFrame::OnSerialMsg (WPARAM wParam, LPARAM /*lParam*/) |
그리고 현재까지의 작업이 오류없이 진행되었는지를 컴파일 해봅니다.
프로그램을 실행시켜 메뉴에서 "접속" 을 누르면 자신 PC 의 COM1 포트가 이상이 없다면 COM1 시리얼 포트를 통해 프로그램이 통신됩니다.
그러나 타겟을 COM1 으로 연결해 데이터를 전송해도 프로그램에는 아무 정보도 표시되지 않고 있습니다.
바로 위 메세지 핸들러가 이때의 처리를 해줍니다만 if 문 안에 아무런 처리루틴도 기입하지 않았기 때문에 아무런 정보도 표시되고 있지 않습니다.
5. 시리얼 수신을 화면에 표시 및 키입력 전송하기
시리얼 클래스에서 시리얼 포트를 감시하다 반응이 오면 이를 처리할 윈도우로 윈도우메세지를 보내주게 됩니다.
우리가 앞서 선언하고 정의한 이 메세지의 핸들러 함수인 CMainFrame::OnSerialMsg 에서 수신받은 데이터를 화면에 표시해주면 됩니다.
CMainFrame 의 OnSerialMsg 함수의 if 안에 다음과 같은 내용을 기입해줍니다.
if (eEvent & CSerial::EEventRecv) { // Create a clean buffer DWORD dwRead; CString szString; CEdit& editView = ((CEditView*)GetActiveView())->GetEditCtrl(); char szData[101]; const int nBuflen = sizeof(szData)-1; // Obtain the data from the serial port do { m_Serial.Read(szData,nBuflen,&dwRead); szData[dwRead] = '\0'; for (DWORD dwChar=0; dwChar<dwRead; dwChar++) { szString += szData[dwChar]; if(szData[dwChar] == '\r') szString += '\n'; } editView.SetSel(-1, 0); // 끝으로 이동 editView.ReplaceSel(szString); // 문자열 추가 } while (dwRead == nBuflen); } return 0; |
위 루틴은 시리얼 포트에서 데이터가 수신될 때 CSerial::Read 함수를 버퍼를 넘겨주고 호출하여 최근 수신된 데이터들을 받아와서 CEditView 클래스로 부터 상속받은 CComConstructorView 클래스에 텍스트(수신된 데이터)를 추가하는 역할을 합니다.
CEditView 에서의 개행을 위해 'r' 문자가 들어오면 'n' 을 더해서 "rn" 이 되도록 문자열을 조작한후 뷰클래스로 넘깁니다. (난 보통 \r\n으로 하는데.. 이럴 경우는 두 줄이 띄어질 수도 있겠다.)
컴파일하고 프로그램을 실행시킨 후 메뉴의 접속을 클릭하면 이제 COM1 으로 부터 전송되어지는 데이터가 화면에 표시될 것입니다.
이제 키보드로부터 받은 키입력 데이터를 시리얼로 전송시키기 위해 Work Space 의 ClassView 탭에서 CComConstructorView 클래스에 마우스 커서를 위치시키고 오른쪽마우스 클릭으로 팝업메뉴를 불러 "Add Window Message Handler" 메뉴를 선택하여 아래그림과 같이 WM_CHAR 메세지에 대한 메세지 핸들러 함수를 추가해줍니다.
이렇게 추가된 CComConstructorView 의 OnChar 함수에 다음과 같은 내용을 기입합니다.
void CComConstructorView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd(); CEdit& editView = GetEditCtrl(); char szChar[4]; char *pChar; pChar = szChar; // Send the data through the serial connection (if it's open) if (pMainFrame->m_Serial.IsOpen()) { char ch = static_cast<char>(nChar); pMainFrame->m_Serial.Write(&ch,1); *pChar++ = ch; if(ch == '\r') *pChar++ ='\n'; *pChar++ = 0x00; editView.SetSel(-1, 0); editView.ReplaceSel(szChar); } } |
(위의 소스가 포함된 곳에 #include "MainFrm.h"을 해줘야 한다. 그래야 CMainFrame 클래스를 얻어올 수 있다. 또한 szChar가 나온 곳에 CString strReplace; strReplace.Format(_T("%s"), szChar); editView.ReplaceSel( strReplace ); 와 같이 해줘야 한다. )
윈도우 메세지 WM_CHAR 는 키보드로부터 키입력이 들어올때 발생되며 우리는 이에대한 처리를 위해 이 메세지의 핸들러 함수인 OnChar 를 만들었으며 CEditView 에 시리얼 포트로 수신된 데이터를 출력해주는 역할을 합니다.
그리고 우리는 뷰 화면에 키보드로 입력을 할 것이므로 이에대한 처리를 뷰 클래스에 만들었습니다.
이제 컴파일 하고 실행시킨 후 시리얼 접속을 시킨 후 화면에 키입력을 하면 시리얼 포트를 통해 전송될 것이고 데이터가 시리얼 포트로 수신된다면 마찬가지로 화면에 표시될 것입니다.
[출처] [VC++ 강좌] 시리얼 통신 프로그램 만들기 [2/2]|작성자 choies1
'Enginius > C / C++' 카테고리의 다른 글
[Linux] opencv, pthread 사용 프로젝트 생성 (0) | 2012.07.22 |
---|---|
MFC에서 CString 사용하기. (0) | 2012.04.27 |
[MFC] BITMAP 으로 그림 그리기 (4) | 2012.04.24 |
MFC에서 랜덤 숫자 사용하기. (0) | 2012.04.16 |
console로 print할 때 주의사항 (0) | 2012.03.26 |