V-USB: The HIDden parts…


Hello again,
Its been a while. Been a bit busy with work and life generally, but I’m sure you know the feeling. Recently, I talked about getting started with V-USB and Atmel Studio 7. In that post, I highlighted a strange occurrence I’d observed and a suitable workaround. That said, that was just me testing the waters with V-USB. I hadn’t really worked with it much after that, until earlier on today.
I wanted to build a simple HID device which could be used as a custom input gizmo i.e it has four buttons, and every time I pressed a button, it would emit a report specifying which button was pressed. On the host side, an application would constantly poll the device and react as needed. Not exactly rocket science I admit, but still somewhat complex since I’d never done it before. This post tries to explain why.
First off, I had to specify (in code) that the device was a HID device. This is typically done by creating/modifying the device descriptor appropriately. Luckily, V-USB has an uber-helpful note in usbconfig.h that explains how you can do that by editing three simple #defines, so I took that route. There was no need for me to alter the device descriptor itself. Basically, the editions I made stated that: the device class would be specified by the interface descriptor, and the interface belongs to the HID class (interface class #define’d as 3). No subclass or protocol were needed (those were #define’d as 0).
Next, I had to specify that I wanted an interrupt endpoint (from the USB spec, every HID device must have at least one endpoint aside from endpoint 0, which is the control endpoint which every type of device has), and this was done by #define’ing USB_CFG_HAVE_INTRIN_ENDPOINT to 1 in usbconfig.h. By so doing, the device will have Control Endpoint 0, and an interrupt endpoint 1.
Next, I needed to specify the report structure. Every HID device requires a report descriptor which tells the host computer how the data it is sending to it is structured. Report descriptors look rather complicated, and I really haven’t found any resource telling me exactly/linearly how to go about writing one. That said, there are tons of examples on the internet, and so I will share the one I ended up using below:

Capture
I effectively copied the report descriptor structure used by the LUFA Generic HID example, because it was as close to what I wanted as I could get (easily), then used the HID Descriptor Tool (available from usb.org) to actually construct the report, then exported the report as a .h file and copied its contents as needed.
*wipes forehead*
Basically, this descriptor tells the computer that the data sent by the device is meant to be translated by the vendor / application, and that INPUT reports (device-to-host packets) will contain a maximum of 8 bytes, and that OUTPUT reports (host-to-device packets) would also contain a maximum of 8 bytes. I personally recall seeing somewhere that for USB 1.1 devices, such reports should contain a maximum of 8 bytes, but for the life of me I can’t seem to remember where I did or if that’s even correct, so I simply decided to err on the side of caution. That said, this person uses a 32-byte report to receive data from the host (i.e OUTPUT reports are 32 bytes) and an 8-byte report to send data to the host (i.e INPUT reports are 8 bytes) so I guess I’m missing something. According to the V-USB API, you’ll need to declare a char array called usbHidReportDescriptor containing your descriptor bytes, and the array must be stored in Flash i.e declared as PROGMEM. Also, in the usbconfig.h file, you must set the USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH #define to the number of bytes in the descriptor. For the descriptor provided above, the length is 34 (I simply counted the bytes one by one), so the #define became:
#define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH 34
If you want to adjust the size of the INPUT or OUTPUT reports, you can edit the lines tagged with “REPORT COUNT” above, and adjust the 0x08s there to reflect whatever value you choose. Proceed at your own risk though.
Once that was done, I believed I was ready to rock. The V-USB API states that you can send data over the default interrupt endpoint using the usbSetInterrupt() function, and receive data meant for any other endpoint asides from endpoint 0 (in other words, all non-control endpoint data) using the usbFunctionWriteOut() function, although this would require you to #define USB_CFG_IMPLEMENT_FN_WRITEOUT to 1 in usbconfig.h, which I did. For a simple test, I wrote code that would toggle the state of an LED once an OUTPUT report was received. For the host side, I intended to use Signal11’s HIDAPI library, which is a cross-platform library for communicating with USB HID devices with a really neat API. I had a Linux box on hand and decided to use that. Sorry Windows, although it is trivial (*swallows noisily*) to port this code across.
HIDAPI on Linux has two backends – LibUSB and HIDRAW, and the exact difference between the two is covered in the HIDAPI documentation. I didn’t want to use LibUSB mostly because HIDRAW was already enabled on my Linux box, and also because I’d never used HIDRAW before. So I knocked up a quick example (needed to install libudev-dev though) and compiled that, then ran it. And…nothing.
The LED was dead.
I spent hours going over everything again and again. The device enumerated correctly, the descriptor was correct, the code compiled with no errors…but it just didn’t work.
So I did what anyone else would do, and surfed the net to see if I could find examples. Eventually I stumbled upon this page, which had firmware code using V-USB’s HID class and communicating with HIDAPI, so which was pretty much what I was trying to do.
And that was when I noticed something strange.
Whenever that person wanted to send data to the host, he would use usbSetInterrupt(). No surprise there – that was what the V-USB API docs said to do. However, when receiving data from the host, there was no mention of usbFunctionWriteOut(). This was decidedly strange, because the docs were clear on the role of usbFunctionWriteOut(). However, for some reason, he had implemented usbFunctionWrite() and was doing something in usbFunctionSetup(). Taking a closer look, I came up with a hypothesis:
Apparently, HIDAPI does not seem to use the interrupt endpoint when sending data to the device. This simply means that the usbFunctionWriteOut() method was never getting called. Rather, data was being transferred via a USB class request (USB_HID_SET_REPORT), making it the coder’s responsibility to implement usbFunctionWriteOut() to properly receive the data from the host.
So, I modified my usbFunctionSetup() to look like this:
usbRequest_t* rq = (void*)data;
    
     if ((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS) {    /* HID class request */
       
        if(rq->bRequest == USBRQ_HID_SET_REPORT){
            return USB_NO_MSG;  /* use usbFunctionWrite() to receive data from host */
        }
     }
     return 0;
Its a bit different from his version, but I wanted to keep things simple and short. Essentially, this code says: if you receive a class request and the request is the (standard) USB_HID_SET_REPORT request, then defer processing to the usbFunctionWrite() method. I also had to #define USB_CFG_IMPLEMENT_FN_WRITE to 1, then actually implement the method (essentially, the method body toggled the LED). Compiling and deploying, I ran my test code again, and it actually worked. I later modified the code to use usbSetInterrupt() to send data to the host, and that also worked. I also compiled the same code on Windows and got the exact same behavior, so it seems to be something within HIDAPI and not a HIDRAW raw thing specifically. Of course, it could be a problem from my descriptor as well, because I’m not really sure I got it correctly.
To definitively determine if this is a HIDAPI thing, I could use LibUsbDotNet or similar to explicitly write to Endpoint 1 and see what happens. As it is, I’m not sure how HIDAPI determines how to send data (particularly which endpoint it uses), but with LibUsb you can specify the particular endpoint to be used. If I ever get round to it, I’ll edit this post to reflect my findings.
TL;DR
If you want to implement a HID device using the V-USB stack from Obdev.at, you need to do the following:
1) Edit the usbconfig.h file to reflect the device’s class as HID – this is most easily done by deferring the class to the interface and specifying the interface class as 3, with the subclass and protocol being set to 0
Capture1
2) Edit the usbconfig.h file to specify that the device is to have at least one interrupt besides the default control endpoint
Capture2
3) Obtain/derive a suitable report descriptor reflecting the data flow and structure for your device. The descriptor provided above is for a device that sends and receives data in 8-byte sized chunks. You may edit the descriptor to suit your own purposes. At your own risk, of course.
4) Count the number of bytes in the descriptor, then specify the size in the usbconfig.h file via the USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH #define:
Capture3
5) Specify that the usbFunctionWrite() function should be used to receive data sent from the host (presumably necessary only when you’re using HIDAPI or any other driver lib that doesn’t let you specify which endpoint to be used when sending data to the device) [TOP PHOTO]. If you can specify the endpoint to be used  (e.g like with libUSB/LibUSBDotNet), specify that the usbFunctionWriteOut() function is to be used to receive data sent from the host [BOTTOM PHOTO] .
Capture4
Capture5
6) Implement the usbFunctionWrite() or usbFunctionWriteOut() methods depending on which you specified in (5). Luckily they have similar signatures.
7) If you’re using usbFunctionWrite(), ensure that you have the following code in your usbFunctionSetup() method (which must be created in your code even if you’re not using control transfers). This is to ensure that you can receive data from the host:
Capture6
You can edit the code segment as necessary.
8) Use the usbSetInterrupt() function to send data to the host when needed.
9) Before deploying, ensure that the correct VID/PID, Serial Number, Manufacturer name, etc are specified in usbconfig.h
10) Walk the plank.
I hope this info helps someone who might be facing the same issue right now or at some point in the future. Not even sure if I’m right to call it an issue…maybe a quirk?
Ps: Did you ever the movie “Crouching Tiger, HIDden dragon”?
Pps: I’m sorry – I couldn’t help myself

Comments

Popular posts from this blog

Bitbanging SPI on the Raspberry Pi (via spi-gpio)

Enabling SPI1 on the Raspberry Pi B+/Zero/2/3

Getting Started with Logic Analyzers and Pulseview