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.

 

abr
Associate II

Hello @D.Botelho 

I am trying to implement a similar composite device but with several CDC interfaces and no HID interfaces.

Is there any example I could use or could you point me to some useful resources?

 

Thank you.

D.Botelho
ST Employee

Hi @abr 

 

Please, check our new article which cover this topic:

How to implement a dual CDC ACM USB device using t... - STMicroelectronics Community

 

Hope it helps!

Best Regards

Dan

 

abr
Associate II

Thank you @D.Botelho !

 

It helped a bit but I am using a H5 microcontroller so I was using the middleware mentioned in this guide. With the help of the tutorial on the link you posted, I was able to create the USB device with two CDC interfaces. However I cannot reliably send data using USBD_CDC_TransmitPacket().

Should I modify the buffers with USBD_CDC_SetTxBuffer() and the hUsbDeviceFS.classId before transmiting or does the transmit function manage all that since the CDC_Instance is passed as a parameter?

Kind regards

Thatseasy
Associate III

I was running USB_CLASSIC_CDC_H503, not the example associated with this article. Had the issue that USB_DRD_FS_IRQHandler() was not called, and it turned out to be the USB GPIO configuration issue.

Thatseasy
Associate III

Never mind, figured it out the USB_DRD_FS_IRQHandler() not called issue.

D.Botelho
ST Employee

Hello @abr,

 

Please see the code below. It's an example on how to handle multiple CDC interfaces using our legacy USB Middleware.

 

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define APP_RX_DATA_SIZE  2048
#define APP_TX_DATA_SIZE  2048

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
USBD_CDC_LineCodingTypeDef LineCoding =
{
  115200, /* baud rate*/
  0x00,   /* stop bits-1*/
  0x00,   /* parity - none*/
  0x08    /* nb. of bits 8*/
};

/* CDC Instance 0 Variables */
uint8_t UserRxBuffer0[APP_RX_DATA_SIZE];/* Received Data over USB are stored in this buffer */
uint8_t UserTxBuffer0[APP_TX_DATA_SIZE];/* Received Data over UART (CDC interface) are stored in this buffer */
uint32_t BuffLength0;
uint32_t UserTxBufPtrIn0 = 0;/* Increment this pointer or roll it back to
                               start address when data are received over USART */
uint32_t UserTxBufPtrOut0 = 0; /* Increment this pointer or roll it back to
                                 start address when data are sent over USB */
/* UART handler declaration */
UART_HandleTypeDef UartHandle;
/* TIM handler declaration */
TIM_HandleTypeDef  TimHandle;


/* CDC Instance 1 Variables */
uint8_t UserRxBuffer1[APP_RX_DATA_SIZE];/* Received Data over USB are stored in this buffer */
uint8_t UserTxBuffer1[APP_TX_DATA_SIZE];/* Received Data over UART (CDC interface) are stored in this buffer */
uint32_t BuffLength1;
uint32_t UserTxBufPtrIn1 = 0;/* Increment this pointer or roll it back to
                               start address when data are received over USART */
uint32_t UserTxBufPtrOut1 = 0; /* Increment this pointer or roll it back to
                                 start address when data are sent over USB */


/* USB handler declaration */
extern USBD_HandleTypeDef  USBD_Device;

/* Private function prototypes -----------------------------------------------*/
static int8_t CDC_Itf_Init(void);
static int8_t CDC_Itf_DeInit(void);
static int8_t CDC_Itf_Control(uint8_t cmd, uint8_t* pbuf, uint16_t length);
static int8_t CDC_Itf_Receive(uint8_t* pbuf, uint32_t *Len);
static int8_t CDC_Itf_TransmitCplt(uint8_t *pbuf, uint32_t *Len, uint8_t epnum);

static void Error_Handler(void);
static void ComPort_Config(void);
static void TIM_Config(void);

USBD_CDC_ItfTypeDef USBD_CDC_fops =
{
  CDC_Itf_Init,
  CDC_Itf_DeInit,
  CDC_Itf_Control,
  CDC_Itf_Receive,
  CDC_Itf_TransmitCplt
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  CDC_Itf_Init
  *         Initializes the CDC media low layer
  * @param  None
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Itf_Init(void)
{
  /* Check if the class ID corresponds to instance 0 */
  if (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 0))
  {
    /*##-1- Configure the UART peripheral ######################################*/
    /* Put the USART peripheral in the Asynchronous mode (UART Mode) */
    /* USART configured as follow:
    - Word Length = 8 Bits
    - Stop Bit    = One Stop bit
    - Parity      = No parity
    - BaudRate    = 115200 baud
    - Hardware flow control disabled (RTS and CTS signals) */
    UartHandle.Instance          = USARTx;
    UartHandle.Init.BaudRate     = 115200;
    UartHandle.Init.WordLength   = UART_WORDLENGTH_8B;
    UartHandle.Init.StopBits     = UART_STOPBITS_1;
    UartHandle.Init.Parity       = UART_PARITY_NONE;
    UartHandle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    UartHandle.Init.Mode         = UART_MODE_TX_RX;
    UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;

    if(HAL_UART_Init(&UartHandle) != HAL_OK)
    {
      /* Initialization Error */
      Error_Handler();
    }

    /*##-2- Put UART peripheral in IT reception process ########################*/
    /* Any data received will be stored in "UserTxBuffer" buffer  */
    if(HAL_UART_Receive_IT(&UartHandle, (uint8_t *)UserTxBuffer0, 1) != HAL_OK)
    {
      /* Transfer error in reception process */
      Error_Handler();
    }

    /*##-3- Configure the TIM Base generation  #################################*/
    TIM_Config();

    /*##-4- Start the TIM Base generation in interrupt mode ####################*/
    /* Start Channel1 */
    if(HAL_TIM_Base_Start_IT(&TimHandle) != HAL_OK)
    {
      /* Starting Error */
      Error_Handler();
    }

    /*##-5- Set Application Buffers ############################################*/
#ifdef USE_USB_DEVICE_LIB_V2_11_0
    USBD_CDC_SetTxBuffer(&USBD_Device, UserTxBuffer0, 0,0);
#else
    USBD_CDC_SetTxBuffer(&USBD_Device, UserTxBuffer0, 0);
#endif
    USBD_CDC_SetRxBuffer(&USBD_Device, UserRxBuffer0);

    return (USBD_OK);
  }
  /* Check if the class ID corresponds to instance 1 */
  else if (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 1))
  {
    /* No hardware initialization required: virtual UART */

    /* Set Application Buffers */
#ifdef USE_USB_DEVICE_LIB_V2_11_0
    USBD_CDC_SetTxBuffer(&USBD_Device, UserTxBuffer1, 0,1);
#else
    USBD_CDC_SetTxBuffer(&USBD_Device, UserTxBuffer1, 0);
#endif
    USBD_CDC_SetRxBuffer(&USBD_Device, UserRxBuffer1);

    return (USBD_OK);
  }
  else
  {
    /* Error: no instancecorresponds to this class ID */
     return USBD_FAIL;
  }
}

/**
  * @brief  CDC_Itf_DeInit
  *         DeInitializes the CDC media low layer
  * @param  None
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Itf_DeInit(void)
{
  /* Check if the class ID corresponds to instance 0 */
  if (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 0))
  {
    /* DeInitialize the UART peripheral */
    if(HAL_UART_DeInit(&UartHandle) != HAL_OK)
    {
      /* Initialization Error */
      Error_Handler();
    }
    return (USBD_OK);
  }
  /* Check if the class ID corresponds to instance 1 */
  else if (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 1))
  {
    /* Reset pointers */
    BuffLength1 = 0;
    UserTxBufPtrIn1 = 0;
    UserTxBufPtrOut1 = 0;

    return (USBD_OK);
  }
  else
  {
    /* Error: no instancecorresponds to this class ID */
     return USBD_FAIL;
  }
}

/**
  * @brief  CDC_Itf_Control
  *         Manage the CDC class requests
  * @param  Cmd: Command code
  * @param  Buf: Buffer containing command data (request parameters)
  * @param  Len: Number of data to be sent (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Itf_Control (uint8_t cmd, uint8_t* pbuf, uint16_t length)
{
  /* Same processing done for both  */

  switch (cmd)
  {
  case CDC_SEND_ENCAPSULATED_COMMAND:
    /* Add your code here */
    break;

  case CDC_GET_ENCAPSULATED_RESPONSE:
    /* Add your code here */
    break;

  case CDC_SET_COMM_FEATURE:
    /* Add your code here */
    break;

  case CDC_GET_COMM_FEATURE:
    /* Add your code here */
    break;

  case CDC_CLEAR_COMM_FEATURE:
    /* Add your code here */
    break;

  case CDC_SET_LINE_CODING:
    LineCoding.bitrate    = (uint32_t)(pbuf[0] | (pbuf[1] << 8) |\
                            (pbuf[2] << 16) | (pbuf[3] << 24));
    LineCoding.format     = pbuf[4];
    LineCoding.paritytype = pbuf[5];
    LineCoding.datatype   = pbuf[6];

    /* Manage HW only for instance 0 */
    if (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 0))
    {
      /* Set the new configuration */
      ComPort_Config();
    }
    break;

  case CDC_GET_LINE_CODING:
    pbuf[0] = (uint8_t)(LineCoding.bitrate);
    pbuf[1] = (uint8_t)(LineCoding.bitrate >> 8);
    pbuf[2] = (uint8_t)(LineCoding.bitrate >> 16);
    pbuf[3] = (uint8_t)(LineCoding.bitrate >> 24);
    pbuf[4] = LineCoding.format;
    pbuf[5] = LineCoding.paritytype;
    pbuf[6] = LineCoding.datatype;
    break;

  case CDC_SET_CONTROL_LINE_STATE:
    /* Add your code here */
    break;

  case CDC_SEND_BREAK:
     /* Add your code here */
    break;

  default:
    break;
  }

  return (USBD_OK);
}

/**
  * @brief  TIM period elapsed callback
  * @param  htim: TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  uint32_t buffptr;
  uint32_t buffsize;

  /* Manage CDC Instance 0 ---------------------------------------------------*/
  if(UserTxBufPtrOut0 != UserTxBufPtrIn0)
  {
    if(UserTxBufPtrOut0 > UserTxBufPtrIn0) /* Roll-back */
    {
      buffsize = APP_RX_DATA_SIZE - UserTxBufPtrOut0;
    }
    else
    {
      buffsize = UserTxBufPtrIn0 - UserTxBufPtrOut0;
    }

    buffptr = UserTxBufPtrOut0;

    /* Set the interface instance */
    USBD_CMPSIT_SetClassID(&USBD_Device, CLASS_TYPE_CDC, 0);
#ifdef USE_USB_DEVICE_LIB_V2_11_0
    USBD_CDC_SetTxBuffer(&USBD_Device, (uint8_t*)&UserTxBuffer0[buffptr], buffsize,0);

    if(USBD_CDC_TransmitPacket(&USBD_Device,0) == USBD_OK)
#else
    USBD_CDC_SetTxBuffer(&USBD_Device, (uint8_t*)&UserTxBuffer0[buffptr], buffsize);

    if(USBD_CDC_TransmitPacket(&USBD_Device) == USBD_OK)
#endif
    {
      UserTxBufPtrOut0 += buffsize;
      if (UserTxBufPtrOut0 == APP_RX_DATA_SIZE)
      {
        UserTxBufPtrOut0 = 0;
      }
    }
  }

  /* Manage CDC Instance 1 ---------------------------------------------------*/
  if(UserTxBufPtrOut1 != UserTxBufPtrIn1)
  {
    if(UserTxBufPtrOut1 > UserTxBufPtrIn1) /* Roll-back */
    {
      buffsize = APP_RX_DATA_SIZE - UserTxBufPtrOut1;
    }
    else
    {
      buffsize = UserTxBufPtrIn1 - UserTxBufPtrOut1;
    }

    buffptr = UserTxBufPtrOut1;

    /* Set the interface instance */
    USBD_CMPSIT_SetClassID(&USBD_Device, CLASS_TYPE_CDC, 1);

#ifdef USE_USB_DEVICE_LIB_V2_11_0
    USBD_CDC_SetTxBuffer(&USBD_Device, (uint8_t*)&UserTxBuffer1[buffptr], buffsize,1);
    
    if(USBD_CDC_TransmitPacket(&USBD_Device,1) == USBD_OK)
#else
    USBD_CDC_SetTxBuffer(&USBD_Device, (uint8_t*)&UserTxBuffer1[buffptr], buffsize);
    
    if(USBD_CDC_TransmitPacket(&USBD_Device) == USBD_OK)
#endif
    {
      UserTxBufPtrOut1 += buffsize;
      if (UserTxBufPtrOut1 == APP_RX_DATA_SIZE)
      {
        UserTxBufPtrOut1 = 0;
      }
    }
  }
}

/**
  * @brief  Rx Transfer completed callback
  * @param  huart: UART handle
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Increment Index for buffer writing */
  UserTxBufPtrIn0++;

  /* To avoid buffer overflow */
  if(UserTxBufPtrIn0 == APP_RX_DATA_SIZE)
  {
    UserTxBufPtrIn0 = 0;
  }

  /* Start another reception: provide the buffer pointer with offset and the buffer size */
  HAL_UART_Receive_IT(huart, (uint8_t *)(UserTxBuffer0 + UserTxBufPtrIn0), 1);
}

/**
  * @brief  CDC_Itf_DataRx
  *         Data received over USB OUT endpoint are sent over CDC interface
  *         through this function.
  * @param  Buf: Buffer of data to be transmitted
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len)
{
  /* Check the instance on which the call is done */
  if (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 0))
  {
    HAL_UART_Transmit_DMA(&UartHandle, Buf, *Len);

    return (USBD_OK);
  }
  else if  (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 1))
  {
    /* Loopback for virtual com port: copy received buffer in transmit buffer.
       In case of physical UART, this loop shall be replaced by physical DMA transmit as above */
    for (uint32_t i = 0; i < *Len; i++)
    {
      /* Copy the byte */
      UserTxBuffer1[UserTxBufPtrIn1++] = Buf[i];

      /* To avoid buffer overflow: */
      if(UserTxBufPtrIn1 == APP_RX_DATA_SIZE)
      {
        UserTxBufPtrIn1 = 0;
      }
    }

    return (USBD_OK);
  }
  else
  {
    return (USBD_FAIL);
  }


}

/**
  * @brief  CDC_Itf_TransmitCplt
  *         Data transmited callback
  *
  * @note
  *         This function is IN transfer complete callback used to inform user that
  *         the submitted Data is successfully sent over USB.
  *
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Itf_TransmitCplt(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
{
  /* Check if this call is relative to the instance 1 */
  if  (USBD_Device.classId == USBD_CMPSIT_GetClassID(&USBD_Device, CLASS_TYPE_CDC, 1))
  {
    /* Initiate next USB packet transfer */
    USBD_CDC_ReceivePacket(&USBD_Device);
  }

  return (0);
}

/**
  * @brief  Tx Transfer completed callback
  * @param  huart: UART handle
  * @retval None
  */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Initiate next USB packet transfer once UART completes transfer (transmitting data over Tx line) */
  USBD_CDC_ReceivePacket(&USBD_Device);
}

/**
  * @brief  ComPort_Config
  *         Configure the COM Port with the parameters received from host.
  * @param  None.
  * @retval None
  * @note   When a configuration is not supported, a default value is used.
  */
static void ComPort_Config(void)
{
  if(HAL_UART_DeInit(&UartHandle) != HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }

  /* set the Stop bit */
  switch (LineCoding.format)
  {
  case 0:
    UartHandle.Init.StopBits = UART_STOPBITS_1;
    break;
  case 2:
    UartHandle.Init.StopBits = UART_STOPBITS_2;
    break;
  default :
    UartHandle.Init.StopBits = UART_STOPBITS_1;
    break;
  }

  /* set the parity bit*/
  switch (LineCoding.paritytype)
  {
  case 0:
    UartHandle.Init.Parity = UART_PARITY_NONE;
    break;
  case 1:
    UartHandle.Init.Parity = UART_PARITY_ODD;
    break;
  case 2:
    UartHandle.Init.Parity = UART_PARITY_EVEN;
    break;
  default :
    UartHandle.Init.Parity = UART_PARITY_NONE;
    break;
  }

  /*set the data type : only 8bits and 9bits is supported */
  switch (LineCoding.datatype)
  {
  case 0x07:
    /* With this configuration a parity (Even or Odd) must be set */
    UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
    break;
  case 0x08:
    if(UartHandle.Init.Parity == UART_PARITY_NONE)
    {
      UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
    }
    else
    {
      UartHandle.Init.WordLength = UART_WORDLENGTH_9B;
    }

    break;
  default :
    UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
    break;
  }

  UartHandle.Init.BaudRate     = LineCoding.bitrate;
  UartHandle.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
  UartHandle.Init.Mode         = UART_MODE_TX_RX;
  UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;

  if(HAL_UART_Init(&UartHandle) != HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }

  /* Start reception: provide the buffer pointer with offset and the buffer size */
  HAL_UART_Receive_IT(&UartHandle, (uint8_t *)(UserTxBuffer0 + UserTxBufPtrIn0), 1);
}

/**
  * @brief  TIM_Config: Configure TIMx timer
  * @param  None.
  * @retval None
  */
static void TIM_Config(void)
{
  /* Set TIMx instance */
  TimHandle.Instance = TIMx;

  /* Initialize TIM3 peripheral as follow:
       + Period = 10000 - 1
       + Prescaler = ((SystemCoreClock/2)/10000) - 1
       + ClockDivision = 0
       + Counter direction = Up
  */
  TimHandle.Init.Period = (CDC_POLLING_INTERVAL*1000) - 1;
  TimHandle.Init.Prescaler = 84-1;
  TimHandle.Init.ClockDivision = 0;
  TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
  TimHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if(HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }
}

/**
  * @brief  UART error callbacks
  * @param  UartHandle: UART handle
  * @retval None
  */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *UartHandle)
{
  /* Transfer error occurred in reception and/or transmission process */
  Error_Handler();
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @param  None
  * @retval None
  */
static void Error_Handler(void)
{
  /* Add your own code here */
}

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
D.Botelho
ST Employee

Hi @Thatseasy ,

 

Could you share how you solved your problem? It may be helpful for people who are facing similar problems.

 

Thank you and Best Regards,

Dan

abr
Associate II

Hello @D.Botelho,

 

I think I have found the problem. Right now, my code is very similar to the one you sent. I do not use neither a circular buffer nor a UART interface. I just want to send periodically to the two CDC interfaces. In the following way:

strcpy(UserTxBuffer_1,"test1\r\n");
	  	USBD_CMPSIT_SetClassID(&hUsbDeviceFS, CLASS_TYPE_CDC, CDC_InstID_1);
	  	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBuffer_1, sizeof(UserTxBuffer_1), CDC_InstID_1);
		result=USBD_CDC_TransmitPacket(&hUsbDeviceFS, CDC_InstID_1);
		HAL_Delay(1000);
		strcpy(UserTxBuffer_2,"test2\r\n");
	  	USBD_CMPSIT_SetClassID(&hUsbDeviceFS, CLASS_TYPE_CDC, CDC_InstID_2);
	  	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBuffer_2, sizeof(UserTxBuffer_2), CDC_InstID_2);
		result=USBD_CDC_TransmitPacket(&hUsbDeviceFS, CDC_InstID_2);
		HAL_Delay(1000);

 The issue is that I have to read the data from CDC_InstID_1 before receiving the data from CDC_InstID_2.

I have traced the issue to the USB_CDC_TransmitPacket.

/**
  * @brief  USBD_CDC_TransmitPacket
  *         Transmit packet on IN endpoint
  * @param  pdev: device instance
  * @param  ClassId: The Class ID
  * @retval status
  */
