Поделиться через


Доступ к USB-устройству с помощью функций WinUSB

В этой статье содержится подробное пошаговое руководство по использованию функций WinUSB для взаимодействия с USB-устройством, использующим Winusb.sys в качестве драйвера функции.

Итоги

  • Открытие устройства и получение дескриптора WinUSB.
  • Получение сведений о параметрах устройства, конфигурации и интерфейса всех интерфейсов и их конечных точках.
  • Чтение и запись данных для массовых и прерываний конечных точек.

Важные API

Если вы используете Microsoft Visual Studio 2013, создайте скелетное приложение с помощью шаблона WinUSB. В этом случае пропустите шаги 1–3 и перейдите к шагу 4 в этой статье. Шаблон открывает дескриптор файла на устройстве и получает дескриптор WinUSB, необходимый для последующих операций. Этот дескриптор хранится в определяемой приложением DEVICE_DATA структуре в device.h.

Дополнительные сведения о шаблоне см. в статье "Запись классического приложения Windows" на основе шаблона WinUSB.

Примечание.

Для функций WinUSB требуется Windows XP или более поздней версии. Эти функции можно использовать в приложении C/C++ для взаимодействия с USB-устройством. Корпорация Майкрософт не предоставляет управляемый API для WinUSB.

Перед началом работы

К этому пошаговому руководству применяются следующие элементы:

  • Эта информация относится к Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista версии Windows.
  • Вы установили Winusb.sys в качестве драйвера функции устройства. Дополнительные сведения об этом процессе см. в статье о установке WinUSB (Winusb.sys).
  • Примеры в этой статье основаны на устройстве КОМПЛЕКТа обучения OSR USB FX2. Эти примеры можно использовать для расширения процедур на других USB-устройствах.

Шаг 1. Создание скелетного приложения на основе шаблона WinUSB

Чтобы получить доступ к USB-устройству, начните с создания скелетного приложения на основе шаблона WinUSB, включенного в интегрированную среду комплекта драйверов Windows (WDK) (с средствами отладки для Windows) и Microsoft Visual Studio. Шаблон можно использовать в качестве отправной точки.

Сведения о коде шаблона, создании, развертывании и отладке скелетного приложения см. в статье "Написание классического приложения Windows" на основе шаблона WinUSB.

Шаблон перечисляет устройства с помощью подпрограмм SetupAPI , открывает дескриптор файла для устройства и создает дескриптор интерфейса WinUSB, необходимый для последующих задач. Пример кода, который получает дескриптор устройства и открывает устройство, см . в разделе "Обсуждение кода шаблона".

Шаг 2. Запрос устройства для дескрипторов USB

Затем запросите устройство для сведений, относящихся к USB, таких как скорость устройства, дескрипторы интерфейса, связанные конечные точки и их каналы. Процедура аналогична той, которую используют драйверы USB-устройств. Однако приложение завершает запросы устройств, вызывая WinUsb_GetDescriptor.

В следующем списке показаны функции WinUSB, которые можно вызвать для получения сведений о usb-адаптере:

  • Дополнительные сведения об устройстве.

    Вызовите WinUsb_QueryDeviceInformation для запроса сведений от дескрипторов устройства. Чтобы получить скорость устройства, задайте DEVICE_SPEED (0x01) в параметре InformationType . Функция возвращает LowSpeed (0x01) или HighSpeed (0x03).

  • дескрипторы интерфейса;

    Вызовите WinUsb_QueryInterfaceSettings и передайте дескрипторы интерфейса устройства, чтобы получить соответствующие дескрипторы интерфейса. Дескриптор интерфейса WinUSB соответствует первому интерфейсу. Некоторые USB-устройства, такие как устройство OSR Fx2, поддерживают только один интерфейс без каких-либо альтернативных параметров. Таким образом, для этих устройств параметр AlternateSettingNumber имеет значение нулю, и функция вызывается только один раз. WinUsb_QueryInterfaceSettings заполняет структуру, выделенную вызывающим объектом USB_INTERFACE_DESCRIPTOR (переданную в параметре UsbAltInterfaceDescriptor) с информацией об интерфейсе. Например, число конечных точек в интерфейсе задается в элементе bNumEndpoints USB_INTERFACE_DESCRIPTOR.

    Для устройств, поддерживающих несколько интерфейсов, вызовите WinUsb_GetAssociatedInterface для получения дескрипторов интерфейса для связанных интерфейсов, указав альтернативные параметры в параметре AssociatedInterfaceIndex .

  • Конечные точки

    Вызовите WinUsb_QueryPipe для получения сведений о каждой конечной точке в каждом интерфейсе. WinUsb_QueryPipe заполняет структуру, выделенной вызывающим WINUSB_PIPE_INFORMATION, сведениями о канале указанной конечной точки. Каналы конечных точек определяются отсчитываемым от нуля индексом и должны быть меньше значения в элементе bNumEndpoints дескриптора интерфейса, полученного в предыдущем вызове **WinUsb_QueryInterfaceSettings. Устройство OSR Fx2 имеет один интерфейс с тремя конечными точками. Для этого устройства параметр AlternateInterfaceNumber функции имеет значение 0, а значение параметра PipeIndex зависит от 0 до 2.

    Чтобы определить тип канала, изучите элемент pipeInfo WINUSB_PIPE_INFORMATION структуры. Этот элемент имеет одно из значений перечисления USBD_PIPE_TYPE : UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk или UsbdPipeTypeInterrupt. Устройство OSR USB FX2 поддерживает канал прерывания, канал массового ввода и массовый канал, поэтому PipeInfo имеет значение UsbdPipeTypeInterrupt или UsbdPipeTypeBulk. Значение UsbdPipeTypeBulk определяет массовые каналы, но не предоставляет направление канала. Сведения о направлении кодируются в высоком бите адреса канала, который хранится в элементе PipeId WINUSB_PIPE_INFORMATION структуры. Самый простой способ определить направление канала — передать значение PipeId одному из следующих макросов из Usb100.h:

    • Макрос USB_ENDPOINT_DIRECTION_IN (PipeId) возвращает значение TRUE , если направление находится в.
    • Макрос USB_ENDPOINT_DIRECTION_OUT(PipeId) возвращает значение TRUE , если направление выходит.

    Приложение использует значение PipeId для определения канала, используемого для передачи данных в вызовах функций WinUSB, таких как 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;
}

