共用方式為


使用 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。

開始之前

下列專案適用於本逐步解說:

  • 此資訊適用於 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 Driver Kit 整合式環境中隨附的 WinUSB 範本建立基本架構應用程式, (WDK) (搭配 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填入usbAltInterfaceDescriptor 參數傳遞的呼叫端配置USB_INTERFACE_DESCRIPTOR結構 () 介面的相關信息。 例如,介面中的端點數目是在 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 函式時要用於數據傳輸的管道,例如本主題的「問題 I/O 要求」一節中所述) 的WinUsb_ReadPipe (,因此此範例會儲存這三個 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. 配置 1 位元組的數據緩衝區,並將數據載入緩衝區,以指定應該藉由設定適當的位來點入元素。

  2. 在呼叫端配置的 WINUSB_SETUP_PACKET 結構中建構安裝封包。 初始化成員來表示要求類型和數據,如下所示:

    • RequestType 成員會指定要求方向。 它設定為 0,表示主機到裝置的數據傳輸。 針對裝置對主機傳輸,請將 RequestType 設定為 1。
    • 要求成員會設定為此要求的廠商定義程式代碼,0xD8。 它定義為方便SET_BARGRAPH_DISPLAY。
    • Length 成員會設定為數據緩衝區的大小。
    • 此要求不需要 IndexValue 成員,因此它們會設定為零。
  3. 呼叫 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 提供下列函式來傳送寫入和讀取要求:

傳送寫入要求

  1. 配置緩衝區,並填入您想要寫入裝置的數據。 如果應用程式未將RAW_IO設定為管道的原則類型,則緩衝區大小沒有任何限制。 WinUSB 會視需要將緩衝區分割成適當大小的區塊。 如果已設定RAW_IO,則緩衝區的大小會受限於WinUSB支援的傳輸大小上限。
  2. 呼叫 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 函式

下列程式代碼範例顯示主控台應用程式的主要函式。

如需取得裝置句柄並開啟裝置 (GetDeviceHandleGetWinUSBHandle 的範例程式代碼,請參閱 範本程式代碼討論) 。

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 同步傳輸

另請參閱