#ifdef USE_USBD_COMPOSITE
uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev, uint8_t ClassId)
{
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)pdev->pClassDataCmsit[ClassId];
#else
uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)
{
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)pdev->pClassDataCmsit[pdev->classId];
#endif  /* USE_USBD_COMPOSITE */

  USBD_StatusTypeDef ret = USBD_BUSY;

#ifdef USE_USBD_COMPOSITE
  /* Get the Endpoints addresses allocated for this class instance */
  CDCInEpAdd  = USBD_CoreGetEPAdd(pdev, USBD_EP_IN, USBD_EP_TYPE_BULK, ClassId);
#endif  /* USE_USBD_COMPOSITE */

  if (hcdc == NULL)
  {
    return (uint8_t)USBD_FAIL;
  }

  if (hcdc->TxState == 0U)
  {
    /* Tx Transfer in progress */
    hcdc->TxState = 1U;

    /* Update the packet total length */
    pdev->ep_in[CDCInEpAdd & 0xFU].total_length = hcdc->TxLength;

    /* Transmit next packet */
    (void)USBD_LL_Transmit(pdev, CDCInEpAdd, hcdc->TxBuffer, hcdc->TxLength);

    ret = USBD_OK;
  }

  return (uint8_t)ret;
}

Here the information in pClassDataCmsit[ClassId] is used to check if TxState == 0.

However with the provided code both pClassDataCmsit[0] and pClassDataCmsit[1] are not initialized.

I am doing something wrong or is this the expecte behaviour? 

 

Kind regards for the assistance.

D.Botelho
ST Employee

Hello @abr ,

 

This variable is filled withing the USBD_CDC_Init function. If you are being able to send data through the interface, I strongly believe that it`s being properly called. So, at this point I can suggest you increase the heap and size of your application to check if this variable is being properly filled.

 

Best Regards,

Daniel

abr
Associate II

Hello @D.Botelho,

The issue was the use of USBD_malloc during the USBD_CDC_Init. It assigned the same memory address for the two interfaces.

 

Modifying USBD_CDC_Init in the following way solved it (I attach only the modified part)

USBD_CDC_HandleTypeDef h0;
USBD_CDC_HandleTypeDef h1;

static uint8_t USBD_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
  UNUSED(cfgidx);
  USBD_CDC_HandleTypeDef *hcdc;

  //hcdc = (USBD_CDC_HandleTypeDef *)USBD_malloc(sizeof(USBD_CDC_HandleTypeDef));
  if(pdev->classId==0) hcdc = &h0;
  if(pdev->classId==1) hcdc = &h1;

 

I know this function is supposed to allocate memory dynamically and this solution does not, but I could not find any other way to solve it. In my case, I want to have eight different interfaces that can all work at the same time so I will not need to allocate memory dynamically, but I feel like there should be a way to do it and allocate different memory for each interface. Is it that I am using wrong the library or is it a known limitation?

abr
Associate II

Hello @D.Botelho ,

 

Now that I have solved the problem with having multiple CDC interfaces working at the same time I would also like to add a DFU interface so that the firmware can be updated without having to reset the microcontroller and controlling the BOOT pins value.

I have managed to add the DFU interface and in the host I see two descriptors related to DFU. A Device Firmware Upgrade Interface Descriptor and a regular interface descriptor with  bInterfaceClass 254 (Application Specific Interface) and bInterfaceSubClass 1 (Device Firmware Update) but I cannot upload a new firmware using STM32CubeProgrammer for example.

When I connect the BOOT pins to enable DFU boot mode, apart from the aforementioned descriptors, I see three more Application Specific Interface descriptors, all of them  of type Device Firmware Update.

What am I missing?

I am using the DFU class of the USB folder mentioned in this tutorial and using https://github.com/STMicroelectronics/STM32CubeF2/tree/master/Projects/NUCLEO-F207ZG/Applications/USB_Device/DFU_Standalone as reference to add the code required in main.c.

 

I have also seen that a jump to the bootloader can be done from the aplication: https://community.st.com/t5/stm32-mcus/how-to-jump-to-system-bootloader-from-application-code-on-stm32/ta-p/49424

but I feel like this is what the DFU class should do. Or am I wrong and that is something I also have to implement?

 

Regards,

 

Asier

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