cancel
Showing results for 
Search instead for 
Did you mean: 

Connecting eMMC to SDMMC1 on the STM32H7

Kim KyungTack
Associate III

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?

1 ACCEPTED SOLUTION

Accepted Solutions

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.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

View solution in original post

3 REPLIES 3
Pavel A.
Evangelist III

I'm guessing it's a cache issue

Disable the cache to check this idea?

 

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.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

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.