基於MFC的USB上位機開發(2)速度測試模組
延伸閱讀:
目錄
本模組用於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