USB HID feature report in descriptor

Not open for further replies.



I'm using a custom HID report descriptor with input, output and feature reports. While input & output works perfectly well, I'm not sure how to use the feature report... I've not seen any example which use a feature report.
Is a feature report linked to an "transmit and receive" endpoint ?
And how to use a feature report in the code ? I tried with usb_tx and usb_rx (using hidapi library host-side) but nothing works. I can send data from the Teensy (16 bytes with usb_tx are reported sent) to the host but the read fail on host side.

Here is my descriptor: (it works with input or output report)

static uint8_t test_report_desc[] = {
0x06, 0xAC, 0xFF, // USAGE_PAGE (0xFFAC)
0x0A, 0x00, 0x02, // USAGE (0x0200)
0xa1, 0x01, // COLLECTION (Application)
0xa1, 0x01, // COLLECTION (Application)
//0x85, 0x01, // REPORT_ID (1)
0x09, 0x02, // USAGE (Vendor Usage 2)
0x75, 0x20, // REPORT_SIZE (32)
0x95, 0x04, // REPORT_COUNT (4)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x27, 0x80, 0x96, 0x98, 0x00, // LOGICAL_MAXIMUM (10000000)
//0x81, 0x02, // Input
0xB1, 0x02, // FEATURE


Any help is welcome !
Thanks ! :)

I took a look in usb_dev.c, but the USB protocol is not very simple... :p
And I'm working on Mac, Mountain Lion, with Teensyduino 1.13 or 1.14 maybe...
Up !

Any idea ? Maybe it's just not implemented in software ?
I tried without success with latest version of Teensyduino (1.15).
Feature reports are only sent using USB control transfers on endpoint zero, not the normal endpoints.

Teensy3's USB code is not meant to be a fully featured "USB stack" for easily developing any type of USB device. A traditional "stack" would have ways to register callback functions or some similar mechanism where you could add your own code for responding to any particular control requests, without editing the USB stack itself. It would probably also come with nice examples demonstrating how to do this. Teensy3 has neither (but it does have some of this stack-style stuff for the non-zero endpoints). For a control transfer on endpoint zero, you need to edit the USB code, and there's little in the way of already-written examples to guide you in doing so.

The function you need to edit is usb_setup() in usb_dev.c. There's not much already there to base your code upon, but look for this bit:

// TODO: this does not work... why?
          case 0x0921: // HID SET_REPORT
          case 0x0A21: // HID SET_IDLE
          // case 0xC940:

If that "this does not work" comment alone doesn't scare you away, well, at least you've got the right "can do" attitude....

From your report descriptor, it looks like you intend to transmit 16 bytes of data to your Mac. The basic idea is you need to add code in that function which initializes "data" with a pointer to the 16 bytes, and "datalen" with the number 16. Of course, you need to get the 16 bit case (which is bmRequestType and bRequest) correct, and maybe other fine details matter too. Remember, this isn't meant to be a fully featured USB stack with APIs (and lots of associated overhead) to make custom USB messages easy. You'll have to resolve any small details. A USB protocol analyzer is probably the easiest way, but maybe a software-based USB packet capture can suffice if you don't have a true hardware analyzer.

Anyway, you can see at the end of usb_setup(), there's a bunch of code which takes the "data" and "datalen" variables you initialized and actually sets up the endpoint zero DMA descriptors to transmit your buffer. It also sets up some static variables with the info, which is used to handle responses longer than two 64 byte packets, because the hardware only allows 2 DMA transfers to be queued. Later when the USB host sends the actual IN tokens, your data gets transmitted, and then usb_control() runs "case 0x09: // IN transaction completed to host". You shouldn't need to mess with that code.

But of course, the key point is you must have the 16 bytes ready in a buffer at all times, so your code in usb_setup() can put the buffer's address into "data" and the length into "datalen". Look at the 0x0680 case for an example, or at least an example that sends const data. You'll probably need to build user-side code with temporarily disables interrupts while you copy the 16 bytes into that buffer. You might also need to add some sort of flag check if usb_setup() has responded to the setup token, but the IN token hasn't completed, so the DMA is about to actually move the data at any moment. If the flag is set, you'll probably spin waiting for the IN token handling to clear the flag, indicating it's safe to write to the buffer (so maybe you will need to mess slightly with the IN token handler stuff?) Then again, if you don't care about the possibility of timing issues where the DMA reads the data while you're changing it (possibly causing a mix or old and new data to be sent), then this might not be a concern.

Of course, just get it working first, but know of this possible race condition if you don't design the timing of writes to that buffer with awareness of when the DMA can read it. Remember, this USB code is not meant to be a fully featured USB stack that's easy to add this sort of customization at the protocol level. It's really only meant to make configuring collections of already-designed interfaces. Customizing within an interface, and especially adding endpoint zero control messages is tricky business, which usually requires at least a way to monitor USB packets, if not a proper hardware analyzer like a "Beagle" from Total Phase.

Also, you'll notice there's a lot of commented out serial printing stuff. I would highly recommend you connect a TTL-level USB-serial converter to monitor pin 1, and of course add Serial1.begin(115200) at the beginning of setup(). Then you can uncomment some of this serial stuff, and add your own, which will really help. It's especially helpful in combination with a real USB analyzer, where you run the analyzer and a terminal emulator on another computer to capture and view all this stuff while your Mac tries to communicate with the Teensy3 running your modified code.

Adding control transfers for features reports is definitely is not an easy task, but hopefully this message helps?
Last edited:
Thanks very much, that really help me ! :)

I'll try to get it working, then. I don't have any hardware USB analyzer, but I'll try to find a nice software analyzer.

There is any delay limits in usb_setup() ? I tried in the past to uncomment some of the serial stuff but my device was then not working properly. Maybe I uncommented too much lines.

Anyway, I'll try and let you know ! Thanks !
Feature reports are only sent using USB control transfers on endpoint zero, not the normal endpoints.

The function you need to edit is usb_setup() in usb_dev.c. There's not much already there to base your code upon, but look for this bit:

Paul, how to determine what data contains setup.wRequestAndType, when the host requests a feature report? Serial.print does not work in this part of the code.
I can add any materials that are needed for this, including data collected through USBlyzer
Not open for further replies.