Программирование PC Как работать с устройством USB через функции WinUSB Mon, December 09 2019  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Как работать с устройством USB через функции WinUSB Печать
Добавил(а) microsin   

Процесс работы с устройством USB заключается в следующем:

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

Для этого используется соответствующее API (DLL):

• Функции SetupAPI [4] (setupapi.dll)
• Функции WinUSB [5] (winusb.dll)

В этой статье (перевод документации [1]) описан процесс использования функций WinUSB для обмена с устройством USB, которое использует Winusb.sys в качестве своего функционального драйвера.

Если Вы используете Microsoft Visual Studio 2013, то создайте свое приложение на основе шаблона WinUSB. В этом случае пропустите шаги от 1 до 3 и сразу переходите к шагу 4. Этот шаблон откроет file handle для устройства и получит WinUSB handle, необходимое для последующих операций. Этот handle сохраняется в определенной приложением структуре DEVICE_DATA в device.h. Для дополнительной информации о шаблоне см. статью "Write a Windows desktop app based on the WinUSB template" [2].

Примечание: функции WinUSB требуют Windows XP или более свежую Windows. Вы можете использовать эти функции в своем приложении C/C++ для обмена со своим устройством USB. Microsoft не предоставляет managed API для WinUSB.

[Что потребуется]

1. Эта информация применима к версиям Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista семейства операционных систем Windows.

2. Для своего устройства Вы установили Winusb.sys в качестве функционального драйвера. Для дополнительной информации по этому процессу см. "WinUSB (Winusb.sys) Installation" [3].

3. Примеры основаны на обучающем устройстве OSR USB FX2 Learning Kit [11]. Вы можете использовать эти примеры для расширения процедур с другими устройствами USB.

[Шаг 1: создание скелета приложения на основе шаблона (WinUSB template)]

Шаблон WinUSB включен в интегрированное окружение Windows Driver Kit (WDK) вместе с инструментами отладки (Debugging Tools for Windows) и в Microsoft Visual Studio. Вы можете использовать этот шаблон как стартовую точку для создания приложения (подробности см. в [2], где описан процесс создания приложения на основе этого шаблона).

Шаблон производит энумерацию устройств с помощью подпрограмм SetupAPI, открывает file handle для устройства, и создает дескриптор для интерфейса WinUSB (WinUSB interface handle), который требуется для последующих задач. Код примера, который получает device handle и открывает устройство, см. в [2].

[Шаг 2: опрос устройства для получения дескрипторов USB]

Опросите устройство для получения относящейся к USB информации, такой как скорость устройства, дескрипторы интерфейса, связанные с ними конечные точки (endpoints) и их каналы (pipes). Эта процедура подобна той, что используется с драйверами устройства USB. Однако приложение завершает опросы устройства вызовом WinUsb_GetDescriptor.

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

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

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

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

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

Чтобы определить тип канала, опросите поле PipeInfo структуры WINUSB_PIPE_INFORMATION. Это поле установится в одно из значений перечисления USBD_PIPE_TYPE: UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk или UsbdPipeTypeInterrupt. Устройство OSR USB FX2 поддерживает interrupt pipe, bulk-in pipe и bulk-out pipe, так что PipeInfo установится в UsbdPipeTypeInterrupt или UsbdPipeTypeBulk. Значение UsbdPipeTypeBulk идентифицирует bulk-каналы, но не предоставляет направление канала. Информация о направлении кодируется в старшем бите адреса канала (pipe address), который сохраняется в поле PipeId структуры WINUSB_PIPE_INFORMATION. Самый простой путь определить направление канала - передать значение PipeId в один из следующих макросов Usb100.h:

• Макрос USB_ENDPOINT_DIRECTION_IN (PipeId) вернет TRUE, если направление от устройства к хосту (IN endpoint).
• Макрос USB_ENDPOINT_DIRECTION_OUT(PipeId) вернет TRUE, если направление от хоста к устройству (OUT endpoint).

