1. 程式人生 > >基於MFC的USB上位機開發(2)速度測試模組

基於MFC的USB上位機開發(2)速度測試模組

延伸閱讀:

基於MFC的USB上位機開發(1)概述

基於MFC的USB上位機開發(2)速度測試模組

基於MFC的USB上位機開發(3)資料傳輸模組

基於MFC的USB上位機開發(4)環路模組

基於MFC的USB上位機開發(5)下環路模組

目錄

1. USB裝置初始化及熱拔插的實現

1.1 USB初始化

1.2 熱拔插程式設計

2. USB速度測試模組設計

2.1 USB的BULK傳輸控制

2.2 測速思路

2.3 MFC控制元件相關操作

3. 模組測試

3.1 USB狀態顯示

3.2 速度測試

4. 參考


        本模組用於USB裝置的狀態顯示和速度測試。上位機的標題欄為USB的狀態資訊顯示區,有裝置連線時會顯示裝置的名稱,沒有時顯示“沒有找到裝置!”速度測試分接收和傳送測試,暫不支援雙向速度測試,在速度測試中,上位機會統計資料傳輸中的成功次數、失敗次數和總共傳輸的位元組數,傳輸速度會通過進度條長度來顯示。USB的理論最大速度為60MB/s,所以速度進度條對應的長度範圍是0~60,通過測試USB的接收或者傳送的單向速度可達37MB/s。

1. USB裝置初始化及熱拔插的實現

1.1 USB初始化

Cypress公司的USB裝置初始化流程如下

(1)新建USB裝置結構體:  pUSBDevice = new CCyUSBDevice(m_hWnd);

(2)開啟USB裝置:             pUSBDevice->Open(0);

(3)初始化裝置端點:          pInEndpt  = pUSBDevice->EndPointOf(0x86);

                                                pOutEndpt = pUSBDevice->EndPointOf(0x02);

裝置的關閉和刪除操作方式如下:

(1)關閉USB裝置:         pUSBDevice->Close();

(2)清空裝置端點:         pInEndpt  = NULL;        pOutEndpt = NULL;

(3)刪除USB裝置:        delete pUSBDevice;

獲取USB裝置資訊的操作如下:

(1)獲取USB裝置的連線數目:pUSBDevice->DeviceCount()

(2)獲取USB裝置的名稱:       pUSBDevice->DeviceName() 

(3)獲取USB裝置的VID:        pUSBDevice->VendorID

(4)獲取USB裝置的PID:        pUSBDevice->ProductID 

        在USB熱拔插的過程中,上位機需根據裝置的狀態,來調整結構體pUSBDevice、pInEndpt和pOutEndpt ,並將USB的連線狀態顯示在上位機視窗上。根據以上要求需要設計一個狀態顯示函式DisplayDevices(),程式程式碼如下,關於USB的更多操作可檢視參考文章[1]。

void CUSBprojDlg::DisplayDevices(void)
{
	CString str;
	UCHAR nCount,n;
	Sleep(10);
	m_Semaphore.Lock();
	nCount=pUSBDevice->DeviceCount();    // 檢查有多少USB裝置已連線
	
	if(nCount>1)
	{
		
		str = _T("USB測試工具v1.4       不支援多臺儀器同時連線,請移除不必要的儀器!");
	}
	else
	{
		pUSBDevice->Open(0);
		if(!pUSBDevice->IsOpen())
		{
			//MessageBox(TEXT("USB裝置移除!"),TEXT(" USB connect"),MB_OK|MB_ICONERROR); 
			pInEndpt  = NULL;
			pOutEndpt = NULL;
			str = _T("USB測試工具v1.4        沒有找到裝置!");
		}
		else
		{
			str = _T("USB測試工具v1.4        ");
			str+=pUSBDevice->DeviceName;
			pInEndpt  = pUSBDevice->EndPointOf(0x86);
			pOutEndpt = pUSBDevice->EndPointOf(0x02);
		}
	}
	m_Semaphore.Unlock();
	SetWindowText(str);	
}

1.2 熱拔插程式設計

        USB的熱拔插通過使用WindowProc()函式來實現,函式攜帶的引數如下:

        LRESULT CUSBprojDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

    其中,message為訊息的ID,它是32位值,指明瞭訊息型別。wParam和lParam,32位值,包含附加資訊,決定於訊息的種類。熱拔插的程式流程圖如下,當系統產生硬體改變的訊息時,通過判斷有無裝置的插入或者拔出以及裝置樹是否改變,來進行USB裝置的開啟或關閉,想了解更多有關USB裝置熱拔插的知識點,可以檢視參考文章[2]-[4]。

  熱拔插的程式碼如下:

LRESULT CUSBprojDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_DEVICECHANGE) 
	{	// Tracks DBT_DEVICEARRIVAL followed by DBT_DEVNODES_CHANGED
		if (wParam == DBT_DEVICEARRIVAL)//有裝置插入
		{
			bPnP_Arrival = true;
			bPnP_DevNodeChange = false;
		}
		// Tracks DBT_DEVNODES_CHANGED followed by DBT_DEVICEREMOVECOMPLETE
		if (wParam == DBT_DEVNODES_CHANGED) //有裝置新增或移除
		{
			bPnP_DevNodeChange = true;
			bPnP_Removal = false;
		}
		if (wParam == DBT_DEVICEREMOVECOMPLETE) //有裝置拔出
		{
			bPnP_Removal = true;
			PDEV_BROADCAST_HDR bcastHdr = (PDEV_BROADCAST_HDR) lParam;
			if (bcastHdr->dbch_devicetype == DBT_DEVTYP_HANDLE) 
			{
				PDEV_BROADCAST_HANDLE pDev = (PDEV_BROADCAST_HANDLE) lParam;
				if (pDev->dbch_handle == pUSBDevice->DeviceHandle())
					pUSBDevice->Close();
				    DisplayDevices();
			}
		}// If DBT_DEVNODES_CHANGED followed by DBT_DEVICEREMOVECOMPLETE
		if (bPnP_Removal && bPnP_DevNodeChange) 
		{
		//	Sleep(10);
		//	DisplayDevices();
			bPnP_Removal = false;
			bPnP_DevNodeChange = false;
		}
		// If DBT_DEVICEARRIVAL followed by DBT_DEVNODES_CHANGED
		if (bPnP_DevNodeChange && bPnP_Arrival) 
		{
			DisplayDevices();
			bPnP_Arrival = false;
			bPnP_DevNodeChange = false;
		}
	}
	return CDialog::WindowProc(message, wParam, lParam);
}

2. USB速度測試模組設計

2.1 USB的BULK傳輸控制

(1)BULK IN傳輸

    BULK IN傳輸中主要是呼叫BeginDataXfer( ),WaitForXfer( )和FinishDataXfer( )三個函式,使用方式如下:

    pInContex=pDlg->pInEndpt->BeginDataXfer(data,nLen,&InOvLap);
    pDlg->pInEndpt->WaitForXfer(&InOvLap,2000)
    success=pDlg->pInEndpt->FinishDataXfer(data,nLen,&InOvLap,pInContex);

BeginDataXfer( )用於開啟上位機非同步接收,向USB讀取長度為nLen的資料,放在陣列data中。

WaitForXfer( )用於規定非同步傳輸事件InOvLap耗時不能超過2s,當傳輸時間大於2s時,可以採用下方條件語句丟棄本次任務。

if(!pDlg->pInEndpt->WaitForXfer(&InOvLap,2000))
{
pDlg->pInEndpt->Abort();
WaitForSingleObject(InOvLap.hEvent,2000);
}   

FinishDataXfer( )用於判斷本次非同步輸入傳輸事件是否成功,成功返回True,失敗返回False!

(2)BULK OUT傳輸

    BULK OUT的傳輸呼叫函式和BULK IN是一樣的,只是對應的USB端點不同,使用方式如下:

    pOutContex=pDlg->pOutEndpt->BeginDataXfer(data,n,&OutOvLap);
    pDlg->pOutEndpt->WaitForXfer(&OutOvLap,2000);
    success=pDlg->pOutEndpt->FinishDataXfer(data,nLen,&OutOvLap,pOutContex);

  同樣的,當非同步輸出傳輸事件OutOvLap耗時超過2s時,以採用下判斷語句丟棄本次傳輸任務。

