cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement the USB device composite class USB DFU + HID

FBL
ST Employee

Introduction

This article is a step-by-step guide on how to implement an STM32 application as a USB device. This device should combine human interface device (HID) and device firmware upgrade (DFU) functionalities into a composite USB device. This allows the device to function as both an HID (for example, a mouse in our case) and a DFU device for firmware updates. We’re using the STM32 legacy library. Additionally, tips are provided to make it work on Windows operating systems.

Prerequisites

1. Project setup

You can directly clone the project from STM32-hotspot/CKB-STM32-HID-DFU-H5 or follow the steps below to set your project.

1.1 Create a project

Start by creating a new project using the STM32CubeIDE by clicking [File New STM32 Project]. Use the [Board Selector] tab and select the [NUCLEO-H563ZI]

FBL_0-1737475963279.png

Under the board project options, you can keep the USER BUTTON configuration.

Once the project creation is done, go into [Connectivity] section and enable the USB peripheral [USB_OTG_FS] peripheral in [Device Only mode].

Under [NVIC Settings], enable USB FS global interrupt.

FBL_0-1740648445294.png

 

Then, under [Code Generator], copy only the necessary libraries files into the project folder.

FBL_1-1737475963161.png

Under [System Core], under [RCC], HSE oscillators is configured in Bypass mode.

FBL_2-1740682200093.png

The instruction cache ICACHE should be disabled in our project.

FBL_0-1740068265056.png

The last step before generating the code is to update the system clock frequency, the USB peripheral is fed with an HSI48. So, activation of CRS Sync Source USB is highly recommended.

FBL_2-1737475963279.png

Due to USB data rate and packet memory interface requirements, the APB2 clock must have a minimum frequency of 12 MHz to avoid data overrun/underrun problems.

1.2 Develop your own project

After generating the code, add composite builder class, DFU class, and HID class source files manually to the project. Since the H5 product is native to AzureRTOS, CubeMX may not generate middleware layer automatically.  In the following table, you’ll find the necessary middleware files if they don’t already exist:

File name  Source Destination
usbd_core.h Core/Inc CKB-STM32-HID-DFU-H5/Middlewares/Core
/Inc/
usbd_ctlreq.h
usbd_ioreq.h
usbd_core.c Core/Src CKB-STM32-HID-DFU-H5/Middlewares/Core/Src/
usbd_ctlreq.c
usbd_ioreq.c
usbd_hid.h Class/HID CKB-STM32-HID-DFU-H5/Middlewares/Class
usbd_hid.c
usbd_dfu.h Class/DFU
usbd_dfu.c
usbd_composite_builder.h Class/CompositeBuilder
usbd_composite_builder.c

Regarding source files, you can copy following template files and edit them
 
Ensure that the USB Device folder and middleware directory structure includes the following:
FBL_0-1739199935239.pngUnder [Properties → C/C++ General → Paths and Symbols], ensure to add related file source under [Source Location].
 
FBL_1-1737475245113.png

Under [Includes], add middleware header files.

FBL_2-1737475719481.png

Now, you should be able to compile without issues.

2. Code 

Next step to facilitate the use of this article, you can simply replace the existent USB Device folder including App and Target with the one in the project on GitHub.

Scroll down to the definition of MX_USB_PCD_Init() function and you can find the following code already generated to initialize the USB peripheral.

 

 

void MX_USB_PCD_Init(void)
{

  /* USER CODE BEGIN USB_Init 0 */

  /* USER CODE END USB_Init 0 */

  /* USER CODE BEGIN USB_Init 1 */

  /* USER CODE END USB_Init 1 */
  hpcd_USB_DRD_FS.Instance = USB_DRD_FS;
  hpcd_USB_DRD_FS.Init.dev_endpoints = 8;
  hpcd_USB_DRD_FS.Init.speed = USBD_FS_SPEED;
  hpcd_USB_DRD_FS.Init.phy_itface = PCD_PHY_EMBEDDED;
  hpcd_USB_DRD_FS.Init.Sof_enable = DISABLE;
  hpcd_USB_DRD_FS.Init.low_power_enable = DISABLE;
  hpcd_USB_DRD_FS.Init.lpm_enable = DISABLE;
  hpcd_USB_DRD_FS.Init.battery_charging_enable = DISABLE;
  hpcd_USB_DRD_FS.Init.vbus_sensing_enable = DISABLE;
  hpcd_USB_DRD_FS.Init.bulk_doublebuffer_enable = DISABLE;
  hpcd_USB_DRD_FS.Init.iso_singlebuffer_enable = DISABLE;
  if (HAL_PCD_Init(&hpcd_USB_DRD_FS) != HAL_OK)
  {
    Error_Handler();
  }
}

 

 

2.1 Main application

In the main loop, between /* USER CODE BEGIN 2 */ and /* USER CODE END2 */, you can insert the following code:

	/* Initialize the USB Device Library */
	if(USBD_Init(&hUsbDeviceFS, &Class_Desc, 0) != USBD_OK)
		Error_Handler();

	/* Store the DFU Class */
	DFU_InstID = hUsbDeviceFS.classId;

	/* Register the DFU Class */
	if(USBD_RegisterClassComposite(&hUsbDeviceFS, USBD_DFU_CLASS, CLASS_TYPE_DFU, NULL) != USBD_OK)
		Error_Handler();

	/* Store HID Instance Class ID */
	HID_InstID = hUsbDeviceFS.classId;

	if(USBD_RegisterClassComposite(&hUsbDeviceFS, USBD_HID_CLASS, CLASS_TYPE_HID, &HID_EpAdd_Inst) != USBD_OK)
		Error_Handler();


	USBD_CMPSIT_SetClassID(&hUsbDeviceFS, CLASS_TYPE_DFU, 0);

	/* Add DFU Interface Class */
	USBD_DFU_RegisterMedia(&hUsbDeviceFS, &USBD_DFU_Flash_fops);


	if(USBD_Start(&hUsbDeviceFS) != USBD_OK)
		Error_Handler();

2.2 Implement HID functionality

  • Set the HID endpoint address and size in the usbd_conf.h file.
#define HID_EPIN_ADDR          	0x81U
#define HID_EPIN_SIZE           0x04U // HID_EPIN_SIZE 4 bytes for simple HID device
  • Define the HID report buffer in main.c:
	hid_report_buffer[0] = 0;   /* Buttons – first 3 bits [LSB] */
	hid_report_buffer[1] = 100; /* X axis 8 bits value signed */
	hid_report_buffer[2] = 0;   /* Y axis 8 bits value signed*/
	hid_report_buffer[3] = 0;   /* Wheel 8 bits value signed*/
  • Check definition of the HID report descriptor in usbd_hid.c:
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = {
  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  0x09, 0x02, // USAGE (Mouse)
  0xa1, 0x01, // COLLECTION (Application)
  0x09, 0x01, // USAGE (Pointer)
  0xa1, 0x00, // COLLECTION (Physical)
  0x05, 0x09, // USAGE_PAGE (Button)
  0x19, 0x01, // USAGE_MINIMUM (Button 1)
  0x29, 0x03, // USAGE_MAXIMUM (Button 3)
  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)
  0x95, 0x01, // REPORT_COUNT (1)
  0x75, 0x05, // REPORT_SIZE (5)
  0x81, 0x03, // INPUT (Cnst,Var,Abs)
  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  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, 0x06, // INPUT (Data,Var,Rel)
  0xc0,       // END_COLLECTION
  0xc0        // END_COLLECTION
};

2.3 Implement DFU functionality

DFU class uses only the control endpoint (endpoint 0) for all its operations. The configuration descriptor includes the configuration descriptor, interface descriptor, and DFU functional descriptor. The total size of the configuration descriptor is calculated based on the number of interfaces and the size of each descriptor.

In our case, the total size is 27 bytes. It includes 9 bytes for the configuration descriptor, 9 bytes for the interface descriptor, and 9 bytes for the DFU functional descriptor.

  • Set the DFU transfer size in the usbd_conf.h file.
    #define USBD_DFU_XFER_SIZE             1024U
  • The maximum packet size for endpoint 0 is typically 64 bytes for a full-speed USB device.

Example code demonstrates the firmware upgrade of the application code using USB device firmware upgrade (DFU). 

Let's return to the main loop. To handle HID reports, once pushing the user button, the mouse should move to the right.

while (1)
{
  if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET)
  {
    USBD_HID_SendReport(&hUsbDeviceFS, hid_report_buffer, sizeof(hid_report_buffer),HID_InstID);
    HAL_Delay(100);
  }
}

3. Build and flash the application

After building and flashing the application, some more steps are required.

3.1 Update driver 

Another step is required updating the driver for DFU, in device manager properties Update Driver Browse my computer for drivers.

FBL_1-1739444344719.png

 

Then select [Let me pick from a list of available drivers of my computer].

FBL_2-1739444419226.png

In the [Update Drivers] window, ensure [Show compatible hardware] is checked, then select [USB Composite Device] from the list and click [Next].

FBL_2-1736869454841.png

To check HID is functional, you can click on the blue button to see the mouse pointer change position.

FBL_11-1736349376599.png

Both interfaces (HID and DFU) share the same VID and PID. You can check "Hardware Ids" property under the USB Input Device window.

FBL_1-1739901271355.png

And you can connect in DFU and have USB HID still functional.

3.2 Troubleshooting DFU enumeration

In case you see this, you can troubleshoot the following issue: DFU is not enumerated correctly,

FBL_2-1739901727399.png

You can update the driver:

FBL_3-1739901797824.pngFBL_4-1739901828226.pngFBL_5-1739902026136.pngFBL_6-1739902056230.png

 

FBL_7-1739902076517.png

 

FBL_7-1736349297080.png

Now that the device is recognized as both an HID and DFU device without any issue.

Conclusion

This guide provides a comprehensive approach to build an application on STM32 that combines HID and DFU functionalities into a composite USB device. By following these steps, you can create a USB device capable of both user interaction and firmware update.

Related links

 
Version history
Last update:
‎2025-02-27 10:56 AM
Updated by: