cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement a USB composite device application with MSC and HID classes

Hamdi_TEYEB
ST Employee

Introduction

This article provides a step-by-step guide to implementing a composite USB device that combines mass storage class (MSC) and human interface device (HID) functionalities on an STM32 platform. Such a device is ideal for applications requiring both user interaction (for example, a mouse) and data storage (using SRAM). Using STM32CubeIDE, STM32CubeMX, and USB middleware, this tutorial demonstrates how to configure and implement this composite device.

For this article, the NUCLEO-U545RE-Q board is used to implement this application and can be easily tailored to any other STM32. This board features an STM32U545RET6Q microcontroller. For further details about the board, refer to the user manual.

STM32U5 Nucleo-64 boardSTM32U5 Nucleo-64 board                                                

1. Project setup

You can directly clone the project from STM32 Hotspot GitHub or follow the steps below to set up your project.

1.1 Create a project

Start a new project in STM32CubeIDE by selecting File  New STM32 Project. Navigate to the [Board Selector] tab and choose [NUCLEO-U545RE-Q].

2.png

After clicking the [Next] button, tick the [USER B1] box in the Board project options.

Board project optionsBoard project options

After creating the project, navigate to the [Connectivity] section, enable the USB peripheral [USB_DRD_FS] in [Device Only mode], and activate the USB global interrupt in the NVIC settings.

4.png

The final step before generating the code is to update the system clock frequency. According to the reference manual, the USB peripheral requires a minimum clock frequency of 48 MHz to avoid data overrun and underrun issues. For this example, the clock frequency is set to the maximum allowable value to achieve optimal performance.

5.png

Afterwards, the instruction cache (ICACHE) must be disabled in our project.

TEYEB_5-1751274330122.png

After generating the code, you must manually add the composite builder class, MSC class, and HID class source files to the project. Since the STM32U5 product is native to AzureRTOS, CubeMX does not automatically generate the middleware layer.

1.2 Add the middleware layer

First, add new folders named Middlewares and USB DEVICE to the project workspace directory.

6.png

After that, refresh the project to update and include the newly added folders.

Added foldersAdded folders

Once the project architecture is complete, if the legacy USB middleware is not available on your PC, download it from STMicroelectronics - GitHub /stm32_mw_usb_device.

The table below lists the files from the USB middleware that must be added to the project:

USB middleware File name  Source of files   Destination 
USB Core  usbd_core.c Core/Src  Middlewares/Core/Src 
usbd_ctlreq.c
usbd_ioreq.c
usbd_core.h Core/Inc  Middlewares/Core/Inc 
usbd_ctlreq.h
usbd_ioreq.h
Class HID usbd_hid.h Class/HID Middlewares/Class/HID/Inc 
usbd_hid.c Middlewares/Class/HID/Src 
Class MSC usbd_msc.c Class/MSC  Middlewares/Class/MSC/Inc 
usbd_msc_bot.c
usbd_msc_data.c
usbd_msc_scsi.c
usbd_msc.h Middlewares/Class/MSC/Src 
usbd_msc_bot.h
usbd_msc_data.h
usbd_msc_scsi.h
Class CompositeBuilder  usbd_composite_builder.c Class/CompositeBuilder Middlewares/Class/Compositebuilder/Src  
usbd_composite_builder.h Middlewares/Class/Compositebuilder/Inc   

And these are the source files modified for my specific application. You can directly clone them from your application.

File name  Source of files  Destination 
usbd_def.h usbd_def.h  USB Device/App/Inc 
usbd_desc.h usbd_desc.h 
usbd_desc.c
usbd_desc.c  USB Device/App/Src 
usbd_conf.h
usbd_conf.h  USB Device/Target/Inc 
usbd_conf.c
usbd_conf.c  USB Device/Target/Src 
usbd_storage.h 
usbd_storage.h  Core/Inc 
usbd_storage.c  usbd_storage.c  Core/Src 

After adding all the files, Under Properties → C/C++ General → Paths and Symbolsensure to add related the file source under [Source Location].

Build configurationBuild configuration

Under [Includes], add the middleware header files.

Build configurationBuild configuration

After adding all the necessary files and including them in the compilation toolchain, Proceed to modify the necessary files for the application.

2. Develop your own project

2.1 Interface file

8.png

The files usbd_storage.h and usbd_storage.c are essential for implementing the USB mass storage class functionality. In these files, the size of the memory space is fixed, and the functions for writing and reading are defined.

#define STORAGE_LUN_NBR                  1
#define STORAGE_BLK_NBR                  300
#define STORAGE_BLK_SIZ                  0x200
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint8_t buffer[STORAGE_BLK_NBR*STORAGE_BLK_SIZ];

The buffer size, defined as buffer[STORAGE_BLK_NBR * STORAGE_BLK_SIZ], directly affects the USB mass storage class memory usage in SRAM. In our application, it is set to approximately 150 KB out of the total 274 KB available SRAM.

Using the STORAGE_Read function, you can efficiently read data from the internal storage buffer and transfer it to the USB host.

int8_t STORAGE_Read(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(lun);
  memcpy(buf, &buffer[blk_addr*STORAGE_BLK_SIZ], blk_len*STORAGE_BLK_SIZ);
  return (USBD_OK);
}

Using the STORAGE_Write function, the data received from the USB host is written to the internal storage buffer.

int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(lun);
  memcpy(&buffer[blk_addr*STORAGE_BLK_SIZ], buf, blk_len*STORAGE_BLK_SIZ);
  return (USBD_OK);
}

These are the essential configurations for memory manipulation in the mass storage application. You can download the complete file from the table or directly through this GitHub link.

2.2 USB configuration

Add the necessary configurations in theusbd_conf.hfile to enable the composite class, including both MSC and HID.

  1. Activate the classes

/* Activate the composite builder */
#define USE_USBD_COMPOSITE

/* Activate HID and MSC classes in composite builder */
#define USBD_CMPSIT_ACTIVATE_HID					1U
#define USBD_CMPSIT_ACTIVATE_MSC					1U

    2. Set HID and MSC endpoints

  • For HID, configure only the IN endpoint address.
  • For MSC, configure both the IN and OUT endpoint addresses.

Ensure that the endpoint addresses for HID and MSC are different to prevent conflicts.

/*Set HID and MSC endpoints*/
#define      HID_EPIN_ADDR                   0x81U

#define      MSC_EPIN_ADDR                   0x82U
#define      MSC_EPOUT_ADDR                  0x01U

    3. Set the media packet for MSC 

Setup of this media packet ensures efficient reading and writing of data blocks during USB communication

/* MSC Class Config */
#define MSC_MEDIA_PACKET                       512U

    4. Configure the USBD_LL_Init function 

In the file usbd_conf.c, scroll down to the definition of the function USBD_LL_Init and add the following code

USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
	hpcd_USB_DRD_FS.Instance = USB_DRD_FS;
	hpcd_USB_DRD_FS.Init.dev_endpoints = 8;
	hpcd_USB_DRD_FS.Init.speed = PCD_SPEED_FULL;
	hpcd_USB_DRD_FS.Init.phy_itface = PCD_PHY_EMBEDDED;
	hpcd_USB_DRD_FS.Init.Sof_enable = DISABLE;
	hpcd_USB_DRD_FS.Init.low_power_enable = DISABLE;
	hpcd_USB_DRD_FS.Init.lpm_enable = DISABLE;
	hpcd_USB_DRD_FS.Init.battery_charging_enable = DISABLE;
	hpcd_USB_DRD_FS.Init.vbus_sensing_enable = DISABLE;
	hpcd_USB_DRD_FS.Init.bulk_doublebuffer_enable = DISABLE;
	hpcd_USB_DRD_FS.Init.iso_singlebuffer_enable = DISABLE;
	HAL_PCD_Init(&hpcd_USB_DRD_FS) ;
	/* Link the driver to the stack */
	pdev->pData  = &hpcd_USB_DRD_FS;
	hpcd_USB_DRD_FS.pData = pdev;


	/* 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, 0x40);

	/* HID Endpoints */
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , HID_EPIN_ADDR , PCD_SNG_BUF, 0x50);

	/* MSC Endpoints */
	HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , MSC_EPIN_ADDR , PCD_SNG_BUF, 0x80);
    HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , MSC_EPOUT_ADDR , PCD_SNG_BUF, 0x80);
	return USBD_OK;
}

2.3 Main application

Firstly, in the main.h file, add the following Includes:

/* Includes ------------------------------------------------------------------*/
#include "stm32u5xx_hal.h"
#include "stm32u5xx_nucleo.h"
#include <stdio.h>
#include "usbd_core.h"
#include "usbd_desc.h"
#include "usbd_composite_builder.h"
#include "usbd_msc.h"
#include "usbd_hid.h"
#include "usbd_storage.h"

Second, in the main.c file, add the following declarations for the endpoint addresses:

uint8_t MSC_EpAdd_Inst[2]={MSC_EPIN_ADDR,MSC_EPOUT_ADDR};
uint8_t HID_EpAdd_Inst   = HID_EPIN_ADDR;
uint8_t hid_InstID= 0;

After that, between /* USER CODE BEGIN 2 */ and /* USER CODE END 2 */, insert the following code to initialize the user button and USB communication.

  /* USER CODE BEGIN 2 */

	/* Initialize USER push-button, will be used to trigger an interrupt each time it's pressed.*/
	BSP_PB_Init(BUTTON_USER, BUTTON_MODE_EXTI);

	/* Init Device Library */
	USBD_Init(&USBD_Device, &Class_Desc, 0);

	/* Add Class MSC */
	USBD_RegisterClassComposite(&USBD_Device ,USBD_MSC_CLASS ,CLASS_TYPE_MSC, MSC_EpAdd_Inst);

	/* Store HID Instance Class ID */
	hid_InstID = USBD_Device.classId;

	/* Add Class HID */
	USBD_RegisterClassComposite(&USBD_Device,USBD_HID_CLASS,CLASS_TYPE_HID, &HID_EpAdd_Inst);

	/* Add MSC Media interface */
	USBD_CMPSIT_SetClassID(&USBD_Device, CLASS_TYPE_MSC,0);

	/* Add Storage callbacks for MSC Class */
	USBD_MSC_RegisterStorage(&USBD_Device,&USBD_DISK_fops);

	/* Start Device Process */
	USBD_Start(&USBD_Device);

  /* USER CODE END 2 */

In a composite USB device with multiple classes, such as HID and MSC, a unique interface number must be assigned to each class to prevent conflicts during enumeration. In this application, the MSC class is assigned interface number 0, and the HID class is assigned the next available interface number by default.

Now, open the file stm32u5xx_it.c to configure the interrupt mode function. This mode is used to send HID reports that move the mouse pointer.

2.4 Interrupt mode

Between /* USER CODE BEGIN 0 */ and /* USER CODE END 0 */, insert the following line of code.

/* USER CODE BEGIN 0 */
extern USBD_HandleTypeDef USBD_Device;
#define CURSOR_STEP     20
uint8_t HID_Buffer[4];
extern uint8_t  hid_InstID;
static void GetPointerData(uint8_t *pbuf);
/* USER CODE END 0 */

Between /* USER CODE BEGIN 1*/ and /* USER CODE END 1 */, insert the definition of the function GetPointerData. This function is used to obtain the coordinates for mouse movement.

static void GetPointerData(uint8_t *pbuf)
{
  static int8_t cnt = 0;
  int8_t  x = 0, y = 0 ;

  if(cnt++ > 0)
  {
    x = CURSOR_STEP;
  }
  else
  {
    x = -CURSOR_STEP;
  }

  pbuf[0] = 0;
  pbuf[1] = x;
  pbuf[2] = y;
  pbuf[3] = 0;
}

After that, add the following callback function to handle the user button press and to send the HID report.

void BSP_PB_Callback(Button_TypeDef Button)
{
	 if (Button == BUTTON_USER)
	  {
	     GetPointerData(HID_Buffer);
	     USBD_HID_SendReport(&USBD_Device,HID_Buffer,sizeof(HID_Buffer), hid_InstID);
	  }
}

The application is now ready. Build the project and flash it to the target.

3. Results

After successfully building and flashing the application onto theNUCLEO-U545RE-Q board, connect the board to the PC using the USB user connectorThe host PC recognizes the composite USB device as both a mass storage device and an HID mouse.

devmgr.png

Now, you can press the blue USER button on the board to see the mouse pointer move on the screen. When opening the file explorer, notice the new USB drive that represents the mass storage device.

´final.png

As we can see, both USB classes operate simultaneously, demonstrating the successful implementation of the composite device (MSC+HID).

Conclusion

This guide provides a detailed approach to developing an STM32 application that integrates human interface device (HID) and mass storage class (MSC) functionalities into a composite USB device. By following these steps, you can create a USB device that enables both user interaction and mass storage capabilities, utilizing SRAM efficiently.

Perspective

This application currently uses internal SRAM as the storage medium for the mass storage class interface. However, it is designed with flexibility in mind and can support various external memory types, such as SD cards, NOR flash, or external SRAM. To enable and customize support for these external memories, modify the usbd_storage.c file accordingly. Adapt the storage interface functions to match your chosen memory hardware.

Related links 

Comments
Enissay0
Associate

Good Job #Hamdi 

ibrahim_gh
Associate

Great work @Hamdi_TEYEB this article is really helpful

MEYRAM
Associate

 

Excellent job @Hamdi_TEYEB, I found this article very valuable

 

Version history
Last update:
‎2025-07-18 1:49 AM
Updated by: