2025-02-21 1:36 AM - edited 2025-02-27 10:56 AM
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.
You can directly clone the project from STM32-hotspot/CKB-STM32-HID-DFU-H5 or follow the steps below to set your 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].
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.
Then, under [Code Generator], copy only the necessary libraries files into the project folder.
Under [System Core], under [RCC], HSE oscillators is configured in Bypass mode.
The instruction cache ICACHE should be disabled in our project.
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.
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.
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 |
File name | Source | Destination |
usbd_def.h | usbd_def.h | CKB-STM32-HID-DFU-H5/USB Device/App |
usbd_desc.h | usbd_desc_template.h | |
usbd_desc.c | usbd_desc_template.c | |
usbd_conf.h | usbd_conf_template.h | CKB-STM32-HID-DFU-H5/USB Device/Target |
usbd_conf.c | usbd_conf_template.c |
Under [Includes], add middleware header files.
Now, you should be able to compile without issues.
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();
}
}
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();
#define HID_EPIN_ADDR 0x81U
#define HID_EPIN_SIZE 0x04U // HID_EPIN_SIZE 4 bytes for simple HID device
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*/
__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
};
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.
#define USBD_DFU_XFER_SIZE 1024U
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);
}
}
After building and flashing the application, some more steps are required.
Another step is required updating the driver for DFU, in device manager properties → Update Driver → Browse my computer for drivers.
Then select [Let me pick from a list of available drivers of my computer].
In the [Update Drivers] window, ensure [Show compatible hardware] is checked, then select [USB Composite Device] from the list and click [Next].
To check HID is functional, you can click on the blue button to see the mouse pointer change position.
Both interfaces (HID and DFU) share the same VID and PID. You can check "Hardware Ids" property under the USB Input Device window.
And you can connect in DFU and have USB HID still functional.
In case you see this, you can troubleshoot the following issue: DFU is not enumerated correctly,
You can update the driver:
Now that the device is recognized as both an HID and DFU device without any issue.
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.