cancel
Showing results for 
Search instead for 
Did you mean: 

Implementing a USB HID keyboard device from a USB HID mouse application

FBL
ST Employee

Summary

This guide helps you migrate an STM32 application from a USB HID mouse to a USB HID keyboard using the STM32 legacy library. Below are the steps to achieve this.

1. Project setup

You can start by obtaining the project from the mouse device project using classic middleware. The direct link to GitHub for the project is: https://github.com/stm32-hotspot/CKB-STM32-HID-KEYBOARD-C0

Here are the steps:

  1. Start a new project:
    - Open STM32CubeMX.
    - Click [File → New → STM32 Project].
    - Use the [Board Selector] tab and select your board (we use [NUCLEO-C071RB] as an example).
  2. Configure PC13 as user button in EXTI mode.
  3. USB configuration:
    - Enable USB global interrupt
    - Full Speed, using internal PHY and keep other default settings.
    - GPIO Settings: PA11 and PA12 (remapped PA9 and PA10 respectively) as DM and DP.
  4. Copy the necessary library and user files into the project folder from the mouse device project.

2. Middleware modification

2.1 Update usbd_hid.c

Modify the USBD_HID_CfgDesc in usbd_hid.c to configure it for a keyboard.

Add a macro conditional definition to handle both mouse and keyboard configurations:

__ALIGN_BEGIN static uint8_t USBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
0x09, /* bLength: Configuration Descriptor size */
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USB_HID_CONFIG_DESC_SIZ, /* wTotalLength: Bytes returned */
0x00,
0x01, /* bNumInterfaces: 1 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
#if (USBD_SELF_POWERED == 1U)
0xE0, /* bmAttributes: Bus Powered according to user configuration */
#else
0xA0, /* bmAttributes: Bus Powered according to user configuration */
#endif /* USBD_SELF_POWERED */
USBD_MAX_POWER, /* MaxPower (mA) */

/************** Descriptor of Joystick Mouse interface ****************/
/* 09 */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface descriptor type */
0x00, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x01, /* bNumEndpoints */
0x03, /* bInterfaceClass: HID */
0x01, /* bInterfaceSubClass : 1=BOOT, 0=no boot */
#ifdef keyboard 
0x01, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
#elif mouse
0x02, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
#endif
0, /* iInterface: Index of string descriptor */
/******************** Descriptor of Joystick Mouse HID ********************/
/* 18 */
0x09, /* bLength: HID Descriptor size */
HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */
0x11, /* bcdHID: HID Class Spec release number */
0x01,
0x00, /* bCountryCode: Hardware target country */
0x01, /* bNumDescriptors: Number of HID class descriptors to follow */
0x22, /* bDescriptorType */
#ifdef keyboard
HID_KEYBOARD_REPORT_DESC_SIZE, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
#elif mouse
HID_MOUSE_REPORT_DESC_SIZE, /* wItemLength: Total length of Report descriptor */
#endif
0x00,
/******************** Descriptor of Mouse endpoint ********************/
/* 27 */
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType:*/
HID_EPIN_ADDR, /* bEndpointAddress: Endpoint Address (IN) */
0x03, /* bmAttributes: Interrupt endpoint */
HID_EPIN_SIZE, /* wMaxPacketSize: 4 Bytes max */
0x00,
HID_FS_BINTERVAL, /* bInterval: Polling Interval */
/* 34 */
};
#endif

2.2 Define report descriptor size for keyboard in usbd_hid.h

Add the following definitions in usbd_hid.h:

#define HID_MOUSE_REPORT_DESC_SIZE 74U
#define HID_KEYBOARD_REPORT_DESC_SIZE 63U

To complete the implementation of a USB HID keyboard device starting from a USB HID mouse application, follow these steps to update the user application code:

3. User application code modifications

3.1 Define keyboard data structure and variables

Add the following code in your user application to define the necessary structures and variables for the keyboard:

#ifdef keyboard
extern USBD_HandleTypeDef hUsbDeviceFS;
typedef struct
{
uint8_t MODIFIER;
uint8_t RESERVED;
uint8_t KEYCODE1;
uint8_t KEYCODE2;
uint8_t KEYCODE3;
uint8_t KEYCODE4;
uint8_t KEYCODE5;
uint8_t KEYCODE6;
} keyboardHID;

