Процесс работы с устройством 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.
Время на прочтение8 мин
Количество просмотров7.9K
Продолжение, первая часть здесь.
Упрощенная структура USB. Видно что есть всего два прерывания USBIRQ и USBWU
Инициализация USB
Ничего сложного, описание регистров смотрите даташите. Разрешаем endpoint 0-2, включаем прерывания, указывает буферы для хранения данных. (Endpoint 2 и 3 разрешены для работы с double buffer при отправке данных к хосту)
Инициализация USB
void usb_init(void)
{
// Setup state information
usb_state = DEFAULT;
usb_bm_state = 0;
// Setconfig configuration information
usb_current_config = 0;
usb_current_alt_interface = 0;
// Disconnect from USB-bus since we are in this routine from a power on and not a soft reset:
usbcs |= 0x08;
delay_ms(50);
usbcs &= ~0x08;
/*intterrupt enable uresie,suspie,sutokie,sudavie */
usbien = 0x1d;
/*Endpoint 0 to 5 IN interrupt enables (in_ien)*/
in_ien = 0x01;
/*Endpoints 0 to 5 IN interrupt request register (in_irq) - clear interrupt*/
in_irq = 0x1f;
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/
out_ien = 0x01;
/*Endpoints 0 to 5 OUT interrupt request register (out_irq) - clear in interrupt*/
out_irq = 0x1f;
// Setup the USB RAM with some OK default values:
bout1addr = MAX_PACKET_SIZE_EP0/2;
bout2addr = MAX_PACKET_SIZE_EP0/2 + USB_EP1_SIZE/2;
bout3addr = MAX_PACKET_SIZE_EP0/2 + 2*USB_EP1_SIZE/2;
bout4addr = MAX_PACKET_SIZE_EP0/2 + 3*USB_EP1_SIZE/2;
bout5addr = MAX_PACKET_SIZE_EP0/2 + 4*USB_EP1_SIZE/2;
binstaddr = 0xc0;
bin1addr = MAX_PACKET_SIZE_EP0/2;
bin2addr = MAX_PACKET_SIZE_EP0/2 + USB_EP1_SIZE/2;
bin3addr = MAX_PACKET_SIZE_EP0/2 + 2*USB_EP1_SIZE/2;
bin4addr = MAX_PACKET_SIZE_EP0/2 + 3*USB_EP1_SIZE/2;
bin5addr = MAX_PACKET_SIZE_EP0/2 + 4*USB_EP1_SIZE/2;
// Set all endpoints to not valid (except EP0IN and EP0OUT)
/*Endpoints 0 to 5 IN valid bits (Inbulkval)*/
inbulkval = 0x01;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
outbulkval = 0x01;
/*Isochronous IN endpoint valid bits (inisoval)*/
inisoval = 0x00;
/*Isochronous OUT endpoint valid bits (outisoval)*/
outisoval = 0x00;
/* Switch ON Endpoint 1 */
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)* - out1ien */
in_ien |= 0x02;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
inbulkval |= 0x02;
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/
out_ien |= 0x02;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
outbulkval |= 0x02;
/* Endpoint 0 to 5 OUT byte count registers (outxbc) ?Maybe 0xff is register clear*/
out1bc = 0xff;
/* Switch ON Endpoint 2 */
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)* - out1ien */
in_ien |= 0x04;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
inbulkval |= 0x04;
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/
out_ien |= 0x04;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
outbulkval |= 0x04;
/* Endpoint 0 to 5 OUT byte count registers (outxbc) ?Maybe 0xff is register clear*/
out2bc = 0xff;
/* Switch ON Endpoint 3 */
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)* - out1ien */
in_ien |= 0x08;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
inbulkval |= 0x08;
/*Endpoint 0 to 5 OUT interrupt enables (out_ien)*/
out_ien |= 0x08;
/*Endpoints 0 to 5 OUT valid bits (outbulkval)*/
outbulkval |= 0x08;
/* Endpoint 0 to 5 OUT byte count registers (outxbc) ?Maybe 0xff is register clear*/
out3bc = 0xff;
}
Далее разрешаем перекрывание USBIRQ (дальше нужно анализировать флаги) и включаем прерывания глобально:
USB = 1; // USBIRQ is mapped to IEN1.4
EA = 1; // enable global interrupt
Собственно это и вся инициализация. При каждом прерывании, будет вызываться обработчик который выглядит примерно вот так:
/* USB interrupt request */
void usb_irq_handler(void) interrupt INTERRUPT_USB_INT {
usb_irq();
}
Обработчик прерывания USB
Это основа нашей системы. Все действие будет крутиться здесь. Какое именно событие произошло будем понимать после анализа регистра IVEC.
void usb_irq(void)
{
uint8_t i;
uint8_t temp_irq;
if (ivec == INT_USBRESET)
{
/*The USB interrupt request register (usbirq) - clear USB reset interrupt request*/
usbirq = 0x10;
usb_state = DEFAULT;
usb_current_config = 0;
usb_current_alt_interface = 0;
usb_bm_state = 0;
}
else
{
switch(ivec)
{
case INT_SUDAV: /*Setup data valid interrupt*/
usbirq = 0x01;
isr_sudav();
break;
case INT_SOF: /*Start of frame interrupt (sofir)*/
usbirq = 0x02;
break;
case INT_SUTOK: /*Setup token interrupt*/
usbirq = 0x04;
packetizer_data_ptr = NULL;
packetizer_data_size = 0;
packetizer_pkt_size = 0;
break;
case INT_SUSPEND: /*Suspend interrupt (suspir)*/
usbirq = 0x08;
break;
case INT_EP0IN:
in_irq = 0x01;
packetizer_isr_ep0_in();
break;
case INT_EP0OUT:
out_irq = 0x01;
packetizer_data_size = 0;
USB_EP0_HSNAK();
break;
case INT_EP1IN:
in_irq = 0x02;
int_ep1in_handler();
break;
case INT_EP1OUT:
out_irq = 0x02;
out1bc = 0xff;
break;
case INT_EP2IN:
in_irq = 0x04;
break;
default:
break;
}
}
}
Энумерация
Пожалуй, воспользуюсь цитатой из замечательного руководства USB in a NutShell — путеводитель по стандарту USB
Энумерация – процесс определения факта, что устройство действительно подключено к шине USB и каких параметров это требует – потребляемая мощность,
количество и тип конечной точки (или точек), класс устройства и т. д. В процессе энумерации хост назначает устройству адрес и разрешает конфигурацию,
позволяющую устройству передавать данные по шине. […]
Общий процесс энумерации под операционной системой Windows включает в себя следующие шаги:1. Хост или хаб детектирует подключение нового устройства с помощью pull-up резисторов, которое устройство подключает к паре сигнальных проводов данных (D+ и D-). Хост делает задержку как минимум 100 мс, что позволяет вставить коннектор полностью и застабилизировать питание устройства.
2. Хост выдает на шину сброс, который выводит устройство в состояние по умолчанию. Устройство может теперь ответить на заданный по умолчанию нулевой адрес.
3. Хост MS Windows запрашивает первые 64 байта дескриптора устройства (Device Descriptor).
4. После приема первых 8 байт дескриптора устройства, хост немедленно выдает новый сброс шины.
5. Теперь хост выдает команду Set Address, чем переводит устройство в адресуемое состояние.
6. Хост запрашивает все 18 байт дескриптора устройства.
7. Затем он запрашивает 9 байт дескриптора конфигурации (Configuration Descriptor), чтобы определить полный её размер.
8. Хост запрашивает 255 байт дескриптора конфигурации.
9. Хост запрашивает все строковые дескрипторы (String Descriptors), если они имеются.
Для поддержи WinUSB требуется дополнительная процедура:
- На шаге 9 Windows будет запрашивать нестандартный строковый дескриптор 0xEE. Именно правильный ответ на него начинает дальнейшую процедуры обмена данными для WinUSB.
- Далее идет запрос Extended Compat ID OS Descriptor.
- Последний — Extended Properties OS Descriptor. Именно здесь девайс сообщает свой GUID.
Рекомендую вот этот мануал для понимая процесса.
Для анализа и отладки минимальный набор это — UART на стороне контроллера и программный анализатор USB на хосте. Рекомендую бесплатный Microsoft Message Analyzer.
Итак, вся энумерация (и последующие vendor requset и прочие запросы, если они нужны) будут обрабатываться в функции isr_sudav() (Setup data valid interrupt).
isr_sudav()
static void isr_sudav()
{
bmRequestType = setupbuf[0];
/* Host-to-device standart request */
if((bmRequestType & 0x60 ) == 0x00)
{
switch(setupbuf[1])
{
case USB_REQ_GET_DESCRIPTOR:
usb_process_get_descriptor();
break;
case USB_REQ_GET_STATUS:
usb_process_get_status();
break;
case USB_REQ_SET_ADDRESS:
usb_state = ADDRESSED;
usb_current_config = 0x00;
break;
case USB_REQ_GET_CONFIGURATION:
switch(usb_state)
{
case ADDRESSED:
in0buf[0] = 0x00;
in0bc = 0x01;
break;
case CONFIGURED:
in0buf[0] = usb_current_config;
in0bc = 0x01;
break;
case ATTACHED:
case POWERED:
case SUSPENDED:
case DEFAULT:
default:
USB_EP0_STALL();
break;
}
break;
case USB_REQ_SET_CONFIGURATION:
switch(setupbuf[2])
{
case 0x00:
usb_state = ADDRESSED;
usb_current_config = 0x00;
USB_EP0_HSNAK();
break;
case 0x01:
usb_state = CONFIGURED;
usb_bm_state |= USB_BM_STATE_CONFIGURED;
usb_current_config = 0x01;
USB_EP0_HSNAK();
break;
default:
USB_EP0_STALL();
break;
}
break;
case USB_REQ_GET_INTERFACE: // GET_INTERFACE
in0buf[0] = usb_current_alt_interface;
in0bc = 0x01;
break;
case USB_REQ_SET_DESCRIPTOR:
case USB_REQ_SET_INTERFACE: // SET_INTERFACE
case USB_REQ_SYNCH_FRAME: // SYNCH_FRAME
default:
USB_EP0_STALL();
break;
}
}
// bmRequestType = 0 01 xxxxx : Data transfer direction: Host-to-device, Type: Class
else if((bmRequestType & 0x60 ) == 0x20) // Class request
{
if(setupbuf[6] != 0 && ((bmRequestType & 0x80) == 0x00))
{
// If there is a OUT-transaction associated with the Control-Transfer-Write we call the callback
// when the OUT-transaction is finished. Note that this function do not handle several out transactions.
out0bc = 0xff;
}
else
{
USB_EP0_HSNAK();
}
}
/* Extended Compat ID OS Descriptor setupbuf[1] (bRequest) is equal to MS_VendorCode (0xAA is current program)*/
else if(bmRequestType == 0xC0 && setupbuf[1] == MS_VENDORCODE)
{
packetizer_pkt_size = MAX_PACKET_SIZE_EP0;
//xprintf("Extended Compat ID\r");
packetizer_data_ptr = g_usb_extended_compat_id;
packetizer_data_size = MIN(setupbuf[6], packetizer_data_ptr[0]);
packetizer_isr_ep0_in();
}
/* Extended Properties OS Descriptor */
else if(bmRequestType == 0xC1 && setupbuf[1] == MS_VENDORCODE)
{
packetizer_pkt_size = MAX_PACKET_SIZE_EP0;
//xprintf("Extended Properties ID\r");
packetizer_data_ptr = g_usb_extended_proper_os;
packetizer_data_size = MIN(setupbuf[6], packetizer_data_ptr[0]);
packetizer_isr_ep0_in();
}
else // Unknown request type
{
USB_EP0_STALL();
}
}
Предпоследние два ветвления в этой функции ответственны за запросы Extended Compat ID OS Descriptor и Extended Properties OS Descriptor.
На этом этапе энумерация закончена, можно приступить к приму/передаче данных.
Bulk Tranfer
In Tranfer
Хост хочет принять данные. Отправляет In token. Если бит inxbsy (x — номер эндпойнта) установлен, то USB контроллер отправляет данные в хост. Что бы установить это самый бит, мы должны предварительно загрузить данные в буфер inxbuf[] и сказать сколько загрузили записью в регистр inxbc, т.е. данные надо предзагрузить до прерывания.
void int_ep1in_handler(void)
{
uint8_t i;
for(i=0;i<64;i++){
in1buf[i]=i;
}
in1bc = 64;
}
После успешной отправки данных — получили ACK — происходит прерывание INT_EP1IN (функция usb_irq) и в нем можно загрузить следующий кусок данных.
Out Tranfer
Хост хочет отправить данные. Отправляет OUT token. Следом за токеном идут данные. После того как данные приняты срабатывает прерывание INT_EP1OUT. Данные читаются из буфера outxbuf[]. Размер данных — outxbc. После того как обработали данные нужно в регистр outxbc записать любое значение, тем самым даем понять USB контроллеру что мы готовы принимать следующий кусок данных.
void int_ep1out_handler(void)
{
do_anything(out1buf,in1bc);
in1bc = 0xFF; //dummy write
}
Double buffering
Если стоит задача отправить или принять данные с максимальной скоростью, то необходимо пользоваться двойной буферизацией. Пока контроллер занят приемом или отправкой данных, его буферы (inxbuf и outxbuf) не доступны. Идея в том что, появляется дополнительный буфер, куда мы пишем во время занятости контроллера, а потом просто их переключаем.
За этот режим отвечает регистр usbpair. Например, записывая туда 0x01 мы объединяем 2 и 3 in endpoint. Буфер endpoint 3 и будет являться вторым буфером.
Однако, документация на этот режим крайне непонятная и порой взаимоисключающая. Путем долгих экспериментов у меня получился вот такой код обслуживания буферов для передачи на хост:
while(1) {
while(in2cs != 0);
if(i%2){
//first buffer
for(i=0;i<64;i++)
in2buf[i]=0xAA;
in2bc = 64;
} else {
//second buffer
for(i=0;i<64;i++)
in3buf[i]=0xBB;
in2bc = 64;
}
i++;
}
Ждем пока IN 2 освободиться и по очереди льем данные то в in2buf то in3buf, то при этом все время обновляем in2bc (!!!). Код особо не тестировался, применяйте с осторожностью.
Программируем Host
Весь код писался на Visual Studio 2013. Сразу установите себе Windows Driver Kit (WDK). Он добавляет шаблоны проектов USB. Советую изучить пару мануалов от Microsoft, там все подробно расписано:
- Write a Windows desktop app based on the WinUSB template
- How to Access a USB Device by Using WinUSB Functions
Программа проста до нельзя: открываем девайс, получаем дескриптор, читаем и пишем в пайп. Все.
Замеры скорости
Вот каких результатов удалось добиться:
Передача данных на хост в single buffer | 360 кб/с |
Передача данных на хост в double buffer | 510 кб/с |
Прием данных с хоста single buffer | 230 кб/c |
Прием данных с хоста double buffer | не тестировался |
Заключение
Надеюсь статья принесет пользу начинающим и не только. Исходники не особо причесаны, сильно не пинайте, выкладываю на гитхаб, пользуйтесь на здоровье.
github.com/covsh/workingtitle/tree/master/nRF24LU1P

Download And Install Winusb Device Winusb Device Driver Id 2158354 You must write a filter driver. there is no other way to intercept traffic generated by other devices. and you can use newer versions of the ddk, that is, kmdf for this task. Winusb consists of two primary components: winusb.sys is a kernel mode driver that can be installed as either a filter or function driver, above the protocol drivers in a usb device’s kernel mode device stack. winusb.dll is a user mode dll that exposes winusb functions.

Download And Install Winusb Device Winusb Device Driver Id 2158354 Microsoft provided winusb.sys (kernel mode driver). if filter drivers are present, access is limited to privileged apps. the app is declared as privileged apps in device metadata by the oem. filter driver can be present in the kernel mode device stack as long as it doesn’t block access to winusb.sys. By default, winusb.sys is installed in the device’s kernel mode stack as an upper filter driver. apps communicate with the device’s umdf function driver to issue read, write, or device i o control requests. in this configuration, winusb.sys serves as the device stack’s plug and play and power owner. Here is an example at google groups about writing a usb filter driver. that thread gives a brief discussion on its limitations. meanwhile, stack overflow question winusb application or user mode driver as a filter driver for usb analysis sniffer trending shows a use case. Winusb includes the winusb kernel mode driver winusb.sys, which is an integral part of wdf user mode driver framework (umdf) support for usb drivers, and the winusb user mode dynamic link library winusb.dll.

Winusb Driver Here is an example at google groups about writing a usb filter driver. that thread gives a brief discussion on its limitations. meanwhile, stack overflow question winusb application or user mode driver as a filter driver for usb analysis sniffer trending shows a use case. Winusb includes the winusb kernel mode driver winusb.sys, which is an integral part of wdf user mode driver framework (umdf) support for usb drivers, and the winusb user mode dynamic link library winusb.dll. You are going to apply the winusb user mode dll, winusb initialize(), to a umdf driver (osr fx2 driver). it can’t. the user mode dll is designed to access just to the winusb kernel mode driver directly. Microsoft visual studio includes usb user mode driver and usb kernel mode driver templates that generate starter code for a umdf and kmdf usb client driver, respectively. the template code initializes a usb target device object to enable communication with the hardware. Winusb is a kernel mode driver that was developed concurrently with the windows driver foundation (wdf). this driver is available on windows xp and later versions of windows and has a user mode api, exported by winusb.dll, which allows applications to talk to usb devices. This is a try to develop a win upper filter driver to patch usb support adding port power control feature to powered usb hubs which support it (very few). the target is to allow uhubctl github mvp uhubctl to work on windows.
Winusb Driver You are going to apply the winusb user mode dll, winusb initialize(), to a umdf driver (osr fx2 driver). it can’t. the user mode dll is designed to access just to the winusb kernel mode driver directly. Microsoft visual studio includes usb user mode driver and usb kernel mode driver templates that generate starter code for a umdf and kmdf usb client driver, respectively. the template code initializes a usb target device object to enable communication with the hardware. Winusb is a kernel mode driver that was developed concurrently with the windows driver foundation (wdf). this driver is available on windows xp and later versions of windows and has a user mode api, exported by winusb.dll, which allows applications to talk to usb devices. This is a try to develop a win upper filter driver to patch usb support adding port power control feature to powered usb hubs which support it (very few). the target is to allow uhubctl github mvp uhubctl to work on windows.

Winusb Driver Winusb is a kernel mode driver that was developed concurrently with the windows driver foundation (wdf). this driver is available on windows xp and later versions of windows and has a user mode api, exported by winusb.dll, which allows applications to talk to usb devices. This is a try to develop a win upper filter driver to patch usb support adding port power control feature to powered usb hubs which support it (very few). the target is to allow uhubctl github mvp uhubctl to work on windows.
Winusb Driver
Provide feedback
Saved searches
Use saved searches to filter your results more quickly
Sign up
Appearance settings
I have encountered problems in following the Microsoft MSDN tutorial on Winusb. The tutorial is Write a Windows desktop app based on the Winusb template.
Following the instructions, in the step where I need to create a Winusb application the site says that two projects will be created, however, my application just creates a project.
I’m using Microsoft Visual Studio Community 2015 and installed WDK. Note: I am using Windows 10 64 bits.
Below follows part of the tutorial saying that two projects will be created.
Below follows a screenshot of my Solution Explorer:
Could someone help me understand why this is happening and help resolve this issue?