cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H743 unreliable micro SD transfers

AHigg
Associate II

Hello all,

I have worked through a number of issues with the SDMMC system and am currently stuck with not being able to reliably transfer from micro SD. I wrote test code to repeatedly read a file from the external card. In some cases HAL_SD_ErrorCallback is called with error code SDMMC_ERROR_CMD_CRC_FAIL.

Here are some things I have tried:

1) Slowing down the clock speed

2) Redesigning the board to improve the signal integrity of the micro SD lines

3) Using SDMMC2 instead of SDMMC1

4) Using 1-bit mode

5) Upgrading to Cube 1.3

6) Switching the pullup mode on the pins on and off

Here is a code excerpt:

SD_HandleTypeDef hsd1;
 
 
void SDMgr::Init()
{
	memset(&hsd1, 0, sizeof(hsd1));
 
	hsd1.Instance					= SDMMC1;
	hsd1.Init.BusWide				= SDMMC_BUS_WIDE_4B;
	hsd1.Init.ClockEdge				= SDMMC_CLOCK_EDGE_RISING;
	hsd1.Init.ClockPowerSave		        = SDMMC_CLOCK_POWER_SAVE_DISABLE;
	hsd1.Init.HardwareFlowControl	= SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
 
 
	// This assumes an incoming clock of 400MHz / 16 = 25MHz
	// Based on measurements, this results in a 50MHz micro SD clock. We get checksum errors
//	hsd1.Init.ClockDiv				= 2;
 
	// Based on measurements, this results in a 25MHz micro SD clock. We get checksum errors
	hsd1.Init.ClockDiv				= 4;
 
	memset(&m_SDFatFs, 0, sizeof(m_SDFatFs));
	memset(&m_SDPath, 0, sizeof(m_SDPath));
 
	// It is not necessary to cal BSP_SD_Init() here. It is already being called
	// through FatFS at the appropriate time.
 
	Mount();
}
 
 
void SDMgr::Mount()
{
	FATFS_UnLinkDriver(m_SDPath);
 
	/*##-1- Link the micro SD disk I/O driver ##################################*/
	if (FATFS_LinkDriver(&SD_Driver, m_SDPath) != 0)
	{
		Logger::Print("Unable to FATFS_LinkDriver\n");
		return;
	}
 
	/*##-2- Register the file system object to the FatFs module ##############*/
	if(f_mount(&m_SDFatFs, (TCHAR const*)m_SDPath, 0) != FR_OK)
	{
		/* FatFs Initialization Error */
		Logger::Print("Unable to f_mount\n");
		Error_Handler();
	}
}
 
 
void HAL_SD_MspInit(SD_HandleTypeDef* hsd)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	if(hsd->Instance==SDMMC1)
	{
		/* Peripheral clock enable */
		__HAL_RCC_SDMMC1_CLK_ENABLE();
 
		/**SDMMC1 GPIO Configuration    
		PC10     ------> SDMMC1_D2
		PC11     ------> SDMMC1_D3
		PC12     ------> SDMMC1_CK
		PD2     ------> SDMMC1_CMD
		PC8     ------> SDMMC1_D0
		PC9     ------> SDMMC1_D1 
		*/
		GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_8 
							  |GPIO_PIN_9;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
		GPIO_InitStruct.Pull = GPIO_PULLUP;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF12_SDIO1;
		HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
		GPIO_InitStruct.Pin = GPIO_PIN_12;
		GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF12_SDIO1;
		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_VERY_HIGH;
		GPIO_InitStruct.Alternate = GPIO_AF12_SDIO1;
		HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
 
		__HAL_RCC_SDMMC1_FORCE_RESET();
		__HAL_RCC_SDMMC1_RELEASE_RESET();
 
		/* NVIC configuration for SDIO interrupts */
		HAL_NVIC_SetPriority(SDMMC1_IRQn, IRQ_PRIORITY_SD, 0);
		HAL_NVIC_EnableIRQ(SDMMC1_IRQn);
	}
}

Any ideas? Thank you very much.

UPDATE: I am using FreeRTOS and followed the FatFs_uSD_DMA_RTOS sample.

19 REPLIES 19
AHigg
Associate II

Here is a patch to stm32h77xx_II_sdmmc.c that looks promising. Note the difference in line 16 where SDMMC_FLAG_BUSYD0END is removed from the conditional.

static uint32_t SDMMC_GetCmdResp1(SDMMC_TypeDef *SDMMCx, uint8_t SD_CMD, uint32_t Timeout)
{
  uint32_t response_r1;
  
  /* 8 is the number of required instructions cycles for the below loop statement.
  The Timeout is expressed in ms */
  register uint32_t count = Timeout * (SystemCoreClock / 8U /1000U);
  
  do
  {
    if (count-- == 0U)
    {
      return SDMMC_ERROR_TIMEOUT;
    }
    
  }while(!__SDMMC_GET_FLAG(SDMMCx, SDMMC_FLAG_CCRCFAIL | SDMMC_FLAG_CMDREND | SDMMC_FLAG_CTIMEOUT));
 
<snip>
 

Khouloud GARSI
Lead II

Hello,

The SDMMC driver is updated in the new STM32H7 Cube package (that will be available soon on ST website).

Regarding this loop condition, it will be updated as described below:

static uint32_t SDMMC_GetCmdResp1(SDMMC_TypeDef *SDMMCx, uint8_t SD_CMD, uint32_t Timeout)
{
  uint32_t response_r1;
  uint32_t sta_reg;
 
  /* 8 is the number of required instructions cycles for the below loop statement.
  The Timeout is expressed in ms */
  register uint32_t count = Timeout * (SystemCoreClock / 8U /1000U);
 
  do
  {
    if (count-- == 0U)
    {
      return SDMMC_ERROR_TIMEOUT;
    }
    sta_reg = SDMMCx->STA;
  }while(((sta_reg & (SDMMC_FLAG_CCRCFAIL | SDMMC_FLAG_CMDREND | SDMMC_FLAG_CTIMEOUT | SDMMC_FLAG_BUSYD0END)) == 0U) ||
         ((sta_reg & SDMMC_FLAG_CMDACT) != 0U ));
....

If you have any feedback, please don't hesitate to share it with us.

Khouloud.

I tested this one change (without taking all of the V1.4.0 update) and all of my DMA-based reads now time out. I am still tracking down why...

It seems to work correctly for retrieving CSD, etc, but not for doing Multi-block DMA reads

The SDMMC_FLAG_CMDACT bit never goes low, when I do a DMA-based single or multi-block read, so it always exits the while loop with a timeout.

Is there something else required?

AHigg
Associate II

This change ultimately proved to be the answer:

uint32_t SDMMC_CmdReadMultiBlock(SDMMC_TypeDef *SDMMCx, uint32_t ReadAdd)
{
    
  uint32_t maskReg = SDMMCx->MASK;
  SDMMCx->MASK = 0;
 
 
  SDMMC_CmdInitTypeDef  sdmmc_cmdinit;
  uint32_t errorstate;
  
  /* Set Block Size for Card */ 
  sdmmc_cmdinit.Argument         = (uint32_t)ReadAdd;
  sdmmc_cmdinit.CmdIndex         = SDMMC_CMD_READ_MULT_BLOCK;
  sdmmc_cmdinit.Response         = SDMMC_RESPONSE_SHORT;
  sdmmc_cmdinit.WaitForInterrupt = SDMMC_WAIT_NO;
  sdmmc_cmdinit.CPSM             = SDMMC_CPSM_ENABLE;
  (void)SDMMC_SendCommand(SDMMCx, &sdmmc_cmdinit);
  
  /* Check for error conditions */
  errorstate = SDMMC_GetCmdResp1(SDMMCx, SDMMC_CMD_READ_MULT_BLOCK, SDMMC_CMDTIMEOUT);
 
  SDMMCx->MASK = maskReg;
 
 
  return errorstate;
}

Rhodes.Keith
Associate II

Hi Aaron,

I don't think your solution will work if DMA is used. It looks like you are disabling all interrupts by setting the MASKR register to all 0's.

In my case, CRC problem isn't really a CRC problem. In the call to GetCMDResp1, the following call fails:

 /* Check response received is of desired command */
  if((response_r1 = SDMMC_GetCommandResponse(SDMMCx)) != SD_CMD)
  {
	  DBGprintf("cmd sent: %x response: %x\n", SD_CMD, response_r1);
    return SDMMC_ERROR_CMD_CRC_FAIL;
  }

In my case, the command (SD_CMD) is 0x10 to set the block length and the response returned is 0x0d. The function does return the SDMMC_ERROR_CMD_CRC_FAIL error, but it is really a command request/response mismatch.

If I put an ITM trace call in the main GetCMDResp1() call, the CRC problem also goes away, which points to a timing issue.

...Keith

Garnett.Robert
Senior III

Hi All,

I've been having trouble with CMD CRC errors with multi block writes. (I don't do much reading as I am logging data into an sqlite db. The error is infrequent and seems random. it occurs in the following part of the sd card driver:

/**
  * @brief  Send the Write Multi Block command and check the response
  * @param  SDMMCx: Pointer to SDMMC register base
  * @retval HAL status
  */
uint32_t SDMMC_CmdWriteMultiBlock(SDMMC_TypeDef *SDMMCx, uint32_t WriteAdd)
{
  SDMMC_CmdInitTypeDef  sdmmc_cmdinit;
  uint32_t errorstate;
 
  /* Set Block Size for Card */
  sdmmc_cmdinit.Argument         = (uint32_t)WriteAdd;
  sdmmc_cmdinit.CmdIndex         = SDMMC_CMD_WRITE_MULT_BLOCK;
  sdmmc_cmdinit.Response         = SDMMC_RESPONSE_SHORT;
  sdmmc_cmdinit.WaitForInterrupt = SDMMC_WAIT_NO;
  sdmmc_cmdinit.CPSM             = SDMMC_CPSM_ENABLE;
  (void)SDMMC_SendCommand(SDMMCx, &sdmmc_cmdinit);
 
  /* Check for error conditions */
  errorstate = SDMMC_GetCmdResp1(SDMMCx, SDMMC_CMD_WRITE_MULT_BLOCK, SDMMC_CMDTIMEOUT);
  seggerError(5, errorstate, sdmmc_cmdinit);	
 
  return errorstate;
}

I have looked at the sd card bus signals and they look fine.

Arron, how does the mod you made fix the problem and do you think it would work for block writes as well as block reads.

I am thinking of giving it a try, but I would like to understand what it is doing.

Best regards

Rob

Garnett.Robert
Senior III

Hi All

I tried out Arron's "turn off SDMMC controller interrupts" patch for the SD Card erros. I put his patch in the write/read multiple blocks functions and it has worked.

/**
  * @brief  Send the Read Multi Block command and check the response
  * @param  SDMMCx: Pointer to SDMMC register base
  * @retval HAL status
  */
uint32_t SDMMC_CmdReadMultiBlock(SDMMC_TypeDef *SDMMCx, uint32_t ReadAdd)
{
	uint32_t maskReg = SDMMCx->MASK; /* rjgMod per AHigg */
  SDMMCx->MASK = 0;                /* rjgMod per AHigg */
	
  SDMMC_CmdInitTypeDef  sdmmc_cmdinit;
  uint32_t errorstate;
 
  /* Set Block Size for Card */
  sdmmc_cmdinit.Argument         = (uint32_t)ReadAdd;
  sdmmc_cmdinit.CmdIndex         = SDMMC_CMD_READ_MULT_BLOCK;
  sdmmc_cmdinit.Response         = SDMMC_RESPONSE_SHORT;
  sdmmc_cmdinit.WaitForInterrupt = SDMMC_WAIT_NO;
  sdmmc_cmdinit.CPSM             = SDMMC_CPSM_ENABLE;
  (void)SDMMC_SendCommand(SDMMCx, &sdmmc_cmdinit);
 
  /* Check for error conditions */
  errorstate = SDMMC_GetCmdResp1(SDMMCx, SDMMC_CMD_READ_MULT_BLOCK, SDMMC_CMDTIMEOUT);
	SDMMCx->MASK = maskReg; /* rjgMod per AHigg */
  seggerError(3, errorstate, sdmmc_cmdinit);	
 
  return errorstate;
} ...
 
...
/**
  * @brief  Send the Write Multi Block command and check the response
  * @param  SDMMCx: Pointer to SDMMC register base
  * @retval HAL status
  */
uint32_t SDMMC_CmdWriteMultiBlock(SDMMC_TypeDef *SDMMCx, uint32_t WriteAdd)
{
	uint32_t maskReg = SDMMCx->MASK; /* rjgMod per AHigg */
  SDMMCx->MASK = 0;                /* rjgMod per AHigg */
	
  SDMMC_CmdInitTypeDef  sdmmc_cmdinit;
  uint32_t errorstate;
 
  /* Set Block Size for Card */
  sdmmc_cmdinit.Argument         = (uint32_t)WriteAdd;
  sdmmc_cmdinit.CmdIndex         = SDMMC_CMD_WRITE_MULT_BLOCK;
  sdmmc_cmdinit.Response         = SDMMC_RESPONSE_SHORT;
  sdmmc_cmdinit.WaitForInterrupt = SDMMC_WAIT_NO;
  sdmmc_cmdinit.CPSM             = SDMMC_CPSM_ENABLE;
  (void)SDMMC_SendCommand(SDMMCx, &sdmmc_cmdinit);
 
  /* Check for error conditions */
  errorstate = SDMMC_GetCmdResp1(SDMMCx, SDMMC_CMD_WRITE_MULT_BLOCK, SDMMC_CMDTIMEOUT);
	SDMMCx->MASK = maskReg; /* rjgMod per AHigg */
  seggerError(5, errorstate, sdmmc_cmdinit);	
 
  return errorstate;
}

Ignore the  seggerError(5, errorstate, sdmmc_cmdinit);    These were added to debug the driver using my Segger j-link debugger.

If you have a j-link debugger then the function I this calls may be found below:

#if  DEBUG_SDMMC	== 1
#include "seggerPrintRTC.h"
SDMMC_CmdInitTypeDef  _sdmmc_cmdinit;
#endif
 
void seggerError(int8_t itemNo, uint32_t errorstate, SDMMC_CmdInitTypeDef  sdmmc_cmdinit)
{
	#if DEBUG_SDMMC	== 1
	
	if (errorstate != SDMMC_ERROR_NONE)
	{
		SEGGER_RTT_printf(0,RTT_CTRL_TEXT_BRIGHT_MAGENTA"************  SDMMC Error: Item = %d Error State = %x   ************\n", itemNo, errorstate);	
 
		if(itemNo < 100)
		{		
		sdmmc_cmdinit.Argument         = (uint32_t)sdmmc_cmdinit.Argument;
		sdmmc_cmdinit.CmdIndex         = _sdmmc_cmdinit.CmdIndex;
		sdmmc_cmdinit.Response         = _sdmmc_cmdinit.Response;
		sdmmc_cmdinit.WaitForInterrupt = _sdmmc_cmdinit.WaitForInterrupt;
		sdmmc_cmdinit.CPSM             = _sdmmc_cmdinit.CPSM;
 
		SEGGER_RTT_printf(0,RTT_CTRL_TEXT_BRIGHT_MAGENTA    "Agument = %x\nCmdIndex = CDM%d\nResponse = %x\nWaitForInt = %x\nCPSM = %x\n\n", 
											_sdmmc_cmdinit.Argument, errorstate, _sdmmc_cmdinit.CmdIndex, _sdmmc_cmdinit.Response, _sdmmc_cmdinit.WaitForInterrupt, _sdmmc_cmdinit.CPSM);	
		}	
		
		HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET); /* rjgBebug */
		HAL_Delay(1);
		HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET); /* rjgBebug */
	}	
	#endif
}

Best regards

Rob

GS1
Senior III

Hi all,

I also had issues when writing to files and randomly got a "FR_DISK_ERR", which was originally caused by the SDMMC_ERROR_CMD_CRC_FAIL error described above.

Setting SDMMCx->MASK = 0 in SDMMC_CmdReadMultiBlock and SDMMC_CmdWriteMultiBlock actually solved the problem!

Thank you for posting this solution!

CodeIsNoGood
Associate

I’m adding this here as I think this is belonging to the same issue but regarding STM32F7 family

The problem I see is about the combination of RTOS, SDMMC, FATFS and DMA

I’m currently working on code development for a new product based on STM32F769. At the moment I’m using STM32F769 Discovery kit as development hardware. I’m using FreeRTOS and FATFS. As data drive I use a standard SD card which is using SDMMC2 in SD 4 bits wide bus mode with a clock frequency of 24MHz. Transfer mode is DMA. I was able to manage through all the problems with DMA, CACHE, MMU and the code seemed to work quite stable. For testing, I’m using a test task which permanently reads a 140MB size file from the SD card (reading in 8kByte chunks). For better understanding I added a shot of successful card reading signals. Top three channels show some IRQs, ERR is indicating a timeout in SDMMC_GetCmdResp1, the signals on the SDCARD (D0-3,CMD,CLK), RESPW toggles while waiting in SDMMC_GetCmdResp1 (also when leaving) and CMD12 which indicates the stop command. (200us/diff)  

 0693W00000Hq5WdQAJ.pngIn my next step I added LWIP to the project and created a task allowing me to do some TCP communication. Now, when sending data on the TCP channel, after some time, I get low level disk I/O errors. The more and faster I sent, the earlier the errors occur.

I did some investigation on that issue and tracked the problem down to SDMMC_GetCmdResp1. Here the code is running into a timeout. I did some hard debugging to figure out to get more info about it. I think I found the following:

0693W00000Hq5XMQAZ.png1.)   After the ending of previous 8k read the next read multiple block command (CMD18) is sent to the card and wait for the response. Below picture show the zoom of the start.

0693W00000Hq5XbQAJ.png

While handling the CMD18 response an IRQ of the Ethernet is occurring. The toggling of RESPW stops although the response is not even received (in above zoom it is setting RESPW to high). Reason for that is that the Ethernet IRQ is serviced (is fine). However, with the IRQ the scheduler also seems do a task switch. The task for the SD card seems to get suspended.   

2.)   The data transfer starts and DMA is handling this. SD card task still seems to be suspended. 

3.)   The data transfer is complete, DMA IRQ handles the stop command and overwrites by that the contents of the SDMMC_STA register. Short time after the SD_IRQ is handled the SD card task is getting back context and continues. However, as SDMMC_STA was modified by the DMA IRQ the SD card task never sees the result of sending the command.   

4.)   SDMMC_GetCmdResp1 runs into timeout

I haven't tried the patch by now but I thought it should be in the STM32F7 firmware package. 

For test purpose I tried vTaskSuspendAll() and xTaskResumeAll() to avoid tasks switching between sending the command and the receive. This removed the the disk IO errors. However, I doubt this is a clean solution.

My environment:

STM32Cube IDE 1.8.0

MCU Package 1.16.1

RTOS, FATFS, DMA, LWIP

STM32F769 Discovery kit

8 Gbyte SD card, 24MHz SD CLK, 4 bit wide mode

Maybe someone is finding this helpful.