on 2025-09-26 2:57 AM
This article provides a detailed, step-by-step guide to implementing a USB mass storage class (MSC) device using the USBX middleware in standalone mode (without an RTOS) on the STM32U5 microcontroller. The example is developed and tested on the STM32U5G9J-DK1 development board featuring embedded eMMC memory.
While this guide specifically targets the STM32U5 board, the principles and code structure can be easily adapted for other STM32 microcontrollers using USBX and USB MSC functionality. This includes but is not limited to the STM32H5 series with minor modifications. This flexibility makes the solution valuable for a wide range of USB MSC standalone applications.
You can directly clone the project from STM32-hotspot/CKB-STM32-MSC-Standalone-U5 or follow the steps below to set your project.
Enable the USBX Device middleware package.
Configure the following parameters:
In Code Generator Settings, set all free GPIO pins to Analog mode to reduce power consumption.
Once the code is generated, perform the following steps to integrate the eMMC interface and ensure proper USB operation:
Add BSP Driver for eMMC
Include the BSP driver source file stm32u5x9j_discovery_mmc.c and its headers into your project. This file provides the necessary board support package (BSP) functions to interface with the eMMC memory.
Enable required HAL modules
In the stm32u5xx_hal_conf.h file, enable the following HAL modules if they are not already enabled, as they may be required by the BSP.
Exclude SDMMC generated code
Since the eMMC interface is managed by the BSP driver, exclude or disable the SDMMC peripheral code automatically generated by CubeMX to avoid conflicts or redundant initialization.
/* USER CODE BEGIN PV */
#define BLOCK_START_ADDR 0 /* Block start address */
#define NUM_OF_BLOCKS 5 /* Total number of blocks */
#define BUFFER_WORDS_SIZE ((MMC_BLOCKSIZE * NUM_OF_BLOCKS) >> 2) /* Total data size in bytes */
int32_t MMC_state=0 ;
/* USER CODE END PV */
/* USER CODE BEGIN SysInit */
MMC_state = BSP_MMC_Init();
if(MMC_state != BSP_ERROR_NONE)
{
printf("\r\nMMC Initialization : Failed");
printf("\r\n");
}
/* USER CODE END SysInit */
/* USER CODE BEGIN 3 */
USBX_Device_Process(NULL);
}
/* USER CODE END 3 */
Make sure to add in usb_otg.c specifically in HAL_PCD_MspInit() between /* USER CODE BEGIN USB_OTG_HS_MspInit 0 */ and /* USER CODE END USB_OTG_HS_MspInit 0 */ if CubeMX does not generate it correctly
/* USER CODE BEGIN USB_OTG_HS_MspInit 0 */
__HAL_RCC_SYSCFG_CLK_ENABLE();
/* USER CODE END USB_OTG_HS_MspInit 0 */
/* USER CODE BEGIN PV */
__ALIGN_BEGIN USB_MODE_STATE USB_Device_State_Msg __ALIGN_END;
extern PCD_HandleTypeDef hpcd_USB_OTG_HS;
/* USER CODE END PV */
/**
* @brief MX_USBX_Device_Process
* Run USBX state machine.
* arg: not used
* @retval none
*/
VOID USBX_Device_Process(VOID *arg)
{
ux_device_stack_tasks_run();
}
VOID USBX_APP_Device_Init(VOID)
{
/* USER CODE BEGIN USB_Device_Init_PreTreatment_0 */
/* USER CODE END USB_Device_Init_PreTreatment_0 */
/* initialize the device controller HAL driver */
MX_USB_OTG_HS_PCD_Init();
/* USER CODE BEGIN USB_Device_Init_PreTreatment_1 */
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_HS, 0x200);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 0, USBD_MAX_EP0_SIZE / 4);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 1, USBD_MSC_EPIN_HS_MPS / 2);
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_HS, 2, USBD_MSC_EPOUT_HS_MPS / 2);
/* USER CODE END USB_Device_Init_PreTreatment_1 */
/* initialize and link controller HAL driver to USBx */
if(_ux_dcd_stm32_initialize((ULONG)USB_OTG_HS, (ULONG)&hpcd_USB_OTG_HS) != UX_SUCCESS)
{
Error_Handler();
}
/* USER CODE BEGIN USB_Device_Init_PostTreatment */
HAL_PCD_Start(&hpcd_USB_OTG_HS);
/* USER CODE END USB_Device_Init_PostTreatment */
}
Warning[Pe223]: function "MX_USB_OTG_HS_PCD_Init" declared implicitly C:\xx\CKB-STM32-MSC-Standalone-U5\USBX\App\app_usbx_device.c 357
To avoid this warning, you can simply include #include "usb_otg.h" in the header file.
/* USER CODE BEGIN MX_USBX_Device_Init1 */
USBX_APP_Device_Init();
/* USER CODE END MX_USBX_Device_Init1 */
/* USER CODE BEGIN EFP */
VOID USBX_APP_Device_Init(VOID);
VOID USBX_Device_Process(VOID *arg);
/* USER CODE END EFP */
/* USER CODE BEGIN 1 */
typedef enum
{
STOP_USB_DEVICE = 1,
START_USB_DEVICE,
} USB_MODE_STATE;
/* USER CODE END 1 */
Now, implement the following USB MSC storage callbacks with eMMC support:
Add between /* USER CODE BEGIN USBD_STORAGE_GetMediaLastLba */ and /* USER CODE END USBD_STORAGE_GetMediaLastLba */
/* USER CODE BEGIN USBD_STORAGE_GetMediaLastLba */
if(eMMC_Init == 0)
{
eMMC_Init = 1;
BSP_MMC_Init();
}
HAL_MMC_CardInfoTypeDef pCardInfo = {0};
HAL_MMC_GetCardInfo(&uSdHandle, &pCardInfo);
LastLba = pCardInfo.BlockNbr-1;
/* USER CODE END USBD_STORAGE_GetMediaLastLba */
Add between /* USER CODE BEGIN USBD_STORAGE_GetMediaBlocklength */ and /* USER CODE END USBD_STORAGE_GetMediaBlocklength */
/* USER CODE BEGIN USBD_STORAGE_GetMediaBlocklength */
if(eMMC_Init == 0)
{
eMMC_Init = 1;
BSP_MMC_Init();
}
HAL_MMC_CardInfoTypeDef pCardInfo = {0};
HAL_MMC_GetCardInfo(&uSdHandle, &pCardInfo);
MediaBlockLen = pCardInfo.BlockSize;
/* USER CODE END USBD_STORAGE_GetMediaBlocklength */
Add between /* USER CODE BEGIN USBD_STORAGE_Read */ and /* USER CODE END USBD_STORAGE_Read*/
/* USER CODE END USBD_STORAGE_Read */
uint32_t timeout = 10000;
// UX_PARAMETER_NOT_USED(storage_instance);
// UX_PARAMETER_NOT_USED(lun);
// UX_PARAMETER_NOT_USED(media_status);
if (BSP_MMC_ReadBlocks((uint32_t*)data_pointer, lba, number_blocks, timeout) == BSP_ERROR_NONE)
{
// Wait until MMC card is ready for next operation
while (BSP_MMC_GetCardState() != MMC_TRANSFER_OK)
{
if (timeout-- == 0)
{
while(1);
}
}
status = UX_STATE_NEXT;
} else
{
while(1);
}
/* USER CODE END USBD_STORAGE_Read */
Add between /* USER CODE BEGIN USBD_STORAGE_Write */ and /* USER CODE END USBD_STORAGE_Write */
/* USER CODE BEGIN USBD_STORAGE_Write */
uint32_t timeout = 10000;
// UX_PARAMETER_NOT_USED(storage_instance);
// UX_PARAMETER_NOT_USED(lun);
// UX_PARAMETER_NOT_USED(media_status);
if (BSP_MMC_WriteBlocks((uint32_t*)data_pointer, lba, number_blocks, timeout) == BSP_ERROR_NONE)
{
// Wait until MMC card is ready for next operation
while (BSP_MMC_GetCardState() != MMC_TRANSFER_OK)
{
if (timeout-- == 0)
{
status= UX_ERROR; // Timeout expired
}
}
status = UX_STATE_NEXT;
}
else
{
while(1);
}
/* USER CODE END USBD_STORAGE_Write */
Do not return status = UX_SUCCESS; in USBD_STORAGE_Read, USBD_STORAGE_Write, or similar USBX MSC callbacks. Use instead: status = UX_STATE_NEXT;
After building and flashing the firmware to your STM32U5G9J-DK1 board:
This guide demonstrates how to implement a USB mass storage class device on the STM32U5 platform using USBX middleware in standalone mode. The device interfaces with onboard eMMC memory and appears as a standard removable USB drive to the host PC.