keyboardHID keyboardhid = {0, 0, 0, 0, 0, 0, 0, 0};
extern uint8_t keyboardpressed;
uint8_t hello_sequence[5] = {0x0B, 0x08, 0x0F, 0x0F, 0x12}; // Keycodes for 'H', 'E', 'L', 'L', 'O'
#endif

3.2 Update usb_device.c

In usb_device.c, after defining extern PCD_HandleTypeDef hpcd_USB_DRD_FS; add the following code to handle keyboard-specific variables:

#ifdef mouse
uint8_t HID_Buffer[4];
#define CURSOR_STEP 5
#elif keyboard
uint8_t keyboardpressed = 0;
#endif

In the same file, update the EXTI interrupt handler to set the keyboardpressed flag when an interrupt is detected and the device is configured:

void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == BUTTON_USER_Pin)
  {
    if ((((USBD_HandleTypeDef *) hpcd_USB_DRD_FS.pData)->dev_remote_wakeup == 1) &&
        (((USBD_HandleTypeDef *) hpcd_USB_DRD_FS.pData)->dev_state ==
         USBD_STATE_SUSPENDED))
    {
      if ((&hpcd_USB_DRD_FS)->Init.low_power_enable)
      {
        HAL_ResumeTick();
      }
      /* Activate Remote wakeup */
      HAL_PCD_ActivateRemoteWakeup((&hpcd_USB_DRD_FS));

      /* Remote wakeup delay */
      HAL_Delay(10);

      /* Disable Remote wakeup */
      HAL_PCD_DeActivateRemoteWakeup((&hpcd_USB_DRD_FS));

      /* change state to configured */
      ((USBD_HandleTypeDef *) hpcd_USB_DRD_FS.pData)->dev_state = USBD_STATE_CONFIGURED;

      /* Change remote_wakeup feature to 0 */
      ((USBD_HandleTypeDef *) hpcd_USB_DRD_FS.pData)->dev_remote_wakeup = 0;
      remotewakeupon = 1;
    }
  else if (((USBD_HandleTypeDef *) hpcd_USB_DRD_FS.pData)->dev_state ==
       USBD_STATE_CONFIGURED)

#ifdef mouse
    {
      GetPointerData(HID_Buffer);
      USBD_HID_SendReport(&hUsbDeviceFS, HID_Buffer, 4);
    } 
#elif keyboard
    keyboardpressed=1;
#endif    
  }
}

3.3 Main loop

In the main loop, add the following code to handle the keyboard input when the keyboardpressed flag is set:

while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
#ifdef keyboard
if (keyboardpressed == 1)
{
for (int i = 0; i < 5; i++)
{
keyboardhid.MODIFIER = 0x00; // No modifier keys
keyboardhid.KEYCODE1 = 0x4E; // Press 'Page Down' button
keyboardhid.KEYCODE2 = hello_sequence[i]; // Press 'H', 'E', 'L', 'L', 'O'
USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&keyboardhid, sizeof(keyboardhid));
HAL_Delay(500);
}
keyboardpressed = 0;
}
else
{
keyboardhid.MODIFIER = 0x00; // No modifier keys
keyboardhid.KEYCODE1 = 0x00; // Release key
keyboardhid.KEYCODE2 = 0x00; // Release key
USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&keyboardhid, sizeof(keyboardhid));
HAL_Delay(500);
}
#endif
}

4. Define predefined symbol in compiler settings

To define the predefined symbol in compiler settings (IAR Workbench is used in our case), follow these steps:

  1. Open [Project Options].
  2. Navigate to [C/C++ Compiler].
  3. Go to [Preprocessor].
  4. In [Defined symbols (one per line)], add the symbol: keyboard.

5. Expected behavior

When opening a text editor, notepad for example, then pressing the USER button on the board, you get the following result as shown in the screenshot below: 

FBL_1-1744713950863.png

Conclusion

By following these steps, you will successfully migrate the user application to implement a USB HID keyboard device starting from a USB HID mouse application.

Related links

Version history
Last update:
‎2025-04-17 1:12 AM
Updated by:
Contributors