Приложение использует значение PipeId для идентификации, какой канал используется для передачи данных в вызовах функций WinUSB, таких как WinUsb_ReadPipe (описывается в секции шага 4 этой статьи). В примере используются все три значения PipeId для последующего использования.

Следующий код примера получает скорость устройства, которое указывается через WinUSB interface handle.

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 interface handle. Пример функции получает типы поддерживаемых конечных точек и их идентификаторов канала (pipe identifiers). Пример сохраняет все три значения 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: отправка Control Transfer в Default Endpoint]

Control Transfer это управляющая передача, которая обычно используется для настройки устройства, передачи ему параметров или получения информации о его текущих настройках. Протокол этой передачи определяется классом устройства USB (определяется спецификацией устройства, предоставляемой стандартом класса или производителем устройства).

Default Endpoint это конечная точка по умолчанию, она же управляющая конечная точка, она же конечная точка 0. Эта конечная точка обязательно должна присутствовать в любом устройстве USB, поскольку она выполняет специальные функции управления устройством.

Все устройства USB имеют конечную точку по умолчанию (default endpoint) в дополнение к конечным точкам, связанным с интерфейсами. Главное назначение default endpoint - предоставить хосту информацию, которую можно использовать для конфигурирования устройства. Однако устройства также могут использовать default endpoint для целей, специфических для устройства. Например, устройство OSR USB FX2 использует default endpoint для управления полосой светодиодов (light bar) и 7-сегментным цифровым дисплеем.

Команды управления состоят и 8-байтного пакета настройки (setup packet), который включает код запроса, описывающий определенный запрос, и опциональный буфер данных. Код запроса и форматы буфера определяются протоколом, зависящим от класса устройства (для пользовательского класса протокол определяется вендором, т. е. производителем устройства). В этом примере приложение отправляет данные в устройство, чтобы управлять light bar. Код запроса для установки light bar равен 0xD8, который определяется для удобства макросом SET_BARGRAPH_DISPLAY. Для этого запроса устройство требует 1-байтный буфер данных, где отдельные биты управляют зажиганием определенного светодиода.

Приложение может реализовать это через интерфейс пользователя (UI), если для этого использовать набор из 8 чекбоксов. Установленная галочка в чекбоксе будет устанавливать бит в 1-байтном буфере данных, что в свою очередь будет управлять свечением светодиодов. Чтобы избежать кода UI, код примера в этой секции установит биты так, чтобы зажглись дополнительные светодиоды.

Используйте следующие шаги для выдачи запроса управления (control request).

1. Выделите буфер данных из 1 байта, и загрузите в него необходимые данные.

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

• Поле RequestType указывает направление запроса. Если его установить в 0, то это показывает направление передачу данных от хоста к устройству. Для передач от устройства к хосту установите RequestType в 1.
• Поле Request устанавливается в код, определяемый вендором для этого запроса. В нашем случае для примера заполните поле Request значением 0xD8, это значение для удобства определено как макрос SET_BARGRAPH_DISPLAY.
• Поле Length устанавливается в размер буфера данных (т. е. в нашем случае 1).
• Поля Index и Value не нужны для этого запроса, поэтому устанавливаются в 0.

3. Вызовите функцию WinUsb_ControlTransfer для передачи запроса в default endpoint путем передачи в функцию WinUSB interface handle устройства и буфера данных. Функция принимает количество байт, передаваемое в устройство, в параметре LengthTransferred.

Следующий пример кода отправляет запрос управления (control request) в указанное устройство USB для управления полосой огней светодиодов (light bar).

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;
 
   // Установка бит для зажигания дополнительных светодиодов бара:
   for (short i = 0; i < 7; i+= 2)
   {
      bars += 1 << i;
   }
 
   // Создание setup-пакета:
   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: выдача запросов ввода/вывода]

Здесь мы будем использовать конечные точки bulk-in и bulk-out для запросов чтения и записи соответственно. На тестовом устройстве OSR USB FX2 эти две конечные точки сконфигурированы как петля передачи данных со входа на выход (loopback), т. е. данные, которые устройство получает, оно зеркально передает (данные, которые пришли на bulk-out endpoint, устройство передает на bulk-in endpoint). В принимаемые данные устройство не вносит никаких изменений и не добавляет новые данные. Конечно, это сделано только в демонстрационных целях. Для loopback-конфигурации запрос чтения (read request) хоста читает данные, которые были ранее отправлены запросом записи (write request) того же хоста. Для отправки запросов записи и чтения WinUSB предоставляет следующие функции:

WinUsb_WritePipe
WinUsb_ReadPipe

Для отправки запроса записи:

1. Выделите в памяти буфер, и заполните его данными, которые хотите записать в устройство. Здесь нет ограничений на размер буфера, если приложение не установило RAW_IO как тип политики канала (pipe policy type). WinUSB автоматически поделит буфер на куски подходящего размера, если это необходимо. Если же установлено RAW_IO, то размер буфера ограничивается максимальным размером передачи, поддерживаемым WinUSB.

2. Вызовите функцию WinUsb_WritePipe для записи буфера в устройство. Передайте в эту функцию WinUSB interface handle устройства, идентификатор канала (pipe identifier) для bulk-out pipe (как было описано на шаге 2) и буфер. Функция вернет количество байт, которое было реально записано в устройство, в параметре bytesWritten. Параметр Overlapped устанавливается в NULL для запроса с синхронным поведением (т. е. с ожиданием завершения запроса). Для выполнения асинхронного запроса записи установите Overlapped в указатель на структуру OVERLAPPED.

Запросы записи, которые содержат данные нулевой длины, перенаправляются вниз в стек USB. Если длина передачи больше, чем максимальная длина передачи, то WinUSB делит запрос на запросы с меньшей длиной запроса, и предоставляет их последовательно. Следующий пример кода выделяет строку и отправляет её в конечную точку bulk-out устройства.

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

Запросы чтения нулевой длины немедленно успешно завершатся без передачи вниз по стеку. Если длина передачи больше максимальной длины пакета, то WinUSB поделит запрос на запросы меньшего размера (максимальной длины передачи) и последовательно предоставит их операционной системе. Если длина передачи не делится на MaxPacketSize конечной точки, то WinUSB увеличит размер передачи до следующего значения, нацело делящегося на MaxPacketSize. Если устройство возвратит данных больше, чем было запрошено, то WinUSB сохранит лишние данные. Если остались данные от предыдущего запроса, то WinUSB скопирует их в начало следующего запроса чтения и завершит запрос, если это необходимо. Следующий код дает пример чтения данных из конечной точки bulk-in устройства.

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: освобождение file handle и WinUSB interface handle]

После того, как Вы завершили все требуемые вызовы для устройства, освободите file handle и WinUSB interface handle. Для этого вызовите следующие функции:

• CloseHandle для освобождения handle, созданный вызовом CreateFile, как описано на шаге 1.
WinUsb_Free для освобождения WinUSB interface handle для устройства, возвращенного вызовом WinUsb_Initialize.

[Шаг 6: реализация Main]

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

int _tmain(int argc, _TCHAR* argv[])
{
   GUID guidDeviceInterface = OSR_DEVICE_INTERFACE; // в INF-файле
   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 и более новых версиях Windows. Для дополнительной информации см. [6].

[Ссылки]

1. How to Access a USB Device by Using WinUSB Functions site:docs.microsoft.com.
2. Write a Windows desktop app based on the WinUSB template site:docs.microsoft.com.
3. WinUSB (Winusb.sys) Installation site:docs.microsoft.com.
4. SetupAPI site:docs.microsoft.com.
5. WinUSB Functions site:docs.microsoft.com.
6. Send USB isochronous transfers from a WinUSB desktop app site:docs.microsoft.com.
7. WinUSB (Winusb.sys) site:docs.microsoft.com.
8. WinUSB Architecture and Modules site:docs.microsoft.com.
9. WinUSB Functions for Pipe Policy Modification site:docs.microsoft.com.
10. WinUSB Power Management site:docs.microsoft.com.
11. OSRONLINE is OSR's Legacy Community Site site:osronline.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page