cancel
Showing results for 
Search instead for 
Did you mean: 

How to configure STM32 as USB dual role

Gyessine
ST Employee

Summary

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.

Introduction

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.

1. Hardware and software prerequisites

Gyessine_0-1750254030723.png

2. Development

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.

2.1 Configure the host 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-H723ZG].
  • In the board project options that pops up, unselect all, and press [OK]

 Gyessine_0-1748273891932.png

 

  • When the .ioc file opens, press [Pinout→ Clear pinouts].

Gyessine_1-1750244633024.png

 

In the connectivity category, click on USB_OTG_HS and choose:

  • Host only under internal FS PHY.
  • Activate VBUS under “Activate VBUS".
  • Select "USB on the Go HS global interrupt" checkbox in NVIC settings.

Gyessine_4-1748351544818.png

 

  • In “Middleware and Software Packs”, choose USB_Host then select HID as a class.

Gyessine_5-1748351571725.png

  • In the “Platform Settings”, select “GPIO:Output” as Drive_VBUS_HS and choose your specific board VBUS supply pin after configuring it as GPIO output in the pinout view.

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:

Gyessine_4-1748015479569.pngGyessine_2-1748336870547.png

 

  • In the system Core category and for RCC, select USB_HS for CRS SYNC.

Gyessine_0-1750244603882.png

  •  Generate your code. 

2.2 Configuring the dual role project

2.2.1 Configuring the .ioc file of the dual role project

Using the STM32CubeIDE:

  • Click [File → New → STM32 Project]
  • Use the [Board Selector] tab and select the [NUCLEO-H723ZG].
  • In the board project options that pops up, select only the USER LEDs, and press [OK]

Gyessine_1-1748332624800.png

  • In the “Connectivity” category, click your board specific USART linked to your ST-LINK via the Virtual Com Port. Check the board schematic to identify the USART to activate. In our case, it is USART3.

Gyessine_10-1748015479600.png

 

Gyessine_11-1748015479602.png

 

  • Choose asynchronous mode.
  • Make sure that STM32CubeMX has configures the exact pins that are used on your board schematic.
  • Ensure that you select a High speed for the GPIOs and enable your USART global interrupt in NVIC settings.

Gyessine_6-1748351656127.png

Gyessine_3-1748337187750.png

  • In USB_OTG_HS select “Device only” and activate VBUS as well as USB OTG global interrupt in NVIC settings.

Gyessine_2-1750244845247.png

  • In “Middleware and Software Packs”, click on "USB_Device" and choose “Communication Device Class”.

Gyessine_7-1748351689014.png

  • Configure the board-specific pin connected to the blue button as a GPIO input (PC13 in our case):

Gyessine_15-1748015479629.png

  • In clock configuration, ensure that you are using the HSI clock source from the PLL source.

Gyessine_17-1748015479637.png

Gyessine_18-1748015479638.png

  • In “project manager”, increase the minimum heap and stack size to 0x800.

Gyessine_19-1748015479639.png

  •  Generate your project.

2.2.2 Dual role project tree configuration

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.

  • Locate the folders where the host project and dual role project are saved in your computer.
  • In the host project, go to "Drivers/STM32H7xx_HAL_Drivers/Src”.

Gyessine_20-1748015525651.png

  •  Copy the stm32h7xx_hal_hcd.c file to the corresponding location under “Drivers/STM32H7xx_HAL_Drivers/Src” in the dual role project:

Gyessine_8-1748351799579.png

  • The table below lists all the required middleware files to add :
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
  • Then click refresh. 

Gyessine_24-1748015525656.png

Your project structure should be as shown below: 

 

Gyessine_9-1748351909336.png

  • The table below lists the source files to add :
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:

Gyessine_4-1748337682774.png

2.2.3 Coding of the dual role project

  • In main.c, add these sections in the specific areas:
/* 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 */
}
  • Comment this line:
 // MX_USB_DEVICE_Init();
  • Check if this code is in the static void MX_GPIO_Init(void).

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 */
  • In stm32h7xx_it.c, add these lines in the specific areas:
#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);
    }
}
}
  • Open usb_host.c under USB_HOST/App and replace the existing code with the code provided below:

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



  • Open usb_host.h under USB_HOST/App and replace the existing code with the code provided down below:
#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__ */
  • In usbh_conf.c, add this line:
#include "stm32h7xx_hal_hcd.h"
  • In "stm32h7xx_hal_conf.h" look for HAL_HCD_MODULE_ENABLED and uncomment it: 
 #define HAL_HCD_MODULE_ENABLED

3. Results 

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.

  • Baud rate: 115200
  • Data: 8 bits
  • Parity: None
  • Stop bits: 1
  • Flow control: None
  • Attach a mouse to the board through the Type A micro-B adapter and open your serial monitor toolchain.
  • Choose the ST-LINK port.

Gyessine_1-1748336091239.png

You should see mouse traffic in the serial monitor.

Gyessine_3-1748351153468.png

  • Remove the mouse and attach the board to the pc from the USB connector through a “Type A to micro-B” cable. Press the blue button. Integrated LEDs turns on to validate the switch.

The device mode is programmed as CDC class that prints hello world repeatedly on the serial monitor.

  • Baud rate: 9600
  • Data: 8 bits
  • Parity: None
  • Stop bits: 1
  • Flow control: None

Gyessine_0-1748336084642.png

Gyessine_35-1748017624582.png

Conclusion

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.

Related links

Version history
Last update:
‎2025-07-02 7:25 AM
Updated by: