STMicroelectronics / STM32CubeL4

STM32Cube MCU Full Package for the STM32L4 series - (HAL + LL Drivers, CMSIS Core, CMSIS Device, MW libraries plus a set of Projects running on all boards provided by ST (Nucleo, Evaluation and Discovery Kits))
Other
259 stars 151 forks source link

SDMMC: DMA is disabled too early to complete transfer when reading from SdCard clocked at 16+ MHz from MSI #90

Open dima-kapustin opened 2 weeks ago

dima-kapustin commented 2 weeks ago

Describe the set-up

Describe the bug Reading from an SdCard hangs with the following conditions: SDMMC_DCTRL.DMAEN = 0 SDMMC_DCTRL.DTEN = 0 SDMMC_DCOUNT = 0

DMA_CCR5.EN = 1 DMA_CNDTR5 = 1 (!) which means no transfer complete interrupt will be generated ever

The issue is similar to https://community.st.com/t5/stm32-mcus-products/stm32l4-sd-dma-8-mhz-read-failure-due-to-missing-byte/td-p/385880.

How To Reproduce

  1. Indicate the global behavior of your application project.

SDMMC interface is configured with DMA 2 Channel 5, 1 bit data line SDMMC clock is sourced form MSI @ 48 MHz, NO devider bypass, HW flow control is ON. PCLK2 clock is 8.192 MHz and sourced from HSE via PLL

Call HAL_MMC_ReadBlocks_DMA() in a loop to read chunks of 1 or 8 blocks waiting until read completes, i.e. HAL_MMC_GetCardState() returns HAL_MMC_CARD_TRANSFER, after each HAL_MMC_ReadBlocks_DMA() call.

After some successful reads the next reading hangs with the conditions (register values) described above.

  1. The modules that you suspect to be the cause of the problem (Driver, BSP, MW ...).

HAL driver for SDMMC

  1. The use case that generates the problem. (see 1)
  2. How we can reproduce the problem. (see 1)

Additional context

The root cause is how read completion (SDMMC_STA.DATAEND = 1) is handled in function HAL_MMC_IRQHandler() in stm32l4xx_hal_mmc.c

I did two things in HAL_MMC_IRQHandler() to fix the issue:

  1. Commented out line https://github.com/STMicroelectronics/stm32l4xx_hal_driver/blob/42d324ac323088701270f2c19f328bcad0457c52/Src/stm32l4xx_hal_mmc.c#L1659

As per the reference manual:

-- It is not necessary to clear the enable bit (SDMMC_DCTRL.DTEN) after the end of a data transfer but the SDMMC_DCTRL must be updated to enable a new data transfer

  1. Added if statement so the lines 1709-1736 get executed ONLY for write requests.
      if (((context & MMC_CONTEXT_WRITE_SINGLE_BLOCK) != 0U) || ((context & MMC_CONTEXT_WRITE_MULTIPLE_BLOCK) != 0U))
      {
      // lines 1709-1736
      }

The code in lines 1709-1736 is ALSO present in MMC_DMAReceiveCplt() where it should be executed when DMA reports transfer complete for reads.

KRASTM commented 2 weeks ago

Hello @dima-kapustin,

Thank you for the report. Could you please, share your IOC file or an extract of the config or project, at least for SDMMC and DMA. So, I can use it to reproduce the issue.

In the meanwhile, I will try to analyze your fix or workaround.

With regards.

dima-kapustin commented 2 weeks ago

Hi @KRASTM !

Here you go (it is almost standard-generated by CubeIDE):

/**
  * Initializes the Global MSP.
  */
void HAL_MspInit(void)
{

  /* USER CODE BEGIN MspInit 0 */

  /* USER CODE END MspInit 0 */

  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_RCC_PWR_CLK_ENABLE();

  /* System interrupt init*/

  /* USER CODE BEGIN MspInit 1 */

  /* USER CODE END MspInit 1 */
}

/**
* @brief MMC MSP Initialization
* This function configures the hardware resources used in this example
* @param hmmc: MMC handle pointer
* @retval None
*/
void HAL_MMC_MspInit(MMC_HandleTypeDef* hmmc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hmmc->Instance==SDMMC1)
  {
  /* USER CODE BEGIN SDMMC1_MspInit 0 */
    LL_GPIO_SetOutputPin(SdCard_D0_GPIO_Port, SdCard_D0_Pin);
    LL_GPIO_SetOutputPin(SdCard_CK_GPIO_Port, SdCard_CK_Pin);
    LL_GPIO_SetOutputPin(SdCard_CMD_GPIO_Port, SdCard_CMD_Pin);

    LL_RCC_SetSDMMCClockSource(LL_RCC_SDMMC1_CLKSOURCE_MSI);

    SdCardPowerOnPin::reset();
    LL_mDelay(50);
  /* USER CODE END SDMMC1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_SDMMC1_CLK_ENABLE();

    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    /**SDMMC1 GPIO Configuration
    PC8     ------> SDMMC1_D0
    PC12     ------> SDMMC1_CK
    PD2     ------> SDMMC1_CMD
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDMMC1;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
    GPIO_InitStruct.Alternate = GPIO_AF12_SDMMC1;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    /* SDMMC1 DMA Init */
    /* SDMMC1 Init */
    hdma_sdmmc1.Instance = DMA2_Channel5;
    hdma_sdmmc1.Init.Request = DMA_REQUEST_7;
    hdma_sdmmc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_sdmmc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_sdmmc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_sdmmc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_sdmmc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_sdmmc1.Init.Mode = DMA_NORMAL;
    hdma_sdmmc1.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    if (HAL_DMA_Init(&hdma_sdmmc1) != HAL_OK)
    {
      Error_Handler();
    }

    /* Several peripheral DMA handle pointers point to the same DMA handle.
     Be aware that there is only one channel to perform all the requested DMAs. */
    /* Be sure to change transfer direction before calling
     HAL_SD_ReadBlocks_DMA or HAL_SD_WriteBlocks_DMA. */
    __HAL_LINKDMA(hmmc,hdmarx,hdma_sdmmc1);
    __HAL_LINKDMA(hmmc,hdmatx,hdma_sdmmc1);

    /* SDMMC1 interrupt Init */
    HAL_NVIC_SetPriority(SDMMC1_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(SDMMC1_IRQn);
  /* USER CODE BEGIN SDMMC1_MspInit 1 */
    SdCardResetPin::set();
    LL_mDelay(50);
  /* USER CODE END SDMMC1_MspInit 1 */
  }
}
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_ENABLE;
  hmmc1.Init.BusWide = SDMMC_BUS_WIDE_1B;
  hmmc1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_ENABLE;
  hmmc1.Init.ClockDiv = 0;
  if (HAL_MMC_Init(&hmmc1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SDMMC1_Init 2 */

  /* USER CODE END SDMMC1_Init 2 */

}
KRASTM commented 1 week ago

Hello @dima-kapustin,

I tried using the config that you shared, but unfortunately, I didn't manage to reproduce the problem on our board (not the same µc, the L496). If you have ST board that you can use it to test an example based on your config, or if you can share some screenshots of the debug mode while the problem occurs, it can help us to confirm the issue.

In the other hand, I shared your fix and proposal with our team in order to analyze it and confirm it.

With regards,

dima-kapustin commented 1 week ago

Hi @KRASTM!

Thanks for pushing it through!

I am afraid I cannot provide more details about the setup at the moment.

We faced the issue when increased SDMMC clock rate over 16 MHz, i.e. 24 MHz and higher. At clock rates lower than or equal to 16 MHz the issue did not appear for years (!).

I think there is fundamental issue in the irq processing flow:

However, in the current implementation the cleanup is done after SDMMC reports end of data transmission in both directions - write and read. When SDMMC clock is low enough, everything works fine. But as soon as SDMMC clock rate increases (24+ MHz in our case) the cleanup fails because DMA gets disabled before it completes data transfer and reports end of the transmission.

Beat regards, Dima.

KRASTM commented 1 week ago

Hello @dima-kapustin,

There is a section in the datasheet DS11453 page174, about SDMMC characteristics: Could you have a look at it, and I suggest you change or set the Speed mode into: OSPEEDRy[1:0] = 11, High or very High and give it a try. It may help you.

With regards,

dima-kapustin commented 1 week ago

This is one of the first things we tested... No, it does not help with the issue.

Thanks, Dima.