본문 바로가기
Visual C++(MFC)

MFC16. Serial 통신

by 정양섭 2022. 3. 7.

Serial 통신 루틴은 일반 Windows용 Application에서 사용하는 방식과 Console Application(DOS용 프로그램과 거의 같다)에서 사용하는 방식이 구조면에서 다릅니다. Windows용 Application에서는 Thread를 실행하여 그 Thread루틴 속에서 계속 통신을 감시하다가 통신 데이터가 들어 오면 해당 윈도우에 SendMessage로 알려 주도록(Windows의 메시지 큐 방식에 의한 실행 때문에 통신되고 있는 데이터를 Window 화면에 출력하려면 이 방식으로 구현해야 함)구현하고, Console Application에서는 단지 통신 포트를 열고 while문에서 통신을 감시하여 그 값이 들어오면 그 자리에서 printf함수를 호출하여 그 데이터를 화면에 표시하도록 구현합니다.
Serial 통신 루틴은 공개용이 많이 있으나 대부분 일반 Windows용 Application에서 사용하기 위하여 Thread루틴을 내부에 포함하고 사용법도 조금 어려워 직접 제작하였습니다.

Console Application에서 사용하는 클레스
Console Application에서 사용하도록 CSerialComm이라는 클레스를 만들었습니다.
CSerialComm에서 사용하는 함수
        //통신포트에 데이타를 쓸경우 RX를 클리어할 것인지를 결정(초기값 TRUE);
        void SetWriteBeforeClear(BOOL bFlag = TRUE){            m_bWriteBeforeClear = bFlag;}
        //통신 실패등 에러 메시지를 표시할 지를 설정(기본값 표시하지 않음)
        void SetMessageBoxFlag(BOOL bFlag);
        //CommId 외부에 가짐, 한 프로그램에서 여러개의 Serial 통신을 할 경우 사용
        BOOL CreateComm (int &nCommId, COMMINFO &CommInfo);
        int WriteData (int nCommId, LPCTSTR SndBuff, int Length) ;
        int WriteByte (int nCommId, BYTE nChar) ;
        int ReadData (int nCommId, LPSTR RcvBuff, int Length, int nTimeOut) ;
        int ReadByte (int nCommId, BYTE &nChar) ;
        void DeleteComm (int nCommId) ;
        //CommId 내부에 가짐, 한 프로그램에서 하나의 Serial 통신을 할 경우 사용
        BOOL CreateComm (COMMINFO& CommInfo) ;
        int WriteData (LPCTSTR SndBuff, int Length) ;
        int WriteByte (BYTE nChar) ;
        int ReadData (LPSTR RcvBuff, int Length, int nTimeOut) ;
        int ReadByte (BYTE &nChar) ;
        void DeleteComm ();

이들중 주로 사용하는 함수는 CommId를 인자로 가지지 않는 함수들입니다. 단지 Create함수로 통신 포트를 열고 WriteData로 데이터를 쓰고, ReadData로 데이터를 읽도록 구현을 하면됩니다. DeleteComm은 열려있는 통신 포트를 닫는 기능을 수행하나 CSerialComm의 파괴자 함수에서 자동 호출이 되므로 사용하지 않아도 됩니다.
일반적으로 제어 장비와 Serial 통신을 할 경우에는 통신의 오동작을 막기 위하여 통신 데이터를 쓰기 전에 RX 및 TX  통신 버퍼에 들어있는 모든 내용을 클리어 하도록 구성하나 일반 Windows 용 Application에서는 TX 통신 버퍼만 클리어합니다. SetWriteBeforeClear함수는 이 것을 설정하도록 하는 함수입니다.

통신 설정 DLL
CSerialComm에서 사용하는 함수 중 CreateComm의 인자를 보면 COMMINFO라는 구조체로 이자를 넘겨 줍니다. 이 구조체를 보면 다음과 같습니다.
typedef struct CommInfo{
        int nPort;                      //0=COM1 ~ 31=COM32
        int nStopBit;                 //0=1Bit, 1=2Bit
        int nParity ;                  //0=NONE, 1=EVEN, 2=ODD
        DWORD nBaudRate ;   //0=1100Bps ~ 12=921600Bps
        int nDataBit ;                //0=8Bit, 1=7Bit
        BYTE bNotUse;            //FALSE 일경우 통신가능, 대화상자 설정에는 없음
} COMMINFO;

이 구조체는 CommonDlg.h에 존재합니다.
이 구조체 값을 수동으로 설정하여 CreateComm 함수를 호출하면 되나 일반적으로 프로그램에서 대화 상자를 이용하여 설정하도록 구성됩니다. 이 기능을 수행하는 클레스가 CCommonDlg이고 이 클레스는 CommonDlg.dll이라는 Dll로 제작하였다. Dll 제작한 Source에 대해서는 설명하지 않고 아래 올려두기만 하니 참고하기 바랍니다.
단지 dll과 같이 제공하는 .h파일을 프로그램에 include(lib 파일은 자동 import함)하고 CCommonDlg로 변수를 선언한 후 ExecuteSetCommDlg함수를 호출하면 COMMINFO구조체의 값을 얻을 수 있습니다.
참고로 CCommonDlg에서 사용가능한 함수를 보면 다음과 같습니다.

        //COMMINFO 구조체의 값으로 부터 설정된 통신 정보를 String로 얻는다. 대화상자 표시시 사용.
        CString GetCommString(COMMINFO &commInfo);
        //통신 설정 대화상자가 생성되어 통신 정보를 COMMINFO구조체 형태로 넘겨 줍니다.
        BOOL ExecuteSetCommDlg(COMMINFO &commInfo, BOOL bInitFlag = TRUE);
        //랜 설정 대화상자가 생성되어 IP 및 PORT를 넘겨 줍니다.
        BOOL ExecuteSetLanDlg(CString &strIp, int &nPort, BOOL bInitFlag = TRUE);

Windows Application에서 사용하는 클레스
Windows Application에서 사용하도록 CCommThread이라는 클레스를 만들었습니다. 이 클레스는 내부적으로 CSerialComm을 사용합니다.
CCommThread에서 사용하는 함수
        BOOL CreateComm (CWnd *pWnd, COMMINFO& CommInfo);
        int WriteData (LPCTSTR SndBuff, int Length) ;
        int WriteByte (BYTE nChar) ;
        void End();                                     //Thread 종료시 사용.

End함수는 Thread 종료시 사용하는 함수로 프로그램 종료시 꼭 호출해야만 합니다(호출하지 않으면 Thread가 종료되지 않아 프로그램이 제대로 종료되지 않습니다).

통신 포트 열기
통신 포트는 해당 윈도우가 만들어 질 때(OnCreate), Thread를 생성한 후 CreateComm함수를 호출하여 포트를 엽니다.

int CXtalkView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
        if (CFormView::OnCreate(lpCreateStruct) == -1)
                return -1;
        
        m_pCommThread = (CCommThread*)AfxBeginThread(RUNTIME_CLASS(CCommThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
        m_pCommThread->CreateComm(this, m_CommInfo);
        m_pCommThread->ResumeThread();
        return 0;
}

데이터 읽기
통신 포트로부터 데이터가 들어오면 CCommThread함수 내에서 WM_COPYDATA라는 메시지로 SendMessage하도록 되어 있습니다. 따라서 해당 윈도우에서 WM_COPYDATA로부터 함수를 생성하여 다음과 같이 해주면 됩니다.

BOOL CXtalkView::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
        DWORD nOpcode = pCopyDataStruct -> dwData;
        TCHAR strBuffer[200];

        if(nOpcode==ID_RECEIVED_SERIAL_DATA){
                UpdateData(TRUE);
                memcpy(strBuffer, pCopyDataStruct->lpData, pCopyDataStruct->cbData);
                strBuffer[pCopyDataStruct->cbData] = 0;
                m_strReceiveData += strBuffer;        //들어온 데이터를 화면상에 표시
                UpdateData(FALSE);
        }
        return CFormView::OnCopyData(pWnd, pCopyDataStruct);
}

데이터 쓰기
단지 WriteData를 호출하면 됩니다.
void CXtalkView::OnSendButton()
{
        UpdateData();
        if(!m_pCommThread)
                return;

        if(m_strSendData.IsEmpty())
                return;

        m_pCommThread->WriteData(m_strSendData, m_strSendData.GetLength());
}

데모용 프로그램
데모용 프로그램은 Windows Application에서 사용하는 클레스를 이용하여 Windows Application을 제작하였다. Console Application에서 사용하는 Class또한 사용하므로(CCommThread내에서 사용) 이 데모를 보고나면 Console Application에서도 Serial 통신을 지원할 수 있을 것입니다. 데모용 프로그램을 컴파일하여 실행하면 다음과 같이 화면에 나타납니다.


클래스 및 DLL 다운로드

SerialCommClass.zip
0.02MB

예제 프로그램 다운로드

SerialCommDemo.zip
0.05MB

참고 : CommonDlg DLL Source

CommonDlg.zip
2.50MB

  

 

'Visual C++(MFC)' 카테고리의 다른 글

MFC00. 소개 및 차례  (0) 2022.03.07
MFC17. AutoSurf 제작  (0) 2022.03.07
MFC15. 설치 프로그램 만들기  (0) 2022.03.07
MFC14. HTML 도움말 작성법  (0) 2022.03.07
MFC13. ADO  (0) 2022.03.07