В следующем примере кода запрашивается различные дескрипторы для USB-устройства, указанного дескриптором интерфейса WinUSB. Пример функции извлекает типы поддерживаемых конечных точек и их идентификаторы канала. В примере хранятся все три значения 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 байтами, указывающий, какие элементы должны быть освещены, задав соответствующие биты.

Приложение может предоставить набор из восьми элементов управления флажка, чтобы указать, какие элементы светлой панели должны быть освещены. Указанные элементы соответствуют соответствующим битам в буфере. Чтобы избежать кода пользовательского интерфейса, пример кода в этом разделе задает биты, чтобы альтернативные свети были освещены.

Выдача запроса элемента управления

  1. Выделите 1-байтовый буфер данных и загрузите данные в буфер, указывающий элементы, которые должны быть освещены, задав соответствующие биты.

  2. Создайте пакет установки в структуре, выделенной вызывающим WINUSB_SETUP_PACKET . Инициализировать элементы для представления типа запроса и данных следующим образом:

    • Элемент RequestType указывает направление запроса. Для него задано значение 0, указывающее передачу данных узла на устройство. Для передачи устройств на узел задайте значение RequestType значение 1.
    • Элемент запроса устанавливается в код, определенный поставщиком для этого запроса, 0xD8. Он определен для удобства как SET_BARGRAPH_DISPLAY.
    • Элемент Length имеет размер буфера данных.
    • Элементы индекса и значения не требуются для этого запроса, поэтому они равны нулю.
  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. Выдача запросов ввода-вывода

Затем отправьте данные в конечные точки массовой и массовой обработки устройства, которые можно использовать для запросов на чтение и запись соответственно. На устройстве USB-FX2 OSR эти две конечные точки настроены для обратного цикла, поэтому устройство перемещает данные из конечной точки массового ввода в конечную точку массовой обработки. Он не изменяет значение данных или не добавляет новые данные. Для конфигурации обратного цикла запрос на чтение считывает данные, отправленные последним запросом на запись. WinUSB предоставляет следующие функции для отправки запросов на запись и чтение:

Отправка запроса на запись

  1. Выделите буфер и заполните его данными, которые необходимо записать на устройство. Нет ограничений на размер буфера, если приложение не задает RAW_IO в качестве типа политики канала. WinUSB делит буфер на соответствующие блоки, если это необходимо. Если задано RAW_IO, размер буфера ограничен максимальным размером передачи, поддерживаемым WinUSB.
  2. Вызовите WinUsb_WritePipe для записи буфера на устройство. Передайте дескриптор интерфейса WinUSB для устройства, идентификатор канала для конвейера массового вывода (как описано в разделе запроса устройства для дескрипторов USB) и буфера. Функция возвращает количество байтов, записанных на устройство в параметре bytesWritten . Параметр Overlapped имеет значение NULL , чтобы запросить синхронную операцию. Чтобы выполнить асинхронный запрос на запись, задайте для указателя на структуру OVERLAPPED.

Запросы на запись, содержащие данные нулевой длины, перенаправляются по стеку 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. Реализация основной функции

В следующем примере кода показана основная функция консольного приложения.

Пример кода, который получает дескриптор устройства и открывает устройство (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. Дополнительные сведения см. в статье Send USB isochronous transfer from a WinUSB desktop app.

См. также