Browse over 10,000 Electronics Projects

USB HID device development on the STM32 F042

USB HID device development on the STM32 F042

USB HID

The subprotocol that I’m interested in is the Human Interface Device (HID) protocol. This protocol was intended to support keyboards, mice and joysticks. Basically anything that you can attach that could work by exchanging small interrupt-driven data packets with the host.

The USB implementors forum have a website dedicated to the HID specification and it’s worth a quick look. In particular you should have a copy of the Device Class Definition for HID 1.11 PDF availble here and also the HID Descriptor Tool Windows utility available from the same page. We’ll have a look at that tool later on.

HID descriptors

I mentioned in the previous paragraph that HID devices exchange data with the host using small interrupt-driven data packets. The data that you send in these packets can be structured or free-form. Structured data is formatted in a way that the host understands. For example, there are defined ways of representing key presses and mouse movements and unstructured data is just generic buffers of bytes that only your driver understands.

Whether you choose structured or unstructured data you need to tell the host during the device enumeration stage how these reports are laid out. Each report has a number that identifies it and a structure that defines how it’s laid out in memory. That structure is called the HID report descriptor and it has a hierarchical structure. For example, here’s a structure that defines how a mouse will report movement to the host.

const uint8_t MouseReportDescriptor[50]={
  0x05, 0x01,     // USAGE_PAGE (Generic Desktop)
  0x09, 0x02,     // USAGE (Mouse)
  0xa1, 0x01,     // COLLECTION (Application)
  0x09, 0x01,     //   USAGE (Pointer)
  0xa1, 0x00,     //   COLLECTION (Physical)
  0x05, 0x09,     //     USAGE_PAGE (Button)
  0x19, 0x01,     //     USAGE_MINIMUM (Button 1)
  0x29, 0x03,     //     USAGE_MAXIMUM (Button 3)
  0x15, 0x00,     //     LOGICAL_MINIMUM (0)
  0x25, 0x01,     //     LOGICAL_MAXIMUM (1)
  0x95, 0x03,     //     REPORT_COUNT (3)
  0x75, 0x01,     //     REPORT_SIZE (1)
  0x81, 0x02,     //     INPUT (Data,Var,Abs)
  0x95, 0x01,     //     REPORT_COUNT (1)
  0x75, 0x05,     //     REPORT_SIZE (5)
  0x81, 0x03,     //     INPUT (Cnst,Var,Abs)
  0x05, 0x01,     //     USAGE_PAGE (Generic Desktop)
  0x09, 0x30,     //     USAGE (X)
  0x09, 0x31,     //     USAGE (Y)
  0x15, 0x81,     //     LOGICAL_MINIMUM (-127)
  0x25, 0x7f,     //     LOGICAL_MAXIMUM (127)
  0x75, 0x08,     //     REPORT_SIZE (8)
  0x95, 0x02,     //     REPORT_COUNT (2)
  0x81, 0x06,     //     INPUT (Data,Var,Rel)
  0xc0,           //   END_COLLECTION
  0xc0            // END_COLLECTION
};

Always define your constant data declarations as const so that gcc will place them in plentiful flash and not scarce SRAM. There is a small performance difference between SRAM and flash access but because there is just one linear address space the difference is not nearly as severe as, for example, accessing flash data on one of the small 8-bit AVR devices.

The terms in the comments on the right are defined by the USB standard and translate directly into the bytes that make up the descriptor. During device enumeration the host will ask for your report descriptors and it’s this that gets sent back as the reply. USB descriptors are typically hard-coded into devices.

Mouse (and keyboard) reports are structured. That is, the device is known to the host to be a keyboard or a mouse and that host is able to translate the report data into physical key presses or mouse movements. If a keyboard or mouse implements the special ‘boot’ report structure then the manufacturer can be confident that they will work during computer startup because BIOS manufacturers hardcode knowledge of the USB boot report structures and that’s why you can use your USB keyboard and mouse during the PC’s boot sequence. The above example implements the ‘boot’ report structure for mice.



Advertisement1


The device tells the host that it implements a ‘boot’ protocol as a flag in the bInterfaceSubClass field of the device’s interface descriptor and it must then conform to the ‘boot’ protocol reports.

To quickly decipher the bit and byte counts that make up the content of the report you have to look for a REPORT_COUNT entry that defines the number of bits in a report followed by a REPORT_SIZE entry that says how many of those bit vectors there are.

So without even knowing the full HID report definition we can look at the above structure and see that 3 bits are used to represent 3 mouse buttons and there’s a 5 bit section without any preceding usage so we might assume that it’s for padding out the 3 bit button report to a byte boundary. Following that we can see two 8 bit reports with X and Y usage that clearly define the mouse position. The low limits for LOGICAL_MINIMUM and LOGICAL_MAXIMUM make me think that the X and Y data are relative to the previous mouse location.

Here’s a similar structure for keyboards.

const uint8_t KeyboardReportDescriptor[63] = {
  0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  0x09, 0x06,                    // USAGE (Keyboard)
  0xa1, 0x01,                    // COLLECTION (Application)
  0x75, 0x01,                    //   REPORT_SIZE (1)
  0x95, 0x08,                    //   REPORT_COUNT (8)
  0x05, 0x07,                    //   USAGE_PAGE (Keyboard)(Key Codes)
  0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)(224)
  0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)(231)
  0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  0x81, 0x02,                    //   INPUT (Data,Var,Abs) ; Modifier byte
  0x95, 0x01,                    //   REPORT_COUNT (1)
  0x75, 0x08,                    //   REPORT_SIZE (8)
  0x81, 0x03,                    //   INPUT (Cnst,Var,Abs) ; Reserved byte
  0x95, 0x05,                    //   REPORT_COUNT (5)
  0x75, 0x01,                    //   REPORT_SIZE (1)
  0x05, 0x08,                    //   USAGE_PAGE (LEDs)
  0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
  0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
  0x91, 0x02,                    //   OUTPUT (Data,Var,Abs) ; LED report
  0x95, 0x01,                    //   REPORT_COUNT (1)
  0x75, 0x03,                    //   REPORT_SIZE (3)
  0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs) ; LED report padding
  0x95, 0x06,                    //   REPORT_COUNT (6)
  0x75, 0x08,                    //   REPORT_SIZE (8)
  0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
  0x05, 0x07,                    //   USAGE_PAGE (Keyboard)(Key Codes)
  0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))(0)
  0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)(101)
  0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
  0xc0                           // END_COLLECTION
};

My guess is that much of the USB device report descriptor implementation that goes on in real life is just a copy-and-paste from previous implementions with little tweaks here and there but you can create your own report structure from scratch with a little help from the HID Descriptor Tool utility for Windows that I mentioned earlier on.

The last modified times on the files in the HID tool folder indicate that it was last modified in 1997, nearly 20 years ago. Happily it still runs on Windows 10 even if it crashes sometimes and behaves in strange ways at others.

If you need to create your own HID report structure then you’ll probably need this tool. Just save your work often and keep backups!

Pages: 1 2 3 4 5 6 7 8 9

 


Top