if(!pDlg->pOutEndpt->WaitForXfer(&OutOvLap,2000))
{
pDlg->pOutEndpt->Abort();
WaitForSingleObject(OutOvLap.hEvent,2000);
}

應用舉例:如何使用BeginDataXfer( ),WaitForXfer( )和FinishDataXfer( )三個函式進行512位元組資料的迴圈採集?

1)開啟執行緒

    為了提高USB資料的傳輸速度與效率,通常需要給資料採集單獨開啟一個bulk執行緒,具體操作如下。其中,pXferIn_512Thread為執行緒的狀態指標,執行緒在執行時為True,停止時為NULL。XferIn_512為執行緒處理函式,實現512位元組資料的迴圈採集。

if(pUSBDevice->IsOpen())                                 //USB裝置已開啟
{
 bLooping = true;                                        //執行緒迴圈標誌為真
 pXferIn_512Thread = AfxBeginThread(XferIn_512, this);   //啟動執行緒
}

2)執行緒處理函式

   執行緒處理函式主要包括三大塊:建立非同步接收事件的結構體InOvLap,使用迴圈標誌位bLooping控制資料的接收和執行緒的釋放。執行緒處理函式的程式碼如下: 

 UINT XferIn_512(LPVOID params)
{   //建立非同步接收事件的結構體InOvLap
    CUSBprojDlg *pDlg= (CUSBprojDlg*) params; //使該執行緒可訪問CUSBprojDlg類的所有公有成員
    OVERLAPPED InOvLap;
    UCHAR *pInContex;
    LONG nLen=512;
    PUCHAR data=new UCHAR[nLen]; ZeroMemory(data,nLen);
    InOvLap.hEvent=CreateEvent(NULL,false,false,_T("USB_IN")); 
   //迴圈接收512位元組資料
    for(;pDlg->bLooping;) 
    {
        pInContex=pDlg->pInEndpt->BeginDataXfer(data,nLen,&InOvLap);
        pDlg->pInEndpt->WaitForXfer(&InOvLap,2000);
        bool success=pDlg->pInEndpt->FinishDataXfer(data,nLen,&InOvLap,pInContex);
    }
    //執行緒釋放
    CloseHandle(InOvLap.hEvent); 
    delete [] data;
    pDlg->pXferIn_512Thread= NULL; //執行緒結束
    return true;
}  

2.2 測速思路

        上圖為“上位機接收資料”速度測試模組的流程圖,提供了按鍵服務程式和執行緒處理函式具體設計思路。在“上位機接收資料”按鈕的服務程式內有一個bool型變數m_bXfer,每當按鈕按下自身進行取反操作。當m_bXfer=true時,“上位機接收資料”按鈕變為“stop”按鈕,同時開啟資料接收的執行緒。當m_bXfer=false時,“stop”按鈕變為“上位機接收資料”按鈕,同時關閉資料接收的執行緒。

        接收執行緒pXferInThread用於上位機接收資料時的速度測試,具體思路是:

(1)定義一個容量為10KByte的陣列UCHAR data[10240],用於上位機端資料的快取;

(2)定義16組非同步傳輸事件結構體陣列OVERLAPPED InOvLap[16],每完成16組非同步傳輸事件,上位機就接收了160KByte資料;

(3)執行並完成16組非同步傳輸事件InOvLap[0]~InOvLap[16],記錄消耗時間t,以及事件成功次數nSucCount和失敗次數nErrCount。

(4)當接收執行緒累積執行時間T超過1s時,計算平均速度:

rate=(nErrCount+nSucCount)*10/1024/T

(5)將成功次數nSucCount、失敗次數nErrCount、平均速度rate以及總傳輸位元組數顯示在上位機上。

“上位機發送資料”測速模組的設計思路和上方是一樣的,這裡就不介紹了。“上位機接收資料”測速模組對應的執行緒處理函式程式碼如下:

UINT XferIn( LPVOID params ) {
    CUSBprojDlg *pDlg= (CUSBprojDlg*) params;	
	LARGE_INTEGER BegainTime; 
	LARGE_INTEGER EndTime;
	LARGE_INTEGER Frequency;
	QueryPerformanceFrequency(&Frequency);
    OVERLAPPED InOvLap[16]; 
	UCHAR  *pInContext[16];
	ULONG nSucCount = 0;	
	ULONG nErrCount = 0;
	LONG nLen = 10240;
	UCHAR data[10240];	ZeroMemory(data,nLen);
	CString s[16];
	int n=0;
 	pDlg->m_Semaphore.Lock();
	for(n=0;n<16;n++)
	{
		if(pDlg->pInEndpt==NULL) break;		
		nLen = 10240;
		s[n].Format(_T("YJUSB_IN%d"),n);
		InOvLap[n].hEvent   = CreateEvent(NULL,false,false, s[n]); 
		pInContext[n] = pDlg->pInEndpt->BeginDataXfer(data,nLen,&InOvLap[n]);		
	}
	pDlg->m_Semaphore.Unlock();
	while (1) 
	{
		bool b=pDlg->m_bXfer;
		if(pDlg->pInEndpt==NULL) break;

		pDlg->m_Semaphore.Lock();
		pDlg->pInEndpt->TimeOut = 0;	
		QueryPerformanceFrequency(&Frequency);
		QueryPerformanceCounter(&BegainTime);
		for(n=0;n<16;n++)
		{			
			if(!pDlg->pInEndpt->WaitForXfer(&InOvLap[n],2000))
			{
				pDlg->pInEndpt->Abort();
				WaitForSingleObject(InOvLap[n].hEvent, 2000);
			}
			nLen = 10240;
			bool success = pDlg->pInEndpt->FinishDataXfer(data,nLen, &InOvLap[n],pInContext[n]); 
			if (success)nSucCount++;
			else nErrCount++;
			nLen = 10240;
			if(b)	pInContext[n] = pDlg->pInEndpt->BeginDataXfer(data,nLen,&InOvLap[n]);
		}
		QueryPerformanceCounter(&EndTime);
		double t=(double)( EndTime.QuadPart - BegainTime.QuadPart )/Frequency.QuadPart;
		pDlg->m_Semaphore.Unlock();
		pDlg->TestRate(t,nSucCount, nErrCount);
		if(!b) break;
	}
	for(n=0;n<16;n++)
	{
		CloseHandle(InOvLap[n].hEvent);
	}
	pDlg->EndOutThread(2);
	return true;
}

2.3 MFC控制元件相關操作

(1)按鈕控制元件

禁用按鈕:  m_x86.EnableWindow(false);

使能按鈕:  m_x86.EnableWindow(true);

更換名稱:  m_x86.SetWindowText(_T("停止"));

(2)示例編輯框控制元件

CString變數賦值:str.Format(_T("%d"),nSuc);

控制元件顯示變數值: m_Success.SetWindowTextW(str);

String.format的更多操作可以查閱參考[5]。

(3)進度條

設定進度條範圍:m_Rate.SetRange(0,60);

設定進度大小為0:m_Rate.SetPos(0);

顯示當前速度:

str.Format(_T("%f  MB/s"),rate);

m_Rate.SetWindowTextW(str);  //顯示速度數值

m_Rate.SetPos((int)rate);       //進度條長度賦值

m_Rate.Invalidate();           //更新進度條長度

3. 模組測試

3.1 USB狀態顯示

(1)裝置不通電或者拔出時

(2)裝置通電並連線上電腦時

3.2 速度測試

(1)接收資料測試

進行中:

停止傳送測試:

(2)傳送資料測試

進行中:

停止接收測試:

(3)清空資料

4. 參考

[1] 賽普拉斯 cypress Cyapi使用心得 - oket007的專欄 - CSDN部落格

https://blog.csdn.net/oket007/article/details/8671359?utm_source=blogxgwz4

[2] 監控USB裝置插拔 - killmice的專欄 - CSDN部落格

https://blog.csdn.net/killmice/article/details/8658761

[3] WindowProc() - cddchina的專欄 - CSDN部落格

https://blog.csdn.net/cddchina/article/details/8701784

[4] 基於Cy68013的USB裝置在VC程式中熱插拔的實現 | 學步園

https://www.xuebuyuan.com/1488224.html

[5] String.format參加字串拼接大比拼 - 追夢 - CSDN部落格

https://blog.csdn.net/u010168160/article/details/52021652