on 2025-07-03 1:00 AM
This article provides a step-by-step guide to configuring the NUCLEO-H723ZG board as a dual role device. It details the entire setup process, starting with the basic hardware connections and progressing to the firmware implementation. By following this guide, users can successfully configure the NUCLEO-H723ZG board to function effectively in both roles: HID host and CDC device.
The current STM32CubeMX release does not natively support dual role functionalities.
There is no code generation for ST USB middleware files or dual role functionalities when you select the OTG dual role device option in the software.
This guide is based on STM32CubeIDE 1.18.0, STM32CubeMX 6.14.0, and STM32CubeH7 1.12.1. It was validated with a NUCLEO-H723ZG board, which embeds an STM32H723ZGT6U device.
A simple switch between modes is achieved by pressing the blue button. This guide outlines the process from configuring the basic hardware connections to implementing the firmware.
Since selecting OTG dual role mode in STM32CubeMX does not generate the necessary files, you must create two projects. One project as a host and the other as a device to access the necessary files and functions for both modes.
In the connectivity category, click on USB_OTG_HS and choose:
To know your USB_FS_PWR_EN pin, check your board schematic. In our case, for example, we need to refer to the MB1364 schematic:
Using the STM32CubeIDE:
The first step is complete. Now, copy the necessary files from the first project to the second project to include all mandatory files for both modes in the same project.
Source path | Destination path | Files |
---|---|---|
h723_Host\Drivers\STM32H7xx_HAL_Driver \Inc |
h723_dualrole\Drivers \STM32H7xx_HAL_Driver\Inc |
stm32h7xx_hal_hcd.h |
h723_Host\Middlewares\ST \STM32_USB_Host_Library\Class\HID\Inc |
h723_dualrole\Middlewares\ST \STM32_USB_Device_Library\Class \CDC\Inc |
usbh_hid_keybd.h, usbh_hid_mouse.h, usbh_hid_parser.h, usbh_hid_usage.h |
h723_Host\Middlewares\ST \STM32_USB_Host_Library\Class\HID\Src |
h723_dualrole\Middlewares\ST \STM32_USB_Device_Library\Class \CDC\Src |
usbh_hid.c, usbh_hid_keybd.c, usbh_hid_mouse.c, usbh_hid_parser.c |
h723_Host\Middlewares\ST \STM32_USB_Host_Library\Core\Inc |
h723_dualrole\Middlewares\ST \STM32_USB_Device_Library\Core\Inc |
usbh_core.h, usbh_ctlreq.h, usbh_def.h, usbh_pipes.h |
h723_Host\Middlewares\ST \STM32_USB_Host_Library\Core\Src |
h723_dualrole\Middlewares\ST\ STM32_USB_Device_Library\Core\Src |
usbh_core.c, usbh_ctlreq.c, usbh_ioreq.c, usbh_pipes.c |
Your project structure should be as shown below:
h723_Host\USB_HOST\App | h723_dualrole\USB_DEVICE\App | usb_host.c, usb_host.h |
h723_Host\USB_HOST\Target | h723_dualrole\USB_DEVICE\Target |
usbh_conf.c usbh_conf.h usbh_platform.c, usbh_platform.h |
The structure should be organized as illustrated below:
/* USER CODE BEGIN Includes */
#include "usb_device.h"
#include "usbd_core.h"
#include <string.h>
#include "usb_host.h"
#include "usbd_core.h"
#include "usbh_platform.h"
#include "stm32h7xx_ll_usb.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PM */
extern USBH_HandleTypeDef hUsbHostHS;
extern USBD_HandleTypeDef hUsbDeviceHS;
volatile uint8_t modes = 1; // State variable for toggling
/* USER CODE END PM */
/* USER CODE BEGIN PV */
UART_HandleTypeDef huart3;
USB_OTG_GlobalTypeDef *USBx = USB_OTG_HS;
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
void HTD(void);
void DTH(void);
/* USER CODE END PFP */
/* USER CODE BEGIN 0 */
void HTD(void) {
USBH_DeInit(&hUsbHostHS);
MX_DriverVbusHS(0);
modes =0;
MX_USB_DEVICE_Init();
}
void DTH(void) {
USBD_DeInit(&hUsbDeviceHS);
MX_DriverVbusHS(1);
modes =1;
MX_USB_HOST_Init();
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
BSP_LED_Init(LED_GREEN);
BSP_LED_Init(LED_YELLOW);
BSP_LED_Init(LED_RED);
MX_USB_HOST_Init();
MX_DriverVbusHS(1);
/* USER CODE END 2 */
while (1) {
if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == GPIO_PIN_SET) {
HAL_Delay(200); // Debounce delay
if (modes == 1) {
HTD();
} else {
DTH();
}
// Toggle LEDs
HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_PIN);
HAL_GPIO_TogglePin(LED2_GPIO_PORT, LED2_PIN);
HAL_GPIO_TogglePin(LED3_GPIO_PORT, LED3_PIN);
}
if (modes == 0) {
CDC_Transmit_HS((uint8_t*)"Hello World\r\n", 13);
HAL_Delay(500);
} else if (modes == 1) {
USBH_Process(&hUsbHostHS);
USBH_HID_AppProcess();
}
/* USER CODE END 3 */
}
// MX_USB_DEVICE_Init();
You may need to modify this code if you want to use another board where it has another VBUS supply pin.
/* USER CODE BEGIN MX_GPIO_Init_2 */
/*Configure GPIO pin : PD10 */
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/* USER CODE END MX_GPIO_Init_2 */
#include "stm32h7xx_hal_hcd.h"
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
extern PCD_HandleTypeDef hpcd_USB_OTG_HS;
extern HCD_HandleTypeDef hhcd_USB_OTG_HS;
extern UART_HandleTypeDef huart3;
extern volatile uint8_t modes;
/* USER CODE END TD */
void OTG_HS_IRQHandler(void) {
if (modes == 1) {
HAL_HCD_IRQHandler(&hhcd_USB_OTG_HS);
} else if (modes == 0) {
HAL_PCD_IRQHandler(&hpcd_USB_OTG_HS);
}
}
}
This code implements mouse and keyboard host functionalities.
#include "usb_host.h"
#include "usbh_core.h"
#include "usbh_hid.h"
#include "main.h"
#include "stm32h7xx_hal_pcd.h"
/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
__IO HID_APP_State hid_app_state;
extern HID_MOUSE_Info_TypeDef mouse_info;
/* USER CODE END PV */
extern UART_HandleTypeDef huart3;
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void USBH_Clock_Config(void);
extern void HID_MOUSE_App(USBH_HandleTypeDef *phost);
extern void HID_KEYBRD_App(USBH_HandleTypeDef *phost);
static void HID_MOUSE_ProcessData(HID_MOUSE_Info_TypeDef *data);
void HID_KEYBRD_App(USBH_HandleTypeDef *phost);
extern void Error_Handler(void);
/* USER CODE END PFP */
/* USB Host core handle declaration */
USBH_HandleTypeDef hUsbHostHS;
ApplicationTypeDef Appli_state = APPLICATION_IDLE;
/* USER CODE BEGIN 0 */
void UART3_Log(const char *message)
{
HAL_UART_Transmit(&huart3, (uint8_t*)message, strlen(message), HAL_MAX_DELAY);
}
void USBH_Clock_Config(void)
{
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
/* PLL3 for USB Clock */
PeriphClkInitStruct.PLL3.PLL3M = 8;
PeriphClkInitStruct.PLL3.PLL3N = 336;
PeriphClkInitStruct.PLL3.PLL3FRACN = 0;
PeriphClkInitStruct.PLL3.PLL3P = 2;
PeriphClkInitStruct.PLL3.PLL3R = 2;
PeriphClkInitStruct.PLL3.PLL3Q = 7;
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USB;
PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_PLL3;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
}
/* USER CODE END 0 */
/*
* user callback declaration
*/
static void USBH_UserProcess(USBH_HandleTypeDef *phost, uint8_t id);
void USBH_HID_AppProcess(void)
{
switch(hid_app_state)
{
case HID_APP_WAIT:
if(Appli_state == APPLICATION_READY)
{
if(USBH_HID_GetDeviceType(&hUsbHostHS) == HID_KEYBOARD)
{
hid_app_state = HID_APP_KEYBOARD;
UART3_Log("Use Keyboard to type characters:\r\n");
}
else if(USBH_HID_GetDeviceType(&hUsbHostHS) == HID_MOUSE)
{
hid_app_state = HID_APP_MOUSE;
UART3_Log("USB HID Host Mouse App...\r\n");
}
}
break;
case HID_APP_MOUSE:
if(Appli_state == APPLICATION_READY)
{
HID_MOUSE_App(&hUsbHostHS);
}
break;
case HID_APP_KEYBOARD:
if(Appli_state == APPLICATION_READY)
{
HID_KEYBRD_App(&hUsbHostHS);
}
break;
default:
break;
}
if(Appli_state == APPLICATION_DISCONNECT)
{
Appli_state = APPLICATION_IDLE;
UART3_Log("USB device disconnected !!!\r\n");
hid_app_state = HID_APP_WAIT;
}
}
void HID_MOUSE_App(USBH_HandleTypeDef *phost)
{
HID_MOUSE_Info_TypeDef *m_pinfo;
m_pinfo = USBH_HID_GetMouseInfo(phost);
if(m_pinfo != NULL)
{
/* Handle Mouse data position */
HID_MOUSE_ProcessData(&mouse_info);
if(m_pinfo->buttons[0])
{
UART3_Log("Left Button Pressed\r\n");
}
if(m_pinfo->buttons[1])
{
UART3_Log("Right Button Pressed\r\n");
}
if(m_pinfo->buttons[2])
{
UART3_Log("Middle Button Pressed\r\n");
}
}
}
static void HID_MOUSE_ProcessData(HID_MOUSE_Info_TypeDef *data)
{
char buffer[50];
if((data->x != 0) || (data->y != 0))
{
snprintf(buffer, sizeof(buffer), "Mouse : X = %3d, Y = %3d\r\n", data->x, data->y);
UART3_Log(buffer);
}
}
void HID_KEYBRD_App(USBH_HandleTypeDef *phost)
{
HID_KEYBD_Info_TypeDef *k_pinfo;
char c;
k_pinfo = USBH_HID_GetKeybdInfo(phost);
if(k_pinfo != NULL)
{
c = USBH_HID_GetASCIICode(k_pinfo);
if(c != 0)
{
char buffer[2] = {c, '\0'};
UART3_Log(buffer);
}
}
}
void MX_USB_HOST_Init(void)
{
/* USER CODE BEGIN USB_HOST_Init_PreTreatment */
USBH_Clock_Config();
/* USER CODE END USB_HOST_Init_PreTreatment */
/* Init host Library, add supported class and start the library. */
if (USBH_Init(&hUsbHostHS, USBH_UserProcess, HOST_HS) != USBH_OK)
{
Error_Handler();
}
if (USBH_RegisterClass(&hUsbHostHS, USBH_HID_CLASS) != USBH_OK)
{
Error_Handler();
}
if (USBH_Start(&hUsbHostHS) != USBH_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USB_HOST_Init_PostTreatment */
HAL_UART_Transmit(&huart3, (uint8_t*)" **** USB OTG HS in FS MSC Host **** \r\n", strlen(" **** USB OTG HS in FS MSC Host **** \r\n"), HAL_MAX_DELAY);
HAL_UART_Transmit(&huart3, (uint8_t*)"USB Host library started.\r\n", strlen("USB Host library started.\r\n"), HAL_MAX_DELAY);
/* Initialize Application and HID process */
HAL_UART_Transmit(&huart3, (uint8_t*)"Starting HID Application\r\n", strlen("Starting HID Application\r\n"), HAL_MAX_DELAY);
HAL_UART_Transmit(&huart3, (uint8_t*)"Connect your HID Device\r\n", strlen("Connect your HID Device\r\n"), HAL_MAX_DELAY);
hid_app_state = HID_APP_WAIT;
/* USER CODE END USB_HOST_Init_PostTreatment */
}
void MX_USB_HOST_Process(void)
{
/* USB Host Background task */
USBH_Process(&hUsbHostHS);
/* HID Application Process */
USBH_HID_AppProcess();
}
static void USBH_UserProcess (USBH_HandleTypeDef *phost, uint8_t id)
{
/* USER CODE BEGIN CALL_BACK_1 */
switch(id)
{
case HOST_USER_CONNECTION:
break;
case HOST_USER_DISCONNECTION:
Appli_state = APPLICATION_DISCONNECT;
break;
case HOST_USER_CLASS_ACTIVE:
Appli_state = APPLICATION_READY;
break;
default:
break;
}
}
#ifndef __USB_HOST__H__
#define __USB_HOST__H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32h7xx.h"
#include "stm32h7xx_hal.h"
/* USER CODE BEGIN INCLUDE */
#include "usbh_hid.h"
#include "usbh_hid_parser.h"
typedef enum {
APPLICATION_IDLE = 0,
APPLICATION_START,
APPLICATION_READY,
APPLICATION_DISCONNECT
}ApplicationTypeDef;
/** USER CODE BEGIN1 **/
typedef enum {
HID_APP_IDLE = 0,
HID_APP_WAIT,
HID_APP_MOUSE,
HID_APP_KEYBOARD,
}HID_APP_State;
void MX_USB_HOST_Init(void);
void MX_USB_HOST_Process(void);
void USBH_HID_AppProcess(void);
#ifdef __cplusplus
}
#endif
#endif /* __USB_HOST__H__ */
#include "stm32h7xx_hal_hcd.h"
#define HAL_HCD_MODULE_ENABLED
You can now proceed to build your project. If all previous steps have been followed correctly, the build process should complete without any errors. Once built, flash the compiled project onto the board.
Note : It is important to note that if you use the code generation tool again, errors might occur because the code generation deletes added files and resets modified ones.
The application is programmed to start as host and switch to device mode once the blue button is pressed. The host mode is configured as a human interface device (HID) mouse that prints mouse activities on the serial monitor.
You should see mouse traffic in the serial monitor.
The device mode is programmed as CDC class that prints hello world repeatedly on the serial monitor.
Now, you have the necessary information to create a dual role project. Follow the same steps to configure the board with other classes.
This application does not support full USB On-The-Go (OTG) functionality. However, it supports dual role data (DRD) mode, which allows role switching between host and device based on the USB cable connection.