USB HID Descriptor for wireless mouse help

Hello, and thanks for reading. We are having some problems with our device, and hope someone here has the missing piece of wisdom we need. Our device implements HID over GATT, and pretends to be a mouse. We are using SDK 2.5.0 and an NRF52840 dongle.

Because of the needs of our project, we need to use absolute positioning, instead of relative positioning like a lot of mice. I therefore wrote the following USB descriptor:

static uint8_t windows_report_map[] = {
    0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
    0x09, 0x02, /* Usage (Mouse) */
    0xA1, 0x01, /* Collection (Application) */
    0x85, 0x01, /* Report Id (1) */
    0x09, 0x01, /* Usage (Pointer) */
    0xA1, 0x00, /* Collection (Physical) */
    0x05, 0x09, /* Usage Page (Button) */
    0x19, 0x01, /* Usage Minimum (0x01) */
    0x29, 0x03, /* Usage Maximum (0x03) */
    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,No Wrap,Linear,...) */
    0x95, 0x01, /* Report Count (1) */
    0x75, 0x05, /* Report Size (5) */
    0x81, 0x03, /* Input (Const,Var,Abs,No Wrap,Linear,...) */
    0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
    0x09, 0x30, /* Usage (X) */
    0x09, 0x31, /* Usage (Y) */
    0x09, 0x38, /* Usage (Wheel) */
    0x15, 0x81, /* Logical Minimum (-127) */
    0x25, 0x7F, /* Logical Maximum (127) */
    0x75, 0x08, /* Report Size (8) */
    0x95, 0x03, /* Report Count (3) */
    0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,...) */
    0xC0,       /* End Collection */
    0xC0        /* End Collection */
};

Note line 27, which specifies absolute positioning.

This code works well, but when your logical max is only 127, the mouse movement is rather coarse... This makes sense, in that you have a grid of only 127 x 127.

To fix this, I want to change the report descriptor to allow me 16 bits for each of X and Y, and then I can raise the logical min and max.

This is my revised USB report descriptor:

static uint8_t windows_report_map[] = {
    0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
    0x09, 0x02, /* Usage (Mouse) */
    0xA1, 0x01, /* Collection (Application) */
    0x85, 0x01, /* Report Id (1) */
    0x09, 0x01, /* Usage (Pointer) */
    0xA1, 0x00, /* Collection (Physical) */
    0x05, 0x09, /* Usage Page (Button) */
    0x19, 0x01, /* Usage Minimum (0x01) */
    0x29, 0x03, /* Usage Maximum (0x03) */
    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,No Wrap,Linear,...) */
    0x95, 0x01, /* Report Count (1) */
    0x75, 0x05, /* Report Size (5) */
    0x81, 0x03, /* Input (Const,Var,Abs,No Wrap,Linear,...) */
    0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */
    0x09, 0x30, /* Usage (X) */
    0x09, 0x31, /* Usage (Y) */
    0x09, 0x38, /* Usage (Wheel) */
    0x16, 0x00, 0x80, /* Logical Minimum (-32768) */ //81???
    0x26, 0xFF, 0x7F, /* Logical Maximum (32767) */
    0x75, 0x10, /* Report Size (16) */
    0x95, 0x03, /* Report Count (3) */
    0x81, 0x02, /* Input (Data,Var,Abs,No Wrap,Linear,...) */
    0xC0,       /* End Collection */
    0xC0        /* End Collection */
};

Unfortunately, theres clearly some detail that has escaped me. I wrote a set of nested FOR loops to test this new report by just sending every combination of X and Y spanning 0,0 to 255,255 (In the interest of time, I didnt want to try going all the way to 0x7FFF).

This is the test code:

for (int16_t i = 0; i < 255 ; i++)
		{
			for (int16_t j = 0 ; j < 255 ; j++)
			{
				HIDS_Send_Directly(i,j,0,0,0);
				static bool LED_Blink = false;
				set_ADS_LED(LED_Blink);
				LED_Blink = !LED_Blink;
				k_msleep(20);

			}
		}
		
		
		

And

void HIDS_Send_Directly (int16_t x_movement, int16_t y_movement, int16_t wheel_movement, bool right_mousebutton, bool left_mousebutton)
{
	//prepare buttons
	uint8_t LMB = left_mousebutton;
	uint8_t RMB = right_mousebutton;
	RMB = RMB << 1;

	Current_X_Position = x_movement;
	Current_Y_Position = y_movement;


	int8_t HID_Report[7] = 
	{
		LMB | RMB,
		(x_movement & 0xFF00) >> 8, x_movement & 0x00FF,
		(y_movement & 0xFF00) >> 8, y_movement & 0x00FF,
		0,0
	};

	NUS_Send("Position %i %i \n",x_movement,y_movement);
		
	bt_gatt_notify(NULL, &hog_svc.attrs[5],HID_Report,sizeof(HID_Report));

}

When X and Y are in the range of zero to +127, it behaves exactly as it did before, with the mouse reaching the far edge of the screen at 127. When we exceed those values, it returns to the 0 position and doesnt move until the variable overflows and we go back into the valid range. Its as though my code change had no effect other than allowing me to send an extra byte in the X, Y and Wheel fields.

I added to my prj.conf the lines

#usb
CONFIG_USB_DEVICE_PID=0x1234  
CONFIG_USB_DEVICE_VID=0x4321

Thinking maybe it was using some cached values, I checked in powershell and can confirm the new PID and VID, but still no changes to the behaviour.

I am using windows 10, but have also tested and had the same issue with windows 11.

Is there some detail that's escaping me about either the USB descriptor, or just using the mouse in absolute mode in windows with more than a signed 8 bit variable?

Thanks for your time!

Parents
  • After two days of hair pulling on this I finally found the problem, I was sending the bytes in the wrong order (little-endian versus big-endian) for the X and Y.

    The correct code should be:

    int8_t HID_Report[7] =
            {
                LMB | RMB,
                x_movement & 0x00FF, (x_movement & 0xFF00) >> 8,
                y_movement & 0x00FF, (y_movement & 0xFF00) >> 8,
                0,0
            };

    Ill leave this up in case anyone else is working on something similar and finds the usb report a good starting point.

    Thanks for looking!

Reply
  • After two days of hair pulling on this I finally found the problem, I was sending the bytes in the wrong order (little-endian versus big-endian) for the X and Y.

    The correct code should be:

    int8_t HID_Report[7] =
            {
                LMB | RMB,
                x_movement & 0x00FF, (x_movement & 0xFF00) >> 8,
                y_movement & 0x00FF, (y_movement & 0xFF00) >> 8,
                0,0
            };

    Ill leave this up in case anyone else is working on something similar and finds the usb report a good starting point.

    Thanks for looking!

Children
No Data
Related