2024-03-04 01:29 AM
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.
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?
Solved! Go to Solution.
2024-03-04 10:16 AM
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.
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.
2024-03-04 10:01 AM
> I'm guessing it's a cache issue
Disable the cache to check this idea?
2024-03-04 10:16 AM
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.
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.
2024-03-04 05:28 PM
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.