Skip to main content
Explorer
March 4, 2024
Solved

Connecting eMMC to SDMMC1 on the STM32H7

  • March 4, 2024
  • 2 replies
  • 4729 views

Hi.

I am using SDMMC1-eMMC(16GB), SDMMC2-SD Card connected on a custom board.

Both eMMCs and SD-Cards use FatFS as their file system.

Both work fine when used alone.

However, if you use f_open or f_write on FatFS on eMMC, you sometimes get an error and the FatFS becomes unusable afterward.

 

My settings are as follows.

 

DMA.pngNVIC.pngSDMMC1_SET.pngSDMMC2_SET.pngSDMMC2.png

 

I tried to find the cause by disabling all device drivers and features and then enabling them one by one.

The problem I found was that FatFS on eMMC often caused problems if the ability to receive UART data via DMA was enabled.

 

void MPU_Config(void) {
 MPU_Region_InitTypeDef MPU_InitStruct = {0};

 /* Disables the MPU */
 HAL_MPU_Disable();

 /** Initializes and configures the Region and the memory to be protected
 */
 MPU_InitStruct.Enable = MPU_REGION_ENABLE;
 MPU_InitStruct.Number = MPU_REGION_NUMBER0;
 MPU_InitStruct.BaseAddress = 0xD0000000;
 MPU_InitStruct.Size = MPU_REGION_SIZE_16MB;
 MPU_InitStruct.SubRegionDisable = 0x0;
 MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
 MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
 MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
 MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
 MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
 MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

 HAL_MPU_ConfigRegion(&MPU_InitStruct);

 /** Initializes and configures the Region and the memory to be protected
 */
 MPU_InitStruct.Number = MPU_REGION_NUMBER1;
 MPU_InitStruct.BaseAddress = 0x30000000;
 MPU_InitStruct.Size = MPU_REGION_SIZE_4KB;
 MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
 MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;

 HAL_MPU_ConfigRegion(&MPU_InitStruct);

 /** Initializes and configures the Region and the memory to be protected
 */
 MPU_InitStruct.Number = MPU_REGION_NUMBER2;
 MPU_InitStruct.BaseAddress = 0x24000000;
 MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
 MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
#if 1
 HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif
 /* Enables the MPU */
 HAL_MPU_Enable(MPU_HFNMI_PRIVDEF);

}

 

MPU_REGION_NUMBER 0 is the MPU setting for the external SDRAM region to use the LTDC screen.

MPU_REGION_NUMBER 1 is the MPU setting for the DMA receive buffer region in the SRAM region of D2.

MPU_REGION_NUMBER 2 is the MPU setting for D1 SRAM.

 

/* Specify the memory areas */
MEMORY
{
 FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
 DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
 RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
 /*RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K*/
 RAM_D2_UART (xrw) : ORIGIN = 0x30000000, LENGTH = 1K
 RAM_D2_SPI (xrw) : ORIGIN = 0x30000400, LENGTH = 1K
 RAM_D2_EXT (xrw) : ORIGIN = 0x30000800, LENGTH = 1K
 RAM_D2 (xrw) : ORIGIN = 0x30001000, LENGTH = (288K-3K)
 RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
 ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
 SDRAM (xrw) : ORIGIN = 0xD0E00000, LENGTH = 2048K
}

This is the mapping address for my memory region.

So I tried turning off the MPU setting for the D1 SRAM, in which case FatFS works fine, but I have problems receiving UART.

 

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file MMC_diskio.c
 * @brief This file includes a diskio driver skeleton to be completed by the user.
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2024 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
 /* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
 * To be suppressed in the future.
 * Kept to ensure backward compatibility with previous CubeMx versions when
 * migrating projects.
 * User code previously added there should be copied in the new user sections before
 * the section contents can be deleted.
 */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */

/* Includes ------------------------------------------------------------------*/
#include "ff_gen_drv.h"
#include "bsp_driver_mmc.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define MMC_DEFAULT_BLOCK_SIZE 512
/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
 DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
 DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef USER_Driver =
{
 USER_initialize,
 USER_status,
 USER_read,
#if _USE_WRITE
 USER_write,
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
 USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
 * @brief Initializes a Drive
 * @PAram pdrv: Physical drive number (0..)
 * @retval DSTATUS: Operation status
 */
DSTATUS USER_initialize (
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
 /* USER CODE BEGIN INIT */
 // unused parameter
 UNUSED(pdrv);
 // set state
 Stat = STA_NOINIT;
 // initialize bsp
 if (BSP_MMC_Init() == MMC_OK)
 // check status
 if (BSP_MMC_GetCardState() == MMC_TRANSFER_OK)
 // reset state
 Stat &= ~STA_NOINIT;
 // result
 return Stat;
 /* USER CODE END INIT */
}

/**
 * @brief Gets Disk Status
 * @PAram pdrv: Physical drive number (0..)
 * @retval DSTATUS: Operation status
 */
DSTATUS USER_status (
	BYTE pdrv /* Physical drive number to identify the drive */
)
{
 /* USER CODE BEGIN STATUS */
 // unused parameter
 UNUSED(pdrv);
 // set state
 Stat = STA_NOINIT;
 // check status
 if (BSP_MMC_GetCardState() == MMC_TRANSFER_OK)
 // reset state
 Stat &= ~STA_NOINIT;
 // result
 return Stat;
 /* USER CODE END STATUS */
}

/**
 * @brief Reads Sector(s)
 * @PAram pdrv: Physical drive number (0..)
 * @PAram *buff: Data buffer to store read data
 * @PAram sector: Sector address (LBA)
 * @PAram count: Number of sectors to read (1..128)
 * @retval DRESULT: Operation result
 */
DRESULT USER_read (
	BYTE pdrv, /* Physical drive nmuber to identify the drive */
	BYTE *buff, /* Data buffer to store read data */
	DWORD sector, /* Sector address in LBA */
	UINT count /* Number of sectors to read */
)
{
 /* USER CODE BEGIN READ */
 DRESULT res;
 // read blocks
 if (BSP_MMC_ReadBlocks((uint32_t *) buff, (uint32_t) sector, count) == MMC_OK) {
 // check card state
 while (BSP_MMC_GetCardState() != MMC_TRANSFER_OK) {
 }
 // set result
 res = RES_OK;
 } else {
 // set error
 res = RES_NOTRDY;
 }

 // unused parameter
 UNUSED(pdrv);

 return res;
 /* USER CODE END READ */
}

/**
 * @brief Writes Sector(s)
 * @PAram pdrv: Physical drive number (0..)
 * @PAram *buff: Data to be written
 * @PAram sector: Sector address (LBA)
 * @PAram count: Number of sectors to write (1..128)
 * @retval DRESULT: Operation result
 */
#if _USE_WRITE == 1
DRESULT USER_write (
	BYTE pdrv, /* Physical drive nmuber to identify the drive */
	const BYTE *buff, /* Data to be written */
	DWORD sector, /* Sector address in LBA */
	UINT count /* Number of sectors to write */
)
{
 /* USER CODE BEGIN WRITE */
 DRESULT res;
 // write blocks
 if (BSP_MMC_WriteBlocks((uint32_t *) buff, (uint32_t) sector, count) == MMC_OK) {
 // check card state
 while (BSP_MMC_GetCardState() != MMC_TRANSFER_OK) {
 }
 // set result
 res = RES_OK;
 } else {
 // set error
 res = RES_NOTRDY;
 }

 // unused parameter
 UNUSED(pdrv);

 return res;
 /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
 * @brief I/O control operation
 * @PAram pdrv: Physical drive number (0..)
 * @PAram cmd: Control code
 * @PAram *buff: Buffer to send/receive control data
 * @retval DRESULT: Operation result
 */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv, /* Physical drive nmuber (0..) */
	BYTE cmd, /* Control code */
	void *buff /* Buffer to send/receive control data */
)
{
 /* USER CODE BEGIN IOCTL */
 DRESULT res;
 HAL_MMC_CardInfoTypeDef info;

 // check state
 if (Stat & STA_NOINIT)
 // not ready
 return RES_NOTRDY;

 // set result
 res = RES_OK;
 // check command
 switch (cmd) {
 case CTRL_SYNC: // Make sure that no pending write process
 break;
 case GET_SECTOR_COUNT: // Get number of sectors on the disk (DWORD)
 // get card info
 BSP_MMC_GetCardInfo(&info);
 // set data
 *(DWORD *) buff = info.LogBlockNbr;
 break;
 case GET_SECTOR_SIZE: // Get R/W sector size (WORD)
 // get card info
 BSP_MMC_GetCardInfo(&info);
 // set data
 *(DWORD *) buff = info.LogBlockSize;
 break;
 case GET_BLOCK_SIZE: // Get erase block size in unit of sector (DWORD)
 // get card info
 BSP_MMC_GetCardInfo(&info);
 // set data
 *(DWORD *) buff = info.LogBlockSize / MMC_DEFAULT_BLOCK_SIZE;
 break;
 default:
 // set error
 res = RES_PARERR;
 break;
 }

 // check card state
 while (BSP_MMC_GetCardState() != MMC_TRANSFER_OK) {}

 // unused parameter
 UNUSED(pdrv);

 return res;
 /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

The following code implements a disk file for eMMC. Note that we have not used DMA.

I'm guessing it's a cache issue with memory between SDMMC1 and the UART, but I don't know the exact cause.

 

Can you tell me what I need to fix?

    This topic has been closed for replies.
    Best answer by Tesla DeLorean

    There should be examples under H7 FatFs of "CACHE MAINTANCE" being used to ensure cache coherency when using DMA, and assort DCache Clean / Invalidate by Address functionality.

    You will still need to make sure that RAM buffers are 32-byte aligned, and perhaps implement methods so Polled or DMA methods can be used when that's not the case. ie use single sector polled methods.

    Generally you can avoid this by having large aligned buffers you use for f_read / f_write, and you don't do small / spanning operations.

    https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Middlewares/Third_Party/FatFs/src/drivers/sd_diskio_dma_template_bspv1.c

    In polled mode the address in RAM should matter far less, but bandwidth becomes a significant issues as speed of transfers increase.

    Not sure why the USART's would have an issue, but you can't expect to ignore a SD/MMC mid-transfer, there's a FIFO but it's not going to let you wander off, switch tasks, or do interrupts/callbacks for very long.

    2 replies

    Super User
    March 4, 2024

    I'm guessing it's a cache issue

    Disable the cache to check this idea?

     

    Graduate II
    March 4, 2024

    There should be examples under H7 FatFs of "CACHE MAINTANCE" being used to ensure cache coherency when using DMA, and assort DCache Clean / Invalidate by Address functionality.

    You will still need to make sure that RAM buffers are 32-byte aligned, and perhaps implement methods so Polled or DMA methods can be used when that's not the case. ie use single sector polled methods.

    Generally you can avoid this by having large aligned buffers you use for f_read / f_write, and you don't do small / spanning operations.

    https://github.com/STMicroelectronics/STM32CubeH7/blob/master/Middlewares/Third_Party/FatFs/src/drivers/sd_diskio_dma_template_bspv1.c

    In polled mode the address in RAM should matter far less, but bandwidth becomes a significant issues as speed of transfers increase.

    Not sure why the USART's would have an issue, but you can't expect to ignore a SD/MMC mid-transfer, there's a FIFO but it's not going to let you wander off, switch tasks, or do interrupts/callbacks for very long.

    Explorer
    March 5, 2024

    Thank you.

    I implemented the 32-byte aligned buffer feature in MMC DISKIO based on the DISKIO file in SDIO and used DMA. The problem disappeared after that.