ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Windows에서 USB 시리얼 포트 자동 인식하기
    프로그래밍/윈도우즈 2013. 3. 13. 14:42
    728x90

    요즘은 MCU에도 USB 기능이 원칩화 되면서 점점 UART to RS232C로 사용하지 않고 UART to USB로 사용할 수 있습니다. 그러다보니 별도의 추가 비용없이 USB Serial 기능을 사용할 수 있게 되어 이제 많이들 USB로 디버깅 메시지들을 보고 계시지요.

     

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

     

    예를 들어 Windows에서 타깃 보드와 시리얼 통신을 하고 있다가 보드에 F/W 다운로드하고 리셋하면 다시 연결하려면 않되는 것이죠.

     

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

     

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

     

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

     

    (최근에는 보드 리셋을 해도 UART로 사용하는 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;
    
    		// 장치가 연결되었을 때(GUID를 등록	했을 경우)
    		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(TI사의 MCU 시리즈)의 경우 위와 같은 방법은 사용할 수 없습니다. 먼저 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를 등록하면 USB 연결시에도 WM_DEVICECHANGE 메시지가 전달되니 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 포트가 연결 해지된 이력이 있었지는로 판단하시면 될 듯 합니다.

     

    그리고 이 방법을 응용하면 COM포트 연결을 감시하여 자동으로 연결되게 하는 프로그램도 만드실 수 있습니다.

     

    예제 소스

    Test.zip
    0.01MB

     

    참고 사이트

    728x90

    댓글

Designed by Tistory.