How to make an embedded device enumerate as a WinUSB device
[Copy link]
As the most popular and universal interface on PC, USB interface can connect various types of devices, has simple connection, plug and play, supports hot plug, does not need to provide independent power supply in most application scenarios, has high transmission rate, high reliability and other characteristics, and is used as the preferred interface by more and more products as the connection method to PC. In order to simplify the development of USB devices and access to PC systems, Microsoft developed WinUSB, which can install Winusb.sys as a device function driver and provide WinUSB API for applications to access devices. For a long time, except for USB HID devices, other types of devices need to install drivers to work in WINDOWS environment. To achieve driver-free USB devices, only HID devices can be used. However, HID devices have slow transmission speeds. In some occasions, when bulk type must be used for batch transmission, third-party drivers must be used or a driver must be developed by oneself, making project development very troublesome. Now, since Microsoft launched WinUSB, it has become very convenient and fast to implement simple bulk type batch transmission on Microsoft's latest operating system. It is very applicable and easy to implement in the research and development process or in some occasions where differentiation requirements are not high. This article is dedicated to implementing a simple WinUSB communication system to meet such needs.
How to make an embedded device enumerate as a WinUSB device
The system uses the USB descriptor to determine which USB Class type to work with. If you want WINDOWS to be able to identify the embedded device as a WinUSB device, its descriptor should at least contain the following fields:
1. Support OS string descriptors:
In order for the USB driver stack to know that the device supports extended characteristic descriptors, the device must define an OS string descriptor that is stored at string index 0xEE. During enumeration, the driver stack queries for the string descriptor. If the descriptor is present, the driver stack assumes that the device contains one or more OS characteristic descriptors and the data required to retrieve those characteristic descriptors. The retrieved string descriptor has a bMS_VendorCode field value. This value of 1 indicates the vendor code that the USB driver stack must use to retrieve the extended characteristic descriptors.
#define bMS_VendorCode (0x01)
// "MSFT100" : index : 0xEE : langId : 0x0000
const U8 OS_StringDescritpor[ ] =
{ 0x12, 0x03, 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, bMS_VendorCode, 0 };
2. Set compatible ID feature descriptor:
const U8 WINUSB_Ex te ndedCompa tI d_Descritpor[ ] =
{
0x28, 0x00, 0x00, 0x00, // dwLength
0x00, 0x01, // bcdVe rs ion
0x04, 0x00, // wIndex
0x01, // bCount
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved[7]
0x00, // bFirstInterfaceNumber
0x01, // RESERVED ( 0x01 )
'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, // compactiableID[8]
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // subCompactiableID[8]
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Reserved[6]
};
Note: WinUSB also supports composite devices. For the simplest system with a single transfer type, we can ignore the requirement for composite devices. The compatibleID field must specify "WINUSB" as the field value. Others can be changed as needed.
3. Register the device interface GUID descriptor:
This descriptor is used to distinguish different WinUSB devices.
const U8 WINUSB_ExtendedProperty_InterfaceGUID_Descritpor[ ] =
{
0x8E, 0x00, 0x00, 0x00, // dwTotalSize = Header + All sections
0x00, 0x01, // bcdVersion
0x05, 0x00, // wIndex
0x01, 0x00, // wCount
0x84, 0x00, 0x00, 0x00, // dwSize -- this section
0x01, 0x00, 0x00, 0x00, // dwPropertyDataType
0x28, 0x00, // wPropertyNameLength 'D',0,'e',0,'v',0,'i',0,'c',0,'e',0,'I',0,' n',0x00,'t',0,'e',0,'r',0,'f',0,'a',0,'c',0,'e',0, 'G' ,0,'U',0,'I',0,'D',0,0,0,
0x4E, 0x00, 0x00, 0x00, // dwPropertyDataLength : 78 Bytes = 0x0000004E
'{',0,'1',0,'2',0,'3',0,'4',0, '5',0,'6',0,'7',0,'8 ',0,'-',0,'1',0,'2',0,'3',0,'4',0,'-',0,'1',0,'3', 0,'4',0,'4',0,'-',0,'1',0,'2',0,'3',0,'4',0,'-',0, '1',0,'2',0,'3',0,'4',0,'5',0,'6',0,'7',0,'8',0,'9 ',0,'A',0,'B',0,'C',0,'}',0,0,0
};// bPropertyData : WCHAR : L"{12345678-1234-1234-1234-123456789ABC}"
4. Endpoint descriptor:
By configuring the number and type of endpoints according to actual needs, the descriptor configuration of the embedded device can be completed.
Generally, the firmware program can be modified through the sample program provided by the MCU manufacturer. The description of the USB firmware function is omitted here. As long as the required fields in the above three descriptors are included, it can be successfully enumerated as a USB Device. After the enumeration is successful, similar devices can be seen in the device WINDOWS device manager, as shown in Figure 1 below.
Figure 1 Successfully enumerated as a USB Device
How to write a PC application to communicate with an embedded device via USB
The PC software is relatively simple, and Microsoft has also provided sample code. The only thing to note is that the GUID parameter of the corresponding software program to obtain the WinUSB device handle must be consistent with the GUID in the descriptor of the embedded device. GUID is the unique mark used by WinUSB to distinguish devices. GUID, short for Globally Unique Identifier, is a binary data generated by an algorithm and is a 128-bit digital identifier.
The specific implementation steps are as follows:
1. Create a file handle for the device:
Call SetupDiGetClassDevs to get the handle of the device information set;
Call SetupDiEnumDeviceInterfaces to enumerate the device interfaces in the device information set and obtain information about the device interfaces;
Call SetupDiGetDeviceInterfaceDet ai l to obtain detailed information of the device interface. The information obtained is returned through the SP_DEVICE_INTERFACE_DETAIL_DATA structure. Since the size of the structure cannot be obtained in advance, it is necessary to call this function twice in succession. When calling the function for the second time, the interface details will be filled into the buffer whose size is determined by the return value of the first call. The "device path" can be obtained through the DevicePath member of the structure in the buffer.
2. Get the WinUSB interface handle of the device:
Call WinUsb_Initialize by passing the file handle that you created in Create device's file handle.
3. Query the device to obtain the USB descriptor:
Next, query the device to obtain USB-specific information, such as device speed, interface descriptors, associated endpoints, and its pipes. Call WinUsb_QueryDeviceInformation to request information from the device's device descriptor. Call WinUsb_QueryInterfaceSettings and pass the device's interface handle to obtain the corresponding interface descriptor. Call WinUsb_QueryPipe to obtain information about each interface and each endpoint. This step is not necessary because the endpoint direction and transfer characteristics are determined by the embedded device descriptor and are known.
4. Send a control transfer to the default endpoint:
This step is not required. Usually payloads are not sent via the default endpoint.
5. Send I/O request:
Send data to the bulk input and bulk output endpoints of the device, which can be used for read requests and write requests respectively. Call WinUsb_ReadPipe to read data from the bulk input endpoint of the device. Call WinUsb_WritePipe to write data to the device through the bulk output endpoint. After writing data to the output endpoint of the embedded device, the data can be read out on the PC side. Conversely, if data is written to the input endpoint of the embedded device on the PC side, the embedded device will generate a USB endpoint write event. How to capture this event is determined by the product hardware of the MCU manufacturer, and the corresponding interrupt information is generated for the interrupt service program to judge. Generally speaking, chip manufacturers will provide MCU's USB communication basic example program, and simple modifications and adaptations can be made on its basis.
6. Release the device handle
After completing all necessary calls to the device, releases the device's file handle and WinUSB interface handle. CloseHandle Releases the handle created by CreateFile.
WinUsb_Free Releases the WinUSB interface handle for the device returned by WinUsb_Initialize.
At this point, the USB code porting of the embedded device firmware and the writing of the PC application have been completed, and the communication method of the USB driver-free device can be realized.
|