본문 바로가기

MFC

[스크랩] USB 시리얼 포트 자동 인식하기

출처: http://infoarts.tistory.com/22


요즘은 MCU에도 USB 기능이 원칩화 되면서 점점 UART를 RS232C로 사용하지 않고 USB로 사용할 수 있습니다. 그러다보니 별도의 추가 비용없이 USB Serial 기능을 사용할 수 있게 되었습니다.

 

그런데, Windows 어플리케이션에서 USB 시리얼을 연결하여 사용하다가 Windows 어플리케이션에서 연결을 끊지 않은 상태에서 USB 시리얼 케이블을 뽑았다가 다시 연결하거나 또는 타깃 보드가 리셋되어서 Windows 어플리케이션에서 연결을 끊고 다시 연결하려면 연결이 않되는 버그가 있습니다.

 

즉, Windows에서 타깃 보드와 시리얼 통신을 하고 있다가 보드를 리셋하면 다시 연결하려면 않되는 것이죠.

 

이 때는 USB 시리얼 포트를 뽑은 후에 Windows 어플리케이션에서 연결을 끊고 다시 USB 시리얼 케이블을 꼽은 후에 연결하면 되긴 합니다.

 

위와 같이 그냥 사용할 수 있는 제품이야 좀 불편해도 참으면 그만인데, 자동화 툴을 만드셔야 되는 상황이라면 해결하고 넘어가야 겠죠...

 

꼼수를 부려봐도 되겠지만, 꼼꼼하게 짚어가보겠습니다.

 

Microsoft 사이트에서 이 버그에 대한 정보를 얻을 수 있었습니다.

근데, 딱히 해결책 없이 버그가 닫혀있네요...

 

어째든, 정리하자면 USB 케이블을 분리하면 COM 포트가 시스템에서 사라기 때문에 Close 기능이 재때 실행되지 않기 때문이라는 군요.

 

그럼 재때 실행하면 된다는 말이죠...?!?

 

그래서 해봤습니다! Windows에 장치가 연결되거나 연결 해지되면 WM_DEVICECHANGE라는 메시지가 브로드 캐스팅되는데, USB 시리얼 케이블이 연결되거나 빠질 때도 역시 이 메시지가 전달됩니다.

 

이 메시지를 이용하여 장치가 연결 해제되는 시기에 Close 함수를 호출 해서 이 문제를 해결할 수 있었습니다!

 

USB 시리얼 전용 장치라면 다음과 같이 장치가 연결되고 연결 해지될 때 다음과 같이 COM 포트 이름을 얻어 올 수 있습니다(모든 예제는 MFC로 되어 있습니다).

 

 #include <dbt.h>
 
 LRESULT CTestDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
 {
     if (message == WM_DEVICECHANGE)
     {
         PDEV_BROADCAST_PORT pdbcp = (PDEV_BROADCAST_PORT)lParam;
 
         // 장치가 연결되었을 때
         if (wParam == DBT_DEVICEARRIVAL)
         {
             if (pdbcp->dbcp_devicetype == DBT_DEVTYP_PORT)
             {
                 MessageBox(pdbcp->dbcp_name);
             }
         }
 
         // 장치가 연결 해지되었을 때
         if (wParam == DBT_DEVICEREMOVECOMPLETE)
         {
             if (pdbcp->dbcp_devicetype == DBT_DEVTYP_PORT)
             {
                 MessageBox(pdbcp->dbcp_name);
             }
         }
     }
 
     return CDialog::DefWindowProc(message, wParam, lParam);
 }

  1. 위와 같이 DefWindowProc() 함수를 오버로딩하셔서 사용하시던가 아니면 OnDeviceChange() 함수를 사용 하시면 됩니다.

하지만, Stellaris의 경우 위와 같은 방법은 사용할 수 없습니다. 먼저 Stellaris가 연결 해지될 때의 방법을 살펴보겠습니다(이번에는 OnDeviceChange() 함수를 이용하여 보겠습니다).

 

 class CTestDlg : public CDialog
 {
 protected:
     // Generated message map functions
     //{{AFX_MSG(CSendTermDlg)
     (... 중략 ...)
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
     BOOL OnDeviceChange(UINT nEventType, DWORD dwData);
 };

 

 BEGIN_MESSAGE_MAP(CSendTermDlg, CDialog)
     //{{AFX_MSG_MAP(CSendTermDlg)
     (... 중략 ...)
     //}}AFX_MSG_MAP
     ON_WM_DEVICECHANGE()
 END_MESSAGE_MAP()
 
 // 장치가 연결 해지될 때 발생 메시지 등록
 DEV_BROADCAST_HANDLE dbch;
 dbch.dbch_size = sizeof(dbch);
 dbch.dbch_devicetype = DBT_DEVTYP_HANDLE;
 dbch.dbch_handle = handle; // 연결된 COM 포트 핸들
 m_hDeviceNotification = RegisterDeviceNotification(GetSafeHwnd(), &dbch, DEVICE_NOTIFY_WINDOW_HANDLE);
 
 BOOL CTestDlg::OnDeviceChange(UINT nEventType, DWORD dwData)
 {
     BOOL bReturn = CWnd::OnDeviceChange(nEventType, dwData);
     PDEV_BROADCAST_HDR pdbch = (PDEV_BROADCAST_HDR)dwData;
 
     if (nEventType == DBT_DEVICEREMOVECOMPLETE)
     {
         if (pdbch->dbch_devicetype == DBT_DEVTYP_HANDLE)
         {
             // TODO: COM 포트 Close 함수 호출 및 UnregisterDeviceNotification 함수 호출
             UnregisterDeviceNotification(m_hDeviceNotification);
         }
     }
 
     return bReturn;
 }

 

그럼 이제, 다시 연결되었을 때 자동으로 연결하는 방법을 살펴보겠습니다.

 

아래와 같이 GUID를 등록하여 메시지가 전달되면 GUID와 VID 및 PID를 확인하고 COM 포트 Open 함수를 호출합니다.

 

 static const GUID s_GuidInterfaceList[] =
 {
     {0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED}}, // USB Raw Device Interface
 };
 
 // 장치 GUID 및 연결될 때 발생 메시지 등록
 DEV_BROADCAST_DEVICEINTERFACE dbcc;
 dbcc.dbcc_size = sizeof(dbcc);
 dbcc.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
 
 for (int i=0; i<sizeof(s_GuidInterfaceList)/sizeof(s_GuidInterfaceList[0]); i++)
 {
     dbcc.dbcc_classguid = s_GuidInterfaceList[i];
     dbcc.dbcc_name[0] = '\0';
 
     RegisterDeviceNotification(GetSafeHwnd(), &dbcc, DEVICE_NOTIFY_WINDOW_HANDLE);
 }
 
 BOOL CTestDlg::OnDeviceChange(UINT nEventType, DWORD dwData)
 {
     BOOL bReturn = CWnd::OnDeviceChange(nEventType, dwData);
     PDEV_BROADCAST_HDR pdbch = (PDEV_BROADCAST_HDR)dwData;
 
     if (nEventType == DBT_DEVICEARRIVAL)
     {
         if (pdbch->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
         {
             PDEV_BROADCAST_DEVICEINTERFACE pdbcc = (PDEV_BROADCAST_DEVICEINTERFACE)lParam;
 
             // TODO: pdbcc를 참조하여 GUID와 VID 및 PID를 확인하고 COM 포트 Open 함수 호출
         }
     }
 
     return bReturn;
 }

 

하지만, 위의 방법으로는 USB 시리얼 전용 장치와 같이 COM 포트 이름을 얻어올 수 없으므로 사용 중이던 COM 포트가 연결된 것인지 새로운 COM 포트가 연결된 것이지는 알 수 없지만 사용하려는 COM 포트가 연결 해지된 이력이 있었지는로 판단하시면 될 듯 합니다.

 

예제 소스


Test.zip

 

참고 사이트