Close

RX-Modulus USB HID

A project log for RX-Modulus (Completely Modular Mouse)

Finally a mouse that can be freely changed, fixed and upgraded suit the user.

benwbenw 10/03/2020 at 12:420 Comments

This was probably the most mind bending part of the project as there are many ways to handle USB HID incorporation. But for this project it's all about taking baby steps.

Stage 1: Get the Example Mouse HID working.

This was probably the easiest part as the STM32CubeMX automatically generates a USB mouse HID type when select "Human Interface Device Class" in the Class For FS IP in the Middleware-USB_Device tab. All we needed to do was add "RX-Modulus HID" to the 'PRODUCT_STRING(Product Identifier). Once the code is generated and built in the Cube-IDE environment we just needed to plug our mouse into the USB port of our PC and deploy our code. 

Looking on the control panel under "Bluetooth & other devices" we can see that the computer happy recognizes our project as a mouse interface. Now we could have stopped here but the project would just be a common mouse if no special keys or functions. To go further we moved to stage 2.

Stage 2: USB HID Collection

This is where it gets tricky but luckily I found these great websites that really helped:

https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/

https://notes.iopush.net/custom-usb-hid-device-descriptor-media-keyboard/

There are commonly three methods of combining USB devices together namely:

  1. Multiple TLC(Top-Level Collection)
    1. Collections can be used to organize your data, for example, a keyboard may have a built-in touchpad, then the data for the keyboard should be kept in one application collection while the touchpad data is kept in another. We can assign an “Report ID” to each collection, which I will show you later.
  2. Composite Device
    1. Uses the same interfacing framework that all HID devices use, which is based in exchanging data with the host through “reports” via  “endpoints”. Each device added will require it's own start and end point.
  3. Custom HID
    1. Basically a peripheral that does not fit any of the common device definitions that conform the HID standard (Mouse, Keyboard, Gamepad, Webcam, etc) but still use the same interfacing framework that all HID devices use, which is based in exchanging data with the host through “reports” via  “endpoints”. With custom HID devices the operating system doesn’t know what their function is, but it does knows how to speak with them since they comply with the HID specification. They are normally highly-specific hardware devices that require special software and vendor-provided tools to interact with them.

For this project we decided to use a TLC HID type to achieve a good starting point for our project.

For this test we used the following descriptor:

__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = {
//50 bytes
		0x05, 0x01, /* Usage Page (Generic Desktop)             */
	0x09, 0x02, /* Usage (Mouse)                            */
	0xA1, 0x01, /* Collection (Application)                 */
	0x09, 0x01, /*  Usage (Pointer)                         */
	0xA1, 0x00, /*  Collection (Physical)                   */
	0x85, 0x01,  /*   Report ID  */
	0x05, 0x09, /*      Usage Page (Buttons)                */
	0x19, 0x01, /*      Usage Minimum (01)                  */
	0x29, 0x03, /*      Usage Maximum (03)                  */
	0x15, 0x00, /*      Logical Minimum (0)                 */
	0x25, 0x01, /*      Logical Maximum (0)                 */
	0x95, 0x03, /*      Report Count (3)                    */
	0x75, 0x01, /*      Report Size (1)                     */
	0x81, 0x02, /*      Input (Data, Variable, Absolute)    */
	0x95, 0x01, /*      Report Count (1)                    */
	0x75, 0x05, /*      Report Size (5)                     */
	0x81, 0x01, /*      Input (Constant)    ;5 bit padding  */
	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, Variable, Relative)    */
	0xC0, 0xC0,/* End Collection,End Collection            */

	//78 bytes
	0x09, 0x06,        // Usage (Keyboard)
	0xA1, 0x01,        // Collection (Application)
	0x85, 0x02,        //   Report ID (2)
	0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
	0x75, 0x01,        //   Report Size (1)
	0x95, 0x08,        //   Report Count (8)
	0x19, 0xE0,        //   Usage Minimum (0xE0)
	0x29, 0xE7,        //   Usage Maximum (0xE7)
	0x15, 0x00,        //   Logical Minimum (0)
	0x25, 0x01,        //   Logical Maximum (1)
	0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
	0x95, 0x03,        //   Report Count (3)
	0x75, 0x08,        //   Report Size (8)
	0x15, 0x00,        //   Logical Minimum (0)
	0x25, 0x64,        //   Logical Maximum (100)
	0x05, 0x07,        //   Usage Page (Kbrd/Keypad)
	0x19, 0x00,        //   Usage Minimum (0x00)
	0x29, 0x65,        //   Usage Maximum (0x65)
	0x81, 0x00,        //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
	0xC0,              // End Collection
	0x05, 0x0C,        // Usage Page (Consumer)
	0x09, 0x01,        // Usage (Consumer Control)
	0xA1, 0x01,        // Collection (Application)
	0x85, 0x03,        //   Report ID (3)
	0x05, 0x0C,        //   Usage Page (Consumer)
	0x15, 0x00,        //   Logical Minimum (0)
	0x25, 0x01,        //   Logical Maximum (1)
	0x75, 0x01,        //   Report Size (1)
	0x95, 0x08,        //   Report Count (8)
	0x09, 0xB5,        //   Usage (Scan Next Track)
	0x09, 0xB6,        //   Usage (Scan Previous Track)
	0x09, 0xB7,        //   Usage (Stop)
	0x09, 0xB8,        //   Usage (Eject)
	0x09, 0xCD,        //   Usage (Play/Pause)
	0x09, 0xE2,        //   Usage (Mute)
	0x09, 0xE9,        //   Usage (Volume Increment)
	0x09, 0xEA,        //   Usage (Volume Decrement)
	0x81, 0x02,        //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
	0xC0,
};

