Skip to main content
Visitor II
September 15, 2020
Solved

USB HID performance optimization

  • September 15, 2020
  • 2 replies
  • 2881 views

I'm planning to use a cheap STM32 board (either with a F103 or F401/11) as a joystick/rotary encoder controller for arcade games. I already have a working proof of concept using the old STM32duino cores (not HAL based), but I prefer to use the HAL environment to avoid more abstraction levels.

I used an existing example as a starting point (https://github.com/miniwinwm/BluePillDemo/tree/master/BluePillDemo_USB_HID), and I run into a problem trying to send data at high rates.

I'm using a Windows PC with mouserate.exe (http://tscherwitschke.de/old/mouseratechecker.html) to check effective rates. Windows can handle 1000Hz rates for mice.

First, I modified usbd_conf.h and changed

#define HID_FS_BINTERVAL   0x1 // was 0x0A

to increase the reported polling rate to 1000Hz

Then I put the following code in my main.c

int main(void)
{
 /* USER CODE BEGIN 1 */
 // in the mouse report byte 0 contains 3 button state bits
 // byte 1,2,3 is x, y, thumbwheel movement
 uint8_t mouse_report[5] = {0};
 /* USER CODE END 1 */
 /* MCU Configuration--------------------------------------------------------*/
 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
 HAL_Init();
 /* USER CODE BEGIN Init */
 /* USER CODE END Init */
 /* Configure the system clock */
 SystemClock_Config();
 /* USER CODE BEGIN SysInit */
 /* USER CODE END SysInit */
 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_USB_DEVICE_Init();
 /* USER CODE BEGIN 2 */
 /* USER CODE END 2 */
 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
 while (1)
 {
 /* USER CODE END WHILE */
 /* USER CODE BEGIN 3 */
 mouse_report[1] = 4;
 mouse_report[2] = 0;
 USBD_HID_SendReport(&hUsbDeviceFS, mouse_report, 5);
 HAL_Delay(2);
 mouse_report[1] = -4;
 mouse_report[2] = 0;
 USBD_HID_SendReport(&hUsbDeviceFS, mouse_report, 5);
 HAL_Delay(2);
 }
 /* USER CODE END 3 */
}

to create a "mouse jiggler" that moves the mouse back and forth by 4 units. It works well, and achieves an average of 333Hz refresh rate

If I reduce the delay to HAL_delay(1), though, the rate increases, but periodically the computer only receives a 10-15 moves left (or right), as if the Windows PC and the STM32 were out of sync and the PC misses a few USB_HID_SendReport(). Which, considering how USB polling works, it's probably what happens. If I remove the delays, then the mouse moves right for a while, then left for a while, and so forth, basically missing even more events

Is there a way to wait until the USB device is ready to be polled, instead of using a HAL_delay(), which by its nature risks always being problematic? Or another way to write code to be interrupt driven instead of using delays?

    This topic has been closed for replies.
    Best answer by TDK

    Check for "hhid->state == HID_IDLE" prior to sending. This is what is done inside USBD_HID_SendReport, before it returns USBD_OK regardless. The USB implementation in general seems way worse than the rest of HAL. Probably just doesn't get many eyes on it.

    uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len)
    {
     USBD_HID_HandleTypeDef *hhid = (USBD_HID_HandleTypeDef *)pdev->pClassData;
     
     if (pdev->dev_state == USBD_STATE_CONFIGURED)
     {
     if (hhid->state == HID_IDLE)
     {
     hhid->state = HID_BUSY;
     (void)USBD_LL_Transmit(pdev, HID_EPIN_ADDR, report, len);
     }
     }
     
     return (uint8_t)USBD_OK;
    }

    2 replies

    TDKAnswer
    Super User
    September 15, 2020

    Check for "hhid->state == HID_IDLE" prior to sending. This is what is done inside USBD_HID_SendReport, before it returns USBD_OK regardless. The USB implementation in general seems way worse than the rest of HAL. Probably just doesn't get many eyes on it.

    uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len)
    {
     USBD_HID_HandleTypeDef *hhid = (USBD_HID_HandleTypeDef *)pdev->pClassData;
     
     if (pdev->dev_state == USBD_STATE_CONFIGURED)
     {
     if (hhid->state == HID_IDLE)
     {
     hhid->state = HID_BUSY;
     (void)USBD_LL_Transmit(pdev, HID_EPIN_ADDR, report, len);
     }
     }
     
     return (uint8_t)USBD_OK;
    }

    fbarAuthor
    Visitor II
    September 16, 2020

    This was it, thanks! After this simple change, I can reliably hit ~500Hz on average with the puny STM32F103, quite impressive