peter-tanner.github.io/_posts/2024-06-23-Debugging-STM32-USB-Mass-Storage-Class-Device.md

10 KiB

title author date categories tags
Debugging STM32 USB Mass Storage Class Device with eMMC peter 2024-06-23 22:25:56 +0800
Electronics
electronics
emmc
embedded

Summary of my thought process when troubleshooting a board containing eMMC and USB mass storage class device on the STM32L476.

Download Wireshark and the USB pcap plugin.

TLDR: Minimum configuration you should try for troubleshooting is:

  • High clock divider (try 4 or 8 or even larger values)
  • 1 bit wide MMC bus
  • Don't initialize MMC peripheral in the USB MSC initialization function.

1. USB connects but then disconnects after a few seconds. The device shows momentarily in the tray, but is not assigned a driver letter.

This also shows in wireshark when the response from the device is a GET MAX LUN Response[Malformed Packet] packet.

wireshark packet capture with malformed packet

Try return USBD_OK for the all functions except STORAGE_GetMaxLun_FS. It should show up in explorer with a drive letter and should not disconnect.

This means the functions interacting with the storage device are not working.


Make sure you are not re-initializing the MMC peripheral in the STORAGE_Init_FS function. This is because in the initialization of the MMC peripheral, HAL_Delay is called twice. The use of HAL_Delay while the USB device is active appears to cause issues with mounting the device.

2. "This request is not supported" when formatting drive, files not readable

Try changing the clock division to something larger like 4 or greater. Once it is stable try decreasing the divider to maximize speed. When I used lower values, the bus was unstable and I could not retrieve files or perform format operations.

hmmc1.Init.ClockDiv = 4;

3. Random files appearing when formatting AND using 4-bit mode

When a drive has already been formatted in 1-bit mode and you try to reformat it, the format fails and there appears to be random files in the apparently formatted drive

The format fails with:

Windows was unable to complete the format

I guess my board doesn't work in 4-bit mode so I just compromised with 1-bit mode and it worked again.

4. DMA not working on STM32L476RET6 STM32L476

Make sure to switch the DMA direction each for RX and TX

Since we are doing bi-directional transfer just use the SDMMC1 DMA request type. Don't use SDMMC1_TX and SDMMC1_RX simulatneously since you need to switch the direction each transfer anyways.

dma config for sdmmc1

Also make sure the preemption priority of the USB is higher than the DMA and SDMMC peripherals and to enable the SDMMC global interrupt.

In my case I am using:

NVIC interrupt table Preemption Priority Sub Priority
SDMMC1 global interrupt 0 0
DMA2 channel4 global interrupt (Use your DMA controller) 1 0
USB OTG FS global interrupt 3 0

nvic priority table

Performance with DMA

Kind of terrible but I am using 1-bit bus width since 4-bit isn't working for me.

crystaldiskmark performance

Sample code for eMMC and USB mass storage class device

main.c

static void MX_SDMMC1_MMC_Init(void)
{

  /* USER CODE BEGIN SDMMC1_Init 0 */

  /* USER CODE END SDMMC1_Init 0 */

  /* USER CODE BEGIN SDMMC1_Init 1 */

  /* USER CODE END SDMMC1_Init 1 */
  hmmc1.Instance = SDMMC1;
  hmmc1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;
  hmmc1.Init.ClockBypass = SDMMC_CLOCK_BYPASS_DISABLE;
  hmmc1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;
  hmmc1.Init.BusWide = SDMMC_BUS_WIDE_1B;
  hmmc1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
  hmmc1.Init.ClockDiv = 2; // MODIFY THIS AS APPROPRIATE
  if (HAL_MMC_Init(&hmmc1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_MMC_ConfigWideBusOperation(&hmmc1, SDMMC_BUS_WIDE_1B) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SDMMC1_Init 2 */
  /* USER CODE END SDMMC1_Init 2 */
}

usbd_storage_if.c

int8_t STORAGE_Init_FS(uint8_t lun)
{
  /* USER CODE BEGIN 2 */
  // ALREADY INITIALIZED IN `MX_SDMMC1_MMC_Init` FUNCTION.
  return USBD_OK;
  /* USER CODE END 2 */
}

int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
  HAL_MMC_CardInfoTypeDef card_info;
  HAL_StatusTypeDef status = HAL_MMC_GetCardInfo(&hmmc1, &card_info);
  *block_num = card_info.LogBlockNbr - 1;
  *block_size = card_info.LogBlockSize;
  return status;
  /* USER CODE END 3 */
}

int8_t STORAGE_IsReady_FS(uint8_t lun)
{
  /* USER CODE BEGIN 4 */
  // eMMC IS ALWAYS CONNECTED TO THE BOARD AND IS ALWAYS READY SINCE IT IS
  // INITIALIZED AT THE START.
  return USBD_OK;
  /* USER CODE END 4 */
}

int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
  /* USER CODE BEGIN 5 */
  // ASSUME eMMC IS NEVER WRITE PROTECTED ON THIS PARTICULAR BOARD
  // WRITE PROTECT FEATURE IS NOT USED.
  return USBD_OK;
  /* USER CODE END 5 */
}

int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
  // TODO: USE DMA TRANSFER LATER
  int8_t status = HAL_MMC_ReadBlocks(&hmmc1, buf, blk_addr, (uint32_t)blk_len, TIMEOUT);
  while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
    ;
  return status;

  /* USER CODE END 6 */
}

int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
  // TODO: USE DMA TRANSFER LATER
  int8_t status = HAL_MMC_WriteBlocks(&hmmc1, buf, blk_addr, (uint32_t)blk_len, TIMEOUT);
  while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
    ;
  return status;
  /* USER CODE END 7 */
}

Sample code DMA version

usbd_storage_if.c

volatile uint8_t mmc_rx_done = 1;
volatile uint8_t mmc_tx_done = 1;

void HAL_MMC_RxCpltCallback(MMC_HandleTypeDef *hmmc)
{
  mmc_rx_done = 1;
}

void HAL_MMC_TxCpltCallback(MMC_HandleTypeDef *hmmc)
{
  mmc_tx_done = 1;
}

// CHANGE DMA DIRECTION
HAL_StatusTypeDef MMC_DMA_direction(uint32_t direction)
{
  HAL_StatusTypeDef status = HAL_OK;
  hdma_sdmmc1.Init.Direction = direction;
  HAL_DMA_Abort(&hdma_sdmmc1);
  HAL_DMA_DeInit(&hdma_sdmmc1);
  return HAL_DMA_Init(&hdma_sdmmc1);
}

//
// [...]
//

/**
 * @brief  Initializes over USB FS IP
 * @param  lun:
 * @retval USBD_OK if all operations are OK else USBD_FAIL
 */
int8_t STORAGE_Init_FS(uint8_t lun)
{
  /* USER CODE BEGIN 2 */
  // ALREADY INITIALIZED IN `MX_SDMMC1_MMC_Init` FUNCTION.
  return USBD_OK;
  /* USER CODE END 2 */
}

/**
 * @brief  .
 * @param  lun: .
 * @param  block_num: .
 * @param  block_size: .
 * @retval USBD_OK if all operations are OK else USBD_FAIL
 */
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
  HAL_MMC_CardInfoTypeDef card_info;
  HAL_StatusTypeDef status = HAL_MMC_GetCardInfo(&hmmc1, &card_info);
  *block_num = card_info.LogBlockNbr - 1;
  *block_size = card_info.LogBlockSize;
  return status;
  /* USER CODE END 3 */
}

/**
 * @brief  .
 * @param  lun: .
 * @retval USBD_OK if all operations are OK else USBD_FAIL
 */
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
  /* USER CODE BEGIN 4 */
  // if (HAL_MMC_GetState(&hmmc1) == HAL_MMC_STATE_BUSY || HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
  //   return USBD_FAIL;
  return USBD_OK;
  /* USER CODE END 4 */
}

/**
 * @brief  .
 * @param  lun: .
 * @retval USBD_OK if all operations are OK else USBD_FAIL
 */
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
  /* USER CODE BEGIN 5 */
  // ASSUME eMMC IS NEVER WRITE PROTECTED ON THIS PARTICULAR BOARD
  // WRITE PROTECT FEATURE IS NOT USED.
  return USBD_OK;
  /* USER CODE END 5 */
}

/**
 * @brief  .
 * @param  lun: .
 * @retval USBD_OK if all operations are OK else USBD_FAIL
 */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
  mmc_rx_done = 0;
  if (MMC_DMA_direction(DMA_PERIPH_TO_MEMORY) != HAL_OK)
    return USBD_FAIL;
  if (HAL_MMC_ReadBlocks_DMA(&hmmc1, buf, blk_addr, blk_len) != HAL_OK)
    return USBD_FAIL;
  while (!mmc_rx_done)
    ;
  while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
    ; // FIXME: IMPLEMENT TIMEOUT FAILSAFE
  return USBD_OK;
  /* USER CODE END 6 */
}

/**
 * @brief  .
 * @param  lun: .
 * @retval USBD_OK if all operations are OK else USBD_FAIL
 */
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
  mmc_tx_done = 0;
  if (MMC_DMA_direction(DMA_MEMORY_TO_PERIPH) != HAL_OK)
    return USBD_FAIL;
  if (HAL_MMC_WriteBlocks_DMA(&hmmc1, buf, blk_addr, blk_len) != HAL_OK)
    return USBD_FAIL;
  while (!mmc_tx_done)
    ;
  while (HAL_MMC_GetCardState(&hmmc1) != HAL_MMC_CARD_TRANSFER)
    ; // FIXME: IMPLEMENT TIMEOUT FAILSAFE
  return USBD_OK;
  /* USER CODE END 7 */
}

/**
 * @brief  .
 * @param  None
 * @retval .
 */
int8_t STORAGE_GetMaxLun_FS(void)
{
  /* USER CODE BEGIN 8 */
  return STORAGE_LUN_NBR - 1;
  /* USER CODE END 8 */
}

Conclusion

I will update this post with more cases as I run into them. Comment on your experiences debugging USB on STM32 and I'll quote them in the article.