This descriptor basically adds the following HID's stacks with a report number to help the host know there are multiple devices. (Report ID 1) Mouse, (Report ID 2) Keyboard, (Report ID 3) Keyboard media keys. As we have 128 bytes of information in our new collection its very important the "HID_MOUSE_REPORT_DESC_SIZE" to 128 from the 50 bytes the standard mouse HID used.  

#define HID_MOUSE_REPORT_DESC_SIZE                 128U // Was 50

This tells the host computer how much data its expecting to receive.

We just need to compile, build and deploy the code. Once running we once again check the control panel.

Success the computer recognizes our USB HID collection. At the moment we are using three device HIDs but in the not so distance future we will be incorporating more for the up and coming Joystick module. 

Now sending useful data to the host computer is actually very easy, especially if you use 'struct' to organize the data. Below are the 'struct' for each HID collection used. You will notice the first line on each struct is the 'id' used. This matches the descriptor table.

// HID Mouse
          struct mouseHID_t mouseHID;
	  mouseHID.id = 1;
	  mouseHID.buttons = 0;
	  mouseHID.x = 0;
	  mouseHID.y = 0;
	  mouseHID.wheel = 0;

// HID Keyboard
	       struct keyboardHID_t {
	       uint8_t id;
	       uint8_t modifiers;
	       uint8_t key1;
	       uint8_t key2;
	       uint8_t key3;
	   };
	   struct keyboardHID_t keyboardHID;
	   keyboardHID.id = 2;
	   keyboardHID.modifiers = 0;
	   keyboardHID.key1 = 0;
	   keyboardHID.key2 = 0;
	   keyboardHID.key3 = 0;
	   
// HID Media
	   struct mediaHID_t {
	     uint8_t id;
	     uint8_t keys;
	   };
	   struct mediaHID_t mediaHID;
	   mediaHID.id = 3;
	   mediaHID.keys = 0;

 As an example if you wanted the mouse to move is the X axis by 10 units you just need to write the following in the main loop:

// Send HID report
    mouseHID.x = 10;
    USBD_HID_SendReport(&hUsbDeviceFS, &mouseHID, sizeof(struct 
    mouseHID_t));
    HAL_Delay(1000);

 Each compiled, built and deployed the mouse cursers will move right every 1 second.

This is a good summery of what has been done to achieve a working HID interface for this project. The real code is currently in development with all the code functions working as expected from a mouse/keyboard hybrid. We are constantly writing more functions to increase the feature set of our project. More code will be available after the contest finals deadline. 

Discussions