cancel
Showing results for 
Search instead for 
Did you mean: 

USB HID performance optimization

fbar
Senior

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?

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

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;
}

If you feel a post has answered your question, please click "Accept as Solution".

View solution in original post

2 REPLIES 2
TDK
Guru

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;
}

If you feel a post has answered your question, please click "Accept as Solution".
fbar
Senior

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