Skip to main content
Explorer
October 13, 2025
Question

STM32H7 USBX Device and mount/eject SD card

  • October 13, 2025
  • 2 replies
  • 268 views

Hi ST community

// --- EDIT --- Missed preview and made some corrections / clarifications

I'm looking for a way to mount a SD card using the Azure RTOS UsbX MSC class, while the MCU is running. The code is mostly based on the ST examples, e.g.:

https://github.com/STMicroelectronics/x-cube-azrtos-h7/tree/main/Projects/STM32H735G-DK/Applications/USBX/Ux_Device_MSC 

The example expects that the SD card is already inserted on power up - therefore the MX_SDMMC1_SD_Init() is called before the USBX device is registered. Therefore the LBA and Blocksize values are already available when the USBX device is registered.

I want to have a more general case, where I can remove and insert a SD card while the application is running. A separate thread (SD_thread_entry) which handles this behaviour and is waiting for the SD card pin to be detected. The card is then power reset, the HAL is initialized and the CardInfo read:

void SD_thread_entry(ULONG thread_input)
{
 // Thread variables
 ULONG flags;
 bool isCardOk;
 uint32_t sdCardstart;

 // Thread init
 if(GPIO_SD_isCardPresent()) {
 tx_event_flags_set(&eventSd, EVT_CARD_INSERTED, TX_OR);
 }

 // Start of thread loop
 while (true)
 {
 // Wait until card is detected
 tx_event_flags_get(&eventSd, EVT_CARD_INSERTED, TX_OR_CLEAR, &flags, TX_WAIT_FOREVER);

 // Setup SD card
 // --------------------------------------------------------

 // Power off SD card
 printf("SD card power reset.\n");
 GPIO_SD_powerEnable(false);
 tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND / 2); // wait 500ms

 // Power on SD card
 GPIO_SD_powerEnable(true);
 tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND / 2); // wait 500ms

 // Check that card is still present
 if (!GPIO_SD_isCardPresent())
 {
 continue;
 }

 // Init SD card
 printf("SD card hardware init.\n");
 MX_SDMMC1_SD_Init();

 // Check card status
 isCardOk = false;
 sdCardstart = tx_time_get();
 while (tx_time_get() - sdCardstart < 2000 && GPIO_SD_isCardPresent())
 {
 // fetch card state
 HAL_SD_CardStateTypeDef state = HAL_SD_GetCardState(&hsd1);
 if (state == HAL_SD_CARD_TRANSFER)
 {
 isCardOk = true;
 break;
 } else {
 printf("SD card state: %lu, present: %d\n", state, GPIO_SD_isCardPresent());
 }

 // not yet ready - sleep 100ms
 tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND / 10);
 }

 if(!isCardOk) {
 // Card error - TODO handle error
 printf("SD card state failed\n");
 continue;
 }

 // Get card info
 if (HAL_SD_GetCardInfo(&hsd1, &SD_CardInfo) != HAL_OK)
 {
 // Card info error - TODO handle error
 printf("SD card info failed\n");
 continue;
 }

 // Update LBA and block size
 USBX_updateSdParameters();

 // Flag card ready
 tx_event_flags_set(&eventSd, EVT_CARD_READY, TX_OR);
 printf("SD card ready.\n");

 // Card ready - wait for write requests or card removed events
 while (true)
 {
 tx_event_flags_get(&eventSd, EVT_CARD_REMOVED, TX_OR_CLEAR, &flags, TX_WAIT_FOREVER);

 if (flags & EVT_CARD_REMOVED)
 {
 break;
 }

 // perform SD card tasks
 }

 // Clear SD flags
 tx_event_flags_get(&eventSd,
 EVT_CARD_READY | EVT_READ_DONE | EVT_WRITE_DONE, TX_OR_CLEAR, &flags, TX_NO_WAIT);

 printf("SD card not ready - waiting for insertion.\n");

 // Deinit SD card
 MX_SDMMC1_SD_DeInit();
 }
}

Only once the SD card is mounted, the blocksize and LBA are known. Therefore the following function is used to update the UsbX storage parameters:

VOID USBX_updateSdParameters() {
 /* Initialize the storage class parameters for reading/writing to the Flash Disk */
 storage_parameter.ux_slave_class_storage_parameter_lun[0].
 ux_slave_class_storage_media_last_lba = USBD_STORAGE_GetMediaLastLba();

 storage_parameter.ux_slave_class_storage_parameter_lun[0].
 ux_slave_class_storage_media_block_length = USBD_STORAGE_GetMediaBlocklength();
}

Furthermore, I implemented a simple media_status function, which just reports NO_SENSE (for card detected) or NOT_READY (for card not detected):

UINT USBD_STORAGE_Status(VOID *storage_instance, ULONG lun, ULONG media_id,
 ULONG *media_status)
{
 UINT status = UX_SUCCESS;

 /* USER CODE BEGIN USBD_STORAGE_Status */
 UX_PARAMETER_NOT_USED(storage_instance);
 UX_PARAMETER_NOT_USED(lun);
 UX_PARAMETER_NOT_USED(media_id);

 // overwrite default success status
 status = UX_ERROR;

 // only report SD card if SD thread is ready
 if (SD_isCardReady())
 {
 *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_NO_SENSE, 0x00, 0x00);
 return UX_SUCCESS; // media present
 }
 else
 {
 *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_NOT_READY, 0x3A, 0x00);
 return UX_ERROR; // media absent
 }
 /* USER CODE END USBD_STORAGE_Status */

 return status;
}

Each read/write/flush checks that the media status is correct or otherwise reports an UX_Error if not ready:

UINT USBD_STORAGE_Write(VOID *storage_instance, ULONG lun, UCHAR *data_pointer,
 ULONG number_blocks, ULONG lba, ULONG *media_status)
{
 UINT status = UX_SUCCESS;

 /* USER CODE BEGIN USBD_STORAGE_Write */
 if(UX_SUCCESS != USBD_STORAGE_Status(storage_instance, lun, 0, media_status)) {
 return UX_ERROR;
 }

 status = SD_writeBlocks(data_pointer, number_blocks, lba);

 /* USER CODE END USBD_STORAGE_Write */

 return status;
}

Is this the correct way to mount a SD card while the application is already running?

On a side note: I'm using a composite class of ACM (Virtual com port) and MSC (mass storage). Starting first with a ACM only class and then registering an addtional class later seems also not to work, probably because the enumeration of the device is already complete.

Unfortunately, the rtos docs regarding the media_status are not that clear and I don't really understand what to return there:

https://github.com/eclipse-threadx/rtos-docs/blob/main/rtos-docs/usbx/usbx-device-stack-5.md#usb_device_storage_class 

Thanks in advance and best regards

Daniel

 

    This topic has been closed for replies.

    2 replies

    Technical Moderator
    November 3, 2025

    Hi @DZimm.9 

    Thank you for your detailed inquiry and for sharing your implementation approach regarding dynamic SD card mounting with USBX MSC.

    The scenario you are describing : handling SD card insertion/removal dynamically during runtime and updating the USB mass storage parameters accordingly, is indeed a more advanced and custom use case beyond the static initialization model provided in the standard examples.

    An internal ticket is submitted to dedicated team (220990)

     

    About On a side note: I'm using a composite class of ACM (Virtual com port) and MSC (mass storage). Starting first with a ACM only class and then registering an addtional class later seems also not to work, probably because the enumeration of the device is already complete.

    > Indeed, you cannot switch between classes while USB peripheral is already running! 

    DZimm.9Author
    Explorer
    November 3, 2025

    Hi FBL

    I unintentionally clicked marked as solution - can you remove this?

    In the meantime, I found a solution which works, however I'm not sure if this is really the proper way to do it. There is a SCSI code to notify the host on a medium change. When the card insertion is detected, then an NOTIFY status is triggered, which updates the LBA and block size from the newly inserted SD card:

    UINT USBD_STORAGE_Status(VOID *storage_instance, ULONG lun, ULONG media_id,
     ULONG *media_status)
    {
     UINT status = UX_SUCCESS;
    
     /* USER CODE BEGIN USBD_STORAGE_Status */
     UX_PARAMETER_NOT_USED(media_id);
    
     // overwrite default success status
     status = UX_SUCCESS;
    
     // Get media status
     tSdStatus sdStatus = SD_getMediaStatus();
     switch(sdStatus) {
     case SD_STATUS_NOTIFY_HOST:
    
     // Update LBA and BlockSize
     UX_SLAVE_CLASS_STORAGE *storage = (UX_SLAVE_CLASS_STORAGE *)storage_instance;
     UX_SLAVE_CLASS_STORAGE_LUN *lun_ptr = &storage->ux_slave_class_storage_lun[lun];
     lun_ptr->ux_slave_class_storage_media_last_lba = SD_getMediaLastLba();
     lun_ptr->ux_slave_class_storage_media_block_length = SD_getMediaBlockLength();
    
     // SCSI SENSE: NOT READY TO READY TRANSITION – MEDIA CHANGED
     *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_UNIT_ATTENTION, 0x28, 0x00);
     break;
     case SD_STATUS_NOT_PRESENT:
     // SCSI SENSE: MEDIUM NOT PRESENT
     *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_NOT_READY, 0x3A, 0x00);
     break;
     case SD_STATUS_INITIALIZING:
     // SCSI SENSE: LOGICAL DRIVE NOT READY - BECOMING READY
     *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_NOT_READY, 0x04, 0x01);
     break;
     case SD_STATUS_READY:
     // SCSI SENSE: NO SENSE - card ready / no error
     *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_NO_SENSE, 0x00, 0x00);
     break;
     case SD_STATUS_UNKNOWN:
     // SCSI SENSE: UNKNOWN error
     *media_status = UX_DEVICE_CLASS_STORAGE_SENSE_STATUS(UX_SLAVE_CLASS_STORAGE_SENSE_KEY_NO_SENSE, 0xFF, 0xFF);
     break;
     }
    
     // Debug reporting
     static ULONG lastMediaStatus = -1;
     if(*media_status != lastMediaStatus) {
     lastMediaStatus = *media_status;
     printf("USB media status: %06X\n", (unsigned int)(*media_status));
     }
    
     // if other than ready, report UX_ERROR to trigger host status update
     if(sdStatus != SD_STATUS_READY) {
     return UX_ERROR;
     }
     /* USER CODE END USBD_STORAGE_Status */
    
     return status;
    }

     
    All other MSC functions return UX_ERROR aslong the status is other than "NO SENSE" == 0. E.g. flush:

    UINT USBD_STORAGE_Flush(VOID *storage_instance, ULONG lun, ULONG number_blocks,
     ULONG lba, ULONG *media_status)
    {
     UINT status = UX_SUCCESS;
    
     /* USER CODE BEGIN USBD_STORAGE_Flush */
     status = UX_ERROR;
    
     USBD_STORAGE_Status(storage_instance, lun, 0, media_status);
     if(*media_status == 0) {
     status = SD_flush();
     }
    
     /* USER CODE END USBD_STORAGE_Flush */
    
     return status;
    }

    Best regards

    Daniel