使用 WinUSB 函式存取 USB 裝置
本文包含詳細的逐步解說,教您如何使用 WinUSB 函式,與採用 Winusb.sys 作為函式驅動程式的 USB 裝置進行通訊。
摘要
- 開啟裝置並取得 WinUSB 句柄。
- 取得所有介面及其端點之裝置、組態和介面設定的相關信息。
- 讀取和寫入數據以大量和中斷端點。
重要 API
如果您使用 Microsoft Visual Studio 2013,請使用 WinUSB 範本建立基本架構應用程式。 在此情況下,請略過步驟 1 到 3,然後從本文中的步驟 4 繼續進行。 範本會開啟裝置的檔案句柄,並取得後續作業所需的 WinUSB 句柄。 該句柄會儲存在 device.h 中應用程式定義的DEVICE_DATA結構中。
如需範本的詳細資訊,請參閱根據 WinUSB 範本撰寫 Windows 傳統型應用程式。
注意
WinUSB 函式需要 Windows XP 或更新版本。 您可以在 C/C++ 應用程式中使用這些函式來與 USB 裝置通訊。 Microsoft不提供 WinUSB 的受控 API。
在您開始使用 Intune 之前
下列專案適用於本逐步解說:
- 此資訊適用於 windows Windows 8.1、Windows 8、Windows 7、Windows Server 2008、Windows Vista 版本的 Windows。
- Winusb.sys 被安裝為裝置的功能驅動程式。 如需此程式的詳細資訊,請參閱 WinUSB (Winusb.sys) 安裝。
- 本文中的範例是以 OSR USB FX2 Learning Kit 裝置為基礎。 您可以使用這些範例,將程序擴充至其他 USB 裝置。
步驟 1:根據 WinUSB 範本建立基本架構應用程式
若要存取 USB 裝置,請先根據 Windows 驅動程式套件 (WDK) 整合式環境中隨附的 WinUSB 範本來建立基本架構應用程式(含適用於 Windows 的偵錯工具),並Microsoft Visual Studio。 您可以使用範本作為起點。
如需範本程式代碼、如何建立、建置、部署及偵錯基本架構應用程式的相關信息,請參閱 根據 WinUSB 範本撰寫 Windows 傳統型應用程式。
此範本會使用 SetupAPI 例程列舉裝置、開啟裝置的檔案句柄,並建立後續工作所需的 WinUSB 介面句柄。 如需取得裝置句柄並開啟裝置的範例程式代碼,請參閱 範本程式代碼討論。
步驟 2:查詢裝置是否有 USB 描述元
接下來,查詢裝置是否有 USB 特定資訊,例如裝置速度、介面描述元、相關端點及其管道。 此程式類似於 USB 裝置驅動器所使用的程式。 不過,應用程式會呼叫 WinUsb_GetDescriptor 來完成裝置查詢。
下列清單顯示您可以呼叫以取得 USB 特定資訊的 WinUSB 函式:
其他裝置資訊。
呼叫 WinUsb_QueryDeviceInformation ,以向裝置描述項要求資訊。 若要取得裝置的速度,請在 InformationType 參數中設定DEVICE_SPEED (0x01)。 函式會傳回 LowSpeed (0x01) 或 HighSpeed (0x03)。
介面描述項
呼叫 WinUsb_QueryInterfaceSettings 並傳遞裝置的介面句柄,以取得對應的介面描述元。 WinUSB 介面句柄會對應至第一個介面。 某些 USB 裝置,例如 OSR Fx2 裝置,僅支援一個介面,而沒有任何替代設定。 因此,針對這些裝置, AlternateSettingNumber 參數會設定為零,且函式只會呼叫一次。 WinUsb_QueryInterfaceSettings填入呼叫端配置的USB_INTERFACE_DESCRIPTOR結構(傳入UsbAltInterfaceDescriptor 參數),其中包含介面的相關信息。 例如,介面中的端點數目是在 USB_INTERFACE_DESCRIPTOR 的 bNumEndpoints 成員中設定。
對於支援多個介面的裝置,請呼叫 WinUsb_GetAssociatedInterface ,藉由在 AssociatedInterfaceIndex 參數中指定替代設定,以取得相關聯介面的介面句柄。
端點
呼叫 WinUsb_QueryPipe ,以取得每個介面上每個端點的相關信息。 WinUsb_QueryPipe以指定端點管道的相關信息填入呼叫端配置的WINUSB_PIPE_INFORMATION結構。 零基索引會識別端點的管道,且其值必須小於在上一次呼叫 **WinUsb_QueryInterfaceSettings中擷取的介面描述元中 bNumEndpoints 的 成員值。 OSR Fx2 裝置有一個具有三個端點的介面。 針對此裝置,函式的 AlternateInterfaceNumber 參數會設定為 0,而 PipeIndex 參數的值會從 0 到 2 不等。
若要判斷管道類型,請檢查 WINUSB_PIPE_INFORMATION 結構的 PipeInfo 成員。 此成員會設定為其中 一個USBD_PIPE_TYPE 列舉值:UsbdPipeTypeControl、UsbdPipeTypeIsochronous、UsbdPipeTypeBulk 或 UsbdPipeTypeInterrupt。 OSR USB FX2 裝置支援中斷管道、大量插入管道和大容量輸出管道,因此 PipeInfo 會設定為 UsbdPipeTypeInterrupt 或 UsbdPipeTypeBulk。 UsbdPipeTypeBulk 值會識別大量管道,但不會提供管道的方向。 方向資訊會編碼在管道位址的高位,該位址會儲存在 WINUSB_PIPE_INFORMATION 結構的 PipeId 成員中。 判斷管道方向的最簡單方式是從 Usb100.h 將 PipeId 值傳遞至下列其中一個巨集:
- 如果
USB_ENDPOINT_DIRECTION_IN (PipeId)
方向為 ,則巨集會 傳回 TRUE 。 - 如果
USB_ENDPOINT_DIRECTION_OUT(PipeId)
方向傳出,則巨集會 傳回 TRUE 。
應用程式會使用 PipeId 值來識別呼叫 WinUSB 函式時要用於數據傳輸的管道,例如WinUsb_ReadPipe(本主題的「問題 I/O 要求」一節所述),因此此範例會儲存所有三個 PipeId 值以供稍後使用。
- 如果
下列範例程式代碼會取得 WinUSB 介面句柄所指定的裝置速度。
BOOL GetUSBDeviceSpeed(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pDeviceSpeed)
{
if (!pDeviceSpeed || hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
ULONG length = sizeof(UCHAR);
bResult = WinUsb_QueryDeviceInformation(hDeviceHandle, DEVICE_SPEED, &length, pDeviceSpeed);
if(!bResult)
{
printf("Error getting device speed: %d.\n", GetLastError());
goto done;
}
if(*pDeviceSpeed == LowSpeed)
{
printf("Device speed: %d (Low speed).\n", *pDeviceSpeed);
goto done;
}
if(*pDeviceSpeed == FullSpeed)
{
printf("Device speed: %d (Full speed).\n", *pDeviceSpeed);
goto done;
}
if(*pDeviceSpeed == HighSpeed)
{
printf("Device speed: %d (High speed).\n", *pDeviceSpeed);
goto done;
}
done:
return bResult;
}
下列範例程式代碼會查詢 WinUSB 介面句柄所指定之 USB 裝置的各種描述項。 此範例函式會擷取支援的端點類型及其管道標識碼。 此範例會儲存所有三個 PipeId 值,以供日後使用。
struct PIPE_ID
{
UCHAR PipeInId;
UCHAR PipeOutId;
};
BOOL QueryDeviceEndpoints (WINUSB_INTERFACE_HANDLE hDeviceHandle, PIPE_ID* pipeid)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
USB_INTERFACE_DESCRIPTOR InterfaceDescriptor;
ZeroMemory(&InterfaceDescriptor, sizeof(USB_INTERFACE_DESCRIPTOR));
WINUSB_PIPE_INFORMATION Pipe;
ZeroMemory(&Pipe, sizeof(WINUSB_PIPE_INFORMATION));
bResult = WinUsb_QueryInterfaceSettings(hDeviceHandle, 0, &InterfaceDescriptor);
if (bResult)
{
for (int index = 0; index < InterfaceDescriptor.bNumEndpoints; index++)
{
bResult = WinUsb_QueryPipe(hDeviceHandle, 0, index, &Pipe);
if (bResult)
{
if (Pipe.PipeType == UsbdPipeTypeControl)
{
printf("Endpoint index: %d Pipe type: Control Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
if (Pipe.PipeType == UsbdPipeTypeIsochronous)
{
printf("Endpoint index: %d Pipe type: Isochronous Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
if (Pipe.PipeType == UsbdPipeTypeBulk)
{
if (USB_ENDPOINT_DIRECTION_IN(Pipe.PipeId))
{
printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
pipeid->PipeInId = Pipe.PipeId;
}
if (USB_ENDPOINT_DIRECTION_OUT(Pipe.PipeId))
{
printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
pipeid->PipeOutId = Pipe.PipeId;
}
}
if (Pipe.PipeType == UsbdPipeTypeInterrupt)
{
printf("Endpoint index: %d Pipe type: Interrupt Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
}
else
{
continue;
}
}
}
done:
return bResult;
}
步驟 3:將控制傳輸傳送至預設端點
接下來,向預設端點發出控制要求來與裝置通訊。
除了與介面相關聯的端點之外,所有 USB 裝置都有預設端點。 默認端點的主要目的是提供主機可用來設定裝置的資訊。 不過,裝置也可以針對裝置特定用途使用預設端點。 例如,OSR USB FX2 裝置會使用預設端點來控制燈條和七段數字顯示器。
控制命令包含8位元組的設定封包,其中包含指定特定要求的要求程式碼,以及選擇性的數據緩衝區。 要求碼和緩衝區格式是廠商定義的。 在此範例中,應用程式會將數據傳送至裝置,以控制燈條。 設定燈條的程式代碼是0xD8,這是為了方便起見而定義為SET_BARGRAPH_DISPLAY。 針對此要求,裝置需要一個 1 位元組的數據緩衝區,以指定應該藉由設定適當的位來點亮哪些元素。
應用程式可以提供一組八個複選框控件,以指定應該點亮的光線列元素。 指定的項目會對應至緩衝區中的適當位。 為了避免 UI 程式代碼,本節中的範例程式代碼會設定位,讓備用燈亮起。
發出控件要求
配置 1 位元組的數據緩衝區,並將資料載入緩衝區,以指定應該透過設定適當的位來點亮的專案。
在呼叫端配置的 WINUSB_SETUP_PACKET 結構中建構設定封包。 初始化成員以表示要求類型和數據,如下所示:
- RequestType 成員會指定要求方向。 RequestType 設定為 0,表示主機對裝置數據傳輸。 針對裝置對主機傳輸,請將 RequestType 設定為 1。
- 要求成員會設定為此要求之廠商定義的程式代碼,0xD8。 為了方便起見,要求 定義為SET_BARGRAPH_DISPLAY。
- Length 成員會設定為數據緩衝區的大小。
- 此要求不需要 Index 和 Value 成員,因此它們會設定為零。
呼叫 WinUsb_ControlTransfer ,藉由傳遞裝置的 WinUSB 介面句柄、設定封包和數據緩衝區,將要求傳輸至預設端點。 函式會接收在 LengthTransferred 參數中傳送至裝置的位元元組數目。
下列程式代碼範例會將控件要求傳送至指定的USB裝置,以控制燈條上的燈光。
BOOL SendDatatoDefaultEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR bars = 0;
WINUSB_SETUP_PACKET SetupPacket;
ZeroMemory(&SetupPacket, sizeof(WINUSB_SETUP_PACKET));
ULONG cbSent = 0;
//Set bits to light alternate bars
for (short i = 0; i < 7; i+= 2)
{
bars += 1 << i;
}
//Create the setup packet
SetupPacket.RequestType = 0;
SetupPacket.Request = 0xD8;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = sizeof(UCHAR);
bResult = WinUsb_ControlTransfer(hDeviceHandle, SetupPacket, &bars, sizeof(UCHAR), &cbSent, 0);
if(!bResult)
{
goto done;
}
printf("Data sent: %d \nActual data transferred: %d.\n", sizeof(bars), cbSent);
done:
return bResult;
}
步驟 4:發出 I/O 要求
接下來,將數據傳送至裝置的大容量載入和大量輸出端點,分別可用於讀取和寫入要求。 在 OSR USB FX2 裝置上,這兩個端點會設定為回送,因此裝置會將數據從大容量載入端點移至大量輸出端點。 它不會變更數據的值,也不會新增任何新數據。 針對回送組態,讀取要求會讀取最近寫入要求所傳送的數據。 WinUSB 提供下列函式來傳送寫入和讀取要求:
傳送寫入要求
- 配置緩衝區,並填入您想要寫入裝置的數據。 如果應用程式未將RAW_IO設定為管道的原則類型,則緩衝區大小沒有任何限制。 WinUSB 會視需要將緩衝區分割成適當大小的區塊。 如果已設定RAW_IO,緩衝區的大小會受限於 WinUSB 支援的最大傳輸大小。
- 呼叫 WinUsb_WritePipe ,將緩衝區寫入裝置。 傳遞裝置的 WinUSB 介面句柄、大量輸出管道的管道識別碼(如本文的查詢 USB 描述項的裝置一節中所述),以及緩衝區。 函式會傳回以 bytesWritten 參數寫入裝置的位元元組數目。 重疊參數會設定為NULL以要求同步作業。 若要執行異步寫入要求,請將 [重疊] 設定為重疊結構的指標。
包含零長度數據的寫入要求會往下轉送 USB 堆疊。 如果傳輸長度大於傳輸長度上限,WinUSB 會將要求分割成最大傳輸長度的較小要求,並依序提交要求。 下列程式代碼範例會配置字串,並將它傳送至裝置的大量輸出端點。
BOOL WriteToBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG* pcbWritten)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE || !pID || !pcbWritten)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR szBuffer[] = "Hello World";
ULONG cbSize = strlen(szBuffer);
ULONG cbSent = 0;
bResult = WinUsb_WritePipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbSent, 0);
if(!bResult)
{
goto done;
}
printf("Wrote to pipe %d: %s \nActual data transferred: %d.\n", *pID, szBuffer, cbSent);
*pcbWritten = cbSent;
done:
return bResult;
}
傳送讀取要求
- 呼叫 WinUsb_ReadPipe ,從裝置的大量端點讀取數據。 傳遞裝置的 WinUSB 介面句柄、大量傳入端點的管道識別碼,以及適當大小的空白緩衝區。 當函式傳回時,緩衝區會包含從裝置讀取的數據。 讀取的位元組數目會在函式的 bytesRead 參數中傳回。 針對讀取要求,緩衝區必須是封包大小上限的倍數。
長度為零的讀取要求會立即完成,且不會傳送到堆棧。 如果傳輸長度大於傳輸長度上限,WinUSB 會將要求分割成最大傳輸長度的較小要求,並依序提交要求。 如果傳輸長度不是端點 MaxPacketSize 的倍數,WinUSB 會將傳輸大小增加至下一個 MaxPacketSize 倍數。 如果裝置傳回的數據超過要求,WinUSB 會儲存多餘的數據。 如果數據仍來自先前的讀取要求,WinUSB 會將其複製到下一個讀取要求的開頭,並視需要完成要求。 下列程式代碼範例會從裝置的大量端點讀取數據。
BOOL ReadFromBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG cbSize)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR* szBuffer = (UCHAR*)LocalAlloc(LPTR, sizeof(UCHAR)*cbSize);
ULONG cbRead = 0;
bResult = WinUsb_ReadPipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbRead, 0);
if(!bResult)
{
goto done;
}
printf("Read from pipe %d: %s \nActual data read: %d.\n", *pID, szBuffer, cbRead);
done:
LocalFree(szBuffer);
return bResult;
}
步驟 5:釋放裝置句柄
完成對裝置的所有必要呼叫之後,請透過呼叫下列函式來釋放裝置的檔案控制代碼和 WinUSB 介面控制代碼:
- CloseHandle 以釋放 CreateFile 所建立的句柄,如步驟 1 中所述。
- WinUsb_Free釋放裝置的 WinUSB 介面句柄,此句柄是由 **WinUsb_Initialize所傳回。
步驟 6:實作 main 函式
下列程式代碼範例顯示主控台應用程式的主要功能。
如需取得裝置句柄並開啟裝置的範例程式代碼(在此範例中為 GetDeviceHandle 和 GetWinUSBHandle ),請參閱 範本程式代碼討論。
int _tmain(int argc, _TCHAR* argv[])
{
GUID guidDeviceInterface = OSR_DEVICE_INTERFACE; //in the INF file
BOOL bResult = TRUE;
PIPE_ID PipeID;
HANDLE hDeviceHandle = INVALID_HANDLE_VALUE;
WINUSB_INTERFACE_HANDLE hWinUSBHandle = INVALID_HANDLE_VALUE;
UCHAR DeviceSpeed;
ULONG cbSize = 0;
bResult = GetDeviceHandle(guidDeviceInterface, &hDeviceHandle);
if(!bResult)
{
goto done;
}
bResult = GetWinUSBHandle(hDeviceHandle, &hWinUSBHandle);
if(!bResult)
{
goto done;
}
bResult = GetUSBDeviceSpeed(hWinUSBHandle, &DeviceSpeed);
if(!bResult)
{
goto done;
}
bResult = QueryDeviceEndpoints(hWinUSBHandle, &PipeID);
if(!bResult)
{
goto done;
}
bResult = SendDatatoDefaultEndpoint(hWinUSBHandle);
if(!bResult)
{
goto done;
}
bResult = WriteToBulkEndpoint(hWinUSBHandle, &PipeID.PipeOutId, &cbSize);
if(!bResult)
{
goto done;
}
bResult = ReadFromBulkEndpoint(hWinUSBHandle, &PipeID.PipeInId, cbSize);
if(!bResult)
{
goto done;
}
system("PAUSE");
done:
CloseHandle(hDeviceHandle);
WinUsb_Free(hWinUSBHandle);
return 0;
}
下一步
如果您的裝置支援不連續的端點,您可以使用 WinUSB 函式 來傳送傳輸。 此功能僅支援Windows 8.1。 如需詳細資訊,請參閱 從 WinUSB 傳統型應用程式傳送 USB 隨機傳輸。