cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement a USB device composite in STM32H5 microcontrollers

D.Botelho
ST Employee

Summary

This article presents a step-by-step tutorial on how to develop a USB device with more than a class in the STM32H5 microcontroller using the classical USB library. The tutorial is based on NUCLEO-H503RB and can be easily tailored to any other STM32.

Introduction

Hello developer, and welcome to this article. If you are developing a USB device composite application for the STM32, this article is for you! Here we show you how to implement the classical USB device middleware to use more than one class in the same application.

For this tutorial, we use the NUCLEO-H503RB board, to implement CDC (Communications Device Class) and HID (Human Interface Device) classes through USB communication. The CDC is used to open a Virtual COM port communication, and the HID acts as a mouse. This board has an STM32H503RBT6 microcontroller, and for further details about the board, refer to the user manual.  The steps shown here can be easily adapted to any other STM32. The STM32CubeIDE 1.15.1, the STM32CubeH5 1.2.0 releases were used to build this tutorial.

Figure 1. NUCLEO-H503RBFigure 1. NUCLEO-H503RB

For some STM32 series (STM32U5, STM32H5 and STM32C0), the now classic ST USB middleware is no longer delivered as part of the STM32Cube native package. But to maintain compatibility with legacy applications, this package is still available to be downloaded via our GitHub page. You can download the packages from the following links:

For a detailed explanation regarding the classic USB library, refer to the ST Wiki and the STM32 USB training:

To get access to the code developed with this article, refer to the following GitHub page:

1. Development

Let us start by creating a project for the STM32H503RBT6 microcontroller in STM32CubeIDE.

Figure 2. Project Creation - STM32CubeIDEFigure 2. Project Creation - STM32CubeIDE

Once the project creation is done, enable the USB peripheral in [Device_Only] mode and activate its interrupt in the [NVIC Settings] tab.

Figure 3. USB Peripheral configuration - STM32CubeIDEFigure 3. USB Peripheral configuration - STM32CubeIDE

For this example, we use the User button to control the mouse through the HID class. For that, configure the GPIO PC13 as GPIO input. Since the pins have an external pull-down and the button shorts it to VDD, it is not necessary to enable either a pull-up or down internal resistor.

Figure 4. User button settingsFigure 4. User button settings

After that, increase the amount of heap and stack sizes of the project as indicated in the image below, this action is done in the [Project Manager] tab:

Figure 5. Heap and Stack size - STM32CubeIDEFigure 5. Heap and Stack size - STM32CubeIDE

For the next step, navigate to the [Advanced Settings] tab, and change the USB generation settings as follows:

Figure 6. Code generation settings - STM32CubeIDEFigure 6. Code generation settings - STM32CubeIDE

The last step before generating the code is to update the system clock frequency. According to the STM32H503 Reference Manual, the USB peripheral requires at least 12MHz of clock frequency at the APB2 bus to avoid data overrun and underrun problems. Just for this example, we set the clock frequency to the maximum allowed to achieve the best performance.

Figure 7. Clock configuration - STM32CubeIDEFigure 7. Clock configuration - STM32CubeIDE

 Still, in the clock three, ensure that the USB peripheral is being fed with a 48MHz clock frequency.

Figure 8. USB Peripheral clock - STM32CubeIDEFigure 8. USB Peripheral clock - STM32CubeIDE

With those settings, you are all set to proceed with the code generation. So, click on the code generator button or press the alt + K shortcut.

Figure 9. Code generation - STM32CubeIDEFigure 9. Code generation - STM32CubeIDE

Now, you must download the classic USB middleware from the following link:

Once downloaded, extract the compressed content and go back to the STM32CubeIDE. Here we create a folder to accommodate the USB stack files. So, right-click over the project, then go to [New] -> [Source Folder], we give it a name “USB”.

Figure 10. Creating a source folder - STM32CubeIDEFigure 10. Creating a source folder - STM32CubeIDE

For the next step, open the USB middleware folder (the downloaded one), then drag and drop the Core folder within the created USB folder.

Figure 11. Importing Core folder - STM32CubeIDEFigure 11. Importing Core folder - STM32CubeIDE

When prompted, select [Copy files and folders], then press [OK].

Figure 12. Import options - STM32CubeIDEFigure 12. Import options - STM32CubeIDE

Now, within the created “USB” folder, create another one called “Class” and finally import all the classes that are used your application. In addition, copy the CompositeBuilder class. This contains all the necessary code to build the composite descriptor.

Figure 13. Importing files and folders - STM32CubeIDEFigure 13. Importing files and folders - STM32CubeIDE

After doing that, we must configure the compiler to find all the include files. For that, right-click over all the folders called “Inc” then go to Add/remove include path… then click OK. It is necessary to do it for all the imported folders called “Inc”.

Figure 14. Referencing header files - STM32CubeIDEFigure 14. Referencing header files - STM32CubeIDE

The next step is to rename the USB/Core/Inc/usbd_conf_template.h file to usbd_conf.h and the USB/Core/Inc/usbd_conf_template.c to usbd_conf.c. After doing it, open the usbd_conf.h file. In this file we need to change the include related to the HAL driver, to the used MCU. Since we are using the STM32H503, we change the include to the stm32h5xx.h file.

 

 

/* Includes ------------------------------------------------------------------*/
#include "stm32h5xx.h"  /* replace 'stm32xxx' with your HAL driver header filename, ex: stm32f4xx.h */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

 

 

Continuing in the file, also replace the defines as presented below. The created configuration was made to match the requirements for CDC and HID classes.

 

 

/** @addtogroup STM32_USB_DEVICE_LIBRARY
  * @{
  */
/** @defgroup USBD_CONF_Exported_Defines
  * @{
  */
#define USBD_MAX_NUM_INTERFACES                     1U
#define USBD_MAX_NUM_CONFIGURATION                  1U
#define USBD_MAX_STR_DESC_SIZ                       0x100U
#define USBD_SELF_POWERED                           1U
#define USBD_DEBUG_LEVEL                            3U
/* CDC Class Config */
#define USBD_CDC_INTERVAL                           2000U
/* CustomHID Class Config */
#define CUSTOM_HID_HS_BINTERVAL                     0x05U
#define CUSTOM_HID_FS_BINTERVAL                     0x05U
#define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE           0x02U
#define USBD_CUSTOM_HID_REPORT_DESC_SIZE            163U
/* Activate the IAD option */
#define USBD_COMPOSITE_USE_IAD						1U
/* Activate the composite builder */
#define USE_USBD_COMPOSITE
/* Activate CustomHID and CDC classes in composite builder */
#define USBD_CMPSIT_ACTIVATE_CDC                    1U
#define USBD_CMPSIT_ACTIVATE_HID  					1U
/* Define the number of supported classes */
#define USBD_MAX_SUPPORTED_CLASS                       2U
/* Define the number of endpoints per class */
#define USBD_MAX_CLASS_ENDPOINTS                       3U
/* Define the number of maximum interfaces per class */
#define USBD_MAX_CLASS_INTERFACES                      2U
/* Classes Endpoint Addresses */
#define HID_EPIN_ADDR                              	0x81U
#define CDC_IN_EP                                   0x82U  /* EP1 for data IN */
#define CDC_OUT_EP                                  0x01U  /* EP1 for data OUT */
#define CDC_CMD_EP                                  0x83U  /* EP2 for CDC commands */
/** @defgroup USBD_Exported_Macros
  * @{
  */
/* Memory management macros make sure to use static memory allocation */
/** Alias for memory allocation. */
#define USBD_malloc         (void *)USBD_static_malloc
/** Alias for memory release. */
#define USBD_free           USBD_static_free
/** Alias for memory set. */
#define USBD_memset         memset
/** Alias for memory copy. */
#define USBD_memcpy         memcpy
/** Alias for delay. */
#define USBD_Delay          HAL_Delay
/* DEBUG macros */
#if (USBD_DEBUG_LEVEL > 0U)
#define  USBD_UsrLog(...)   do { \
                                 printf(__VA_ARGS__); \
                                 printf("\n"); \
                               } while (0)
#else
#define USBD_UsrLog(...) do {} while (0)
#endif /* (USBD_DEBUG_LEVEL > 0U) */
#if (USBD_DEBUG_LEVEL > 1U)
#define  USBD_ErrLog(...) do { \
                               printf("ERROR: ") ; \
                               printf(__VA_ARGS__); \
                               printf("\n"); \
                             } while (0)
#else
#define USBD_ErrLog(...) do {} while (0)
#endif /* (USBD_DEBUG_LEVEL > 1U) */
#if (USBD_DEBUG_LEVEL > 2U)
#define  USBD_DbgLog(...)   do { \
                                 printf("DEBUG : ") ; \
                                 printf(__VA_ARGS__); \
                                 printf("\n"); \
                               } while (0)
#else
#define USBD_DbgLog(...) do {} while (0)
#endif /* (USBD_DEBUG_LEVEL > 2U) */
/** @defgroup USBD_CONF_Exported_FunctionsPrototype
  * @{
  */
/* Exported functions -------------------------------------------------------*/
void *USBD_static_malloc(uint32_t size);
void USBD_static_free(void *p);

 

 

To use the HID and CDC classes, these settings are enough. If other classes are used, edit the file per your application requirements.

Once you are done with the changes, save and close the usbd_conf.h file. Then open the usbd_conf.c. Within this one, we need to link the HAL PCD drivers with the USB stack. So, let us start including the necessary header files. Here it's also necessary to include the header of all used classes.

 

 

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usbd_core.h"
#include "usbd_hid.h"
#include "usbd_cdc.h"

 

 

Once you have completed this step, we need to import the PCD_HandleTypeDef to this file:

 

 

extern PCD_HandleTypeDef hpcd_USB_DRD_FS;

 

 

Create the following function prototype:

 

 

/* Private function prototypes -----------------------------------------------*/
USBD_StatusTypeDef USBD_Get_USB_Status(HAL_StatusTypeDef hal_status);

 

 

Then create the code for all the PCD callbacks:

 

 

void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd)
{
  USBD_LL_SetupStage((USBD_HandleTypeDef*)hpcd->pData, (uint8_t *)hpcd->Setup);
}
void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
  USBD_LL_DataOutStage((USBD_HandleTypeDef*)hpcd->pData, epnum, hpcd->OUT_ep[epnum].xfer_buff);
}
void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
{
  USBD_LL_DataInStage((USBD_HandleTypeDef*)hpcd->pData, epnum, hpcd->IN_ep[epnum].xfer_buff);
}
void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd)
{
  USBD_LL_SOF((USBD_HandleTypeDef*)hpcd->pData);
}
void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd)
{
  USBD_SpeedTypeDef speed = USBD_SPEED_FULL;
  if ( hpcd->Init.speed != PCD_SPEED_FULL)
  {
    Error_Handler();
  }
    /* Set Speed. */
  USBD_LL_SetSpeed((USBD_HandleTypeDef*)hpcd->pData, speed);
  /* Reset Device. */
  USBD_LL_Reset((USBD_HandleTypeDef*)hpcd->pData);
}
void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd)
{
  USBD_LL_DevConnected((USBD_HandleTypeDef*)hpcd->pData);
}
void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd)
{
  USBD_LL_DevDisconnected((USBD_HandleTypeDef*)hpcd->pData);
}

 

 

Finally, implement all the other functions to complete the link to the STM32H503 USB HAL.

 

 

USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
	/* Link the driver to the stack */
	pdev->pData  = &hpcd_USB_DRD_FS;
	hpcd_USB_DRD_FS.pData = pdev;
	/* Initialize LL Driver */
	MX_USB_PCD_Init();
	/* Control Endpoints */
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x20);
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x60);
	/* HID Endpoints */
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , HID_EPIN_ADDR , PCD_SNG_BUF, 0xA0);
	/* CDC Endpoints */
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, 0xE0);
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, 0x120);
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD_EP , PCD_SNG_BUF, 0x140);
	return USBD_OK;
}
USBD_StatusTypeDef USBD_LL_DeInit(USBD_HandleTypeDef *pdev)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_DeInit(pdev->pData);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_Start(USBD_HandleTypeDef *pdev)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_Start(pdev->pData);
	return  USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_Stop(USBD_HandleTypeDef *pdev)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_Stop(pdev->pData);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_OpenEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr,
		uint8_t ep_type, uint16_t ep_mps)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_Open(pdev->pData, ep_addr, ep_mps, ep_type);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_CloseEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_Close(pdev->pData, ep_addr);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_FlushEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_Flush(pdev->pData, ep_addr);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_StallEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_SetStall(pdev->pData, ep_addr);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_ClearStallEP(USBD_HandleTypeDef *pdev,
		uint8_t ep_addr)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_ClrStall(pdev->pData, ep_addr);
	return USBD_Get_USB_Status(hal_status);
}
uint8_t USBD_LL_IsStallEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
	PCD_HandleTypeDef *hpcd = (PCD_HandleTypeDef*) pdev->pData;
	if((ep_addr & 0x80) == 0x80)
	{
		return hpcd->IN_ep[ep_addr & 0x7F].is_stall;
	}
	else
	{
		return hpcd->OUT_ep[ep_addr & 0x7F].is_stall;
	}
}
USBD_StatusTypeDef USBD_LL_SetUSBAddress(USBD_HandleTypeDef *pdev,
		uint8_t dev_addr)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_SetAddress(pdev->pData, dev_addr);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_Transmit(USBD_HandleTypeDef *pdev, uint8_t ep_addr,
		uint8_t *pbuf, uint32_t size)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_Transmit(pdev->pData, ep_addr, pbuf, size);
	return USBD_Get_USB_Status(hal_status);
}
USBD_StatusTypeDef USBD_LL_PrepareReceive(USBD_HandleTypeDef *pdev,
		uint8_t ep_addr, uint8_t *pbuf,
		uint32_t size)
{
	HAL_StatusTypeDef hal_status;
	hal_status = HAL_PCD_EP_Receive(pdev->pData, ep_addr, pbuf, size);
	return USBD_Get_USB_Status(hal_status);
}
uint32_t USBD_LL_GetRxDataSize(USBD_HandleTypeDef *pdev, uint8_t ep_addr)
{
	return HAL_PCD_EP_GetRxCount((PCD_HandleTypeDef*) pdev->pData, ep_addr);
}
void *USBD_static_malloc(uint32_t size)
{
	UNUSED(size);
	static uint32_t mem[(sizeof(USBD_HID_HandleTypeDef) / 4) + 1]; /* On 32-bit boundary */
	return mem;
}
void USBD_static_free(void *p)
{
	UNUSED(p);
}
void USBD_LL_Delay(uint32_t Delay)
{
     HAL_Delay(Delay);
}

 

 

After filling in all the functions, create the following one:

 

 

USBD_StatusTypeDef USBD_Get_USB_Status(HAL_StatusTypeDef hal_status)
{
	USBD_StatusTypeDef usb_status = USBD_OK;
	switch (hal_status)
	{
	case HAL_OK :
		usb_status = USBD_OK;
		break;
	case HAL_ERROR :
		usb_status = USBD_FAIL;
		break;
	case HAL_BUSY :
		usb_status = USBD_BUSY;
		break;
	case HAL_TIMEOUT :
		usb_status = USBD_FAIL;
		break;
	default :
		usb_status = USBD_FAIL;
		break;
	}
	return usb_status;
}

 

 

All the code for the usbd_conf.c file is done. So, you can now save and close the file. Now, we can move forward to the usbd_desc files. Start renaming the USB/Core/Src/usbd_desc_template.c and USB/Core/Inc/usbd_desc_template.h to usbd_desc.c and usbd_desc.h file.

First, we work with the usbd_desc.h file. In this file, rename the USBD_DescriptorsTypeDef to Class_Desc:

 

 

/* Exported functions ------------------------------------------------------- */
extern USBD_DescriptorsTypeDef Class_Desc; /* Replace 'XXX_Desc' with your active USB device class, ex: HID_Desc */

 

 

You can save and close the file. Then proceed opening the usbd_desc.c file and edit the following defines for your application:

 

 

/* Private define ------------------------------------------------------------*/
#define USBD_VID                      0x0483
#define USBD_PID                      0x5750
#define USBD_LANGID_STRING            0x409
#define USBD_MANUFACTURER_STRING      "STMicroelectronics"
#define USBD_PRODUCT_HS_STRING        "Composite_HID_CDC_CDC in HS Mode"
#define USBD_PRODUCT_FS_STRING        "Composite_HID_CDC_CDC in FS Mode"
#define USBD_CONFIGURATION_HS_STRING  "Composite_HID_CDC_CDC Config"
#define USBD_INTERFACE_HS_STRING      "Composite_HID_CDC_CDC Interface"
#define USBD_CONFIGURATION_FS_STRING  "Composite_HID_CDC_CDC Config"
#define USBD_INTERFACE_FS_STRING      "Composite_HID_CDC_CDC Interface"

 

 

For the next step, it is necessary to rename the USB /Class/CDC/Inc/usbd_cdc_if_template.h file to usbd_cdc_if.h and the USB /Class/CDC/Inc/usbd_cdc_if_template.c file to usbd_cdc_if.c.

Open the usbd_cdc_if.c file to create the Tx and Rx buffers. Import some variables used by the application (these two variables will be created later at the main.c file):

 

 

extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint8_t CDC_InstID;
uint8_t UserTxBuffer[64] = "MY CDC is Working!\r\n";
uint8_t UserRxBuffer[64];

 

 

You can rename all the functions available in the usbd_cdc_if.c from TEMPLATE_xx to the most convenient name for your application. This file was created in this way to allow you to handle more than one CDC instance if needed.

Add the following code into the TEMPLATE_Init function to configure the Tx and Rx buffers:

 

 

static int8_t TEMPLATE_Init(void)
{
	hUsbDeviceFS.classId = CDC_InstID;
	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBuffer, sizeof("MY CDC is Working!\r\n"), CDC_InstID);
	USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBuffer);
	return (USBD_OK);
}

 

 

Finally, add the code to the TEMPLATE_Receive function. In this function, you may add your code to handle the data reception in your application. Here we also need to prepare the USB to receive the next packages, for that the code presented below is needed:

 

 

static int8_t TEMPLATE_Receive(uint8_t *Buf, uint32_t *Len)
{
	/* Add your RX code here */
	USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
	USBD_CDC_ReceivePacket(&hUsbDeviceFS);
	return (USBD_OK);
}

 

 

Then, open the main.c file to start creating the application.  The first step in this file is to add the following includes:

 

 

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usbd_core.h"
#include "usbd_cdc.h"
#include "usbd_cdc_if.h"
#include "usbd_hid.h"
#include "usbd_desc.h"
#include "usbd_composite_builder.h"
/* USER CODE END Includes */

 

 

Create the variables presented in the code section below. There are two variables to store the addresses of the classes (CDC_EpAdd_Inst and HID_EpAdd_Inst). The USB device handle typedef required by the USB device application, a buffer for the HID report, and finally two variables to store the number of the instance for each used class (HID_InstID and CDC_InstID).

 

 

/* USER CODE BEGIN PV */
uint8_t CDC_EpAdd_Inst[3] = {CDC_IN_EP, CDC_OUT_EP, CDC_CMD_EP}; 	/* CDC Endpoint Addresses array */
uint8_t HID_EpAdd_Inst = HID_EPIN_ADDR;								/* HID Endpoint Address array */
USBD_HandleTypeDef hUsbDeviceFS;
uint8_t hid_report_buffer[4];
uint8_t HID_InstID = 0, CDC_InstID = 0;
/* USER CODE END PV */

 

 

Fill the HID report buffer within the main function with the following information (this setting should move the cursor 100 units to the right):

 

 

/* USER CODE BEGIN 1 */
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*/
/* USER CODE END 1 */

 

 

Scroll down to the second user code section (USER CODE BEGIN 2), and add the following code to initialize the USB peripheral. Stack and register all the used classes:

 

 

/* USER CODE BEGIN 2 */
/* Initialize the USB Device Library */
if(USBD_Init(&hUsbDeviceFS, &Class_Desc, 0) != USBD_OK)
	Error_Handler();
/* Store HID Instance Class ID */
HID_InstID = hUsbDeviceFS.classId;
/* Register the HID Class */
if(USBD_RegisterClassComposite(&hUsbDeviceFS, USBD_HID_CLASS, CLASS_TYPE_HID, &HID_EpAdd_Inst) != USBD_OK)
	Error_Handler();
/* Store the HID Class */
CDC_InstID = hUsbDeviceFS.classId;
/* Register CDC Class First Instance */
if(USBD_RegisterClassComposite(&hUsbDeviceFS, USBD_CDC_CLASS, CLASS_TYPE_CDC, CDC_EpAdd_Inst) != USBD_OK)
	Error_Handler();
/* Add CDC Interface Class */
if (USBD_CMPSIT_SetClassID(&hUsbDeviceFS, CLASS_TYPE_CDC, 0) != 0xFF)
{
	USBD_CDC_RegisterInterface(&hUsbDeviceFS, &USBD_CDC_Template_fops);
}
USBD_Start(&hUsbDeviceFS);
/* USER CODE END 2 */

 

 

Let us create a simple code to test our application. To do this, fill the while loop with the code presented below. It monitors the User Button and when it is pressed, it sends the HID report and a message through the CDC port.

 

 

/* USER CODE BEGIN WHILE */
while (1)
{
	if(HAL_GPIO_ReadPin(USER_BT_GPIO_Port, USER_BT_Pin) == GPIO_PIN_SET)
	{
		USBD_HID_SendReport(&hUsbDeviceFS, hid_report_buffer, 4, HID_InstID);
		USBD_CDC_TransmitPacket(&hUsbDeviceFS, CDC_InstID);
		HAL_Delay(100);
	}
/* USER CODE END WHILE */

 

 

Now, all the code implementation is done! You can build and flash the code into your device and try it!

2. Results

After flashing the code, connect a USB Type-C® cable into the User USB Connector and connect to a computer. Once connected, a USB Composite device must be enumerated with the two classes HID and CDC. Open a terminal to observe the message being transmitted and the mouse being moved when the button is pressed.

Figure 15. Demo working - Tera TermFigure 15. Demo working - Tera Term

Figure 16. Composite Device recognized by host - USB Tree ViewFigure 16. Composite Device recognized by host - USB Tree View

3. Conclusion

And that concludes our article. Now, you have the necessary knowledge to implement a composite class on an STM32 using our legacy USB middleware. Here we presented the step-by-step to construct an HID + CDC, but the steps for implementing other classes should be similar.

For details on how to use/implement the other device classes, refer to our USB Composite GitHub page. Within this page, you can find a large set of examples showing the usage of the most classes available.

Best wishes for your developments and hope you enjoyed this material!

Related links

Here are some useful links that contain material, which was used to build this article and can be helpful in your developments.

NUCLEO-H503RB - STM32 Nucleo-64 development board with STM32H503RBT6 MCU, supports Arduino and ST Morpho connectivity - STMicroelectronics

STM32H5 Nucleo-64 board (MB1814) - User manual

STM32H503RB - High-performance, Arm Cortex-M33, MCU with 128-Kbyte Flash, 32-Kbyte RAM, 250 MHz CPU - STMicroelectronics

GitHub - STMicroelectronics/stm32_mw_usb_device

ST Wiki - Introduction to USB with STM32

MOOC - STM32 USB Training

Comments

Hi @D.Botelho 
Do you have a composite example with Audio in/out?

The usb libraries provided by ST only have audio out.

D.Botelho
ST Employee

Hello, @Zhou JianQiang 

Unfortunately, we don't have any example showing this specific feature.

 

Version history
Last update:
‎2024-09-03 05:25 AM
Updated by: