cancel
Showing results for 
Search instead for 
Did you mean: 

eMMC, what is the proper way to detect when a multiple sector write or read is completed?

Craig B
Associate II

We have a custom board with an STM32F469 and a SanDisk iNAND 7250-I 8GB eMMC. We have our HCLK set to 168MHz and SDIO clock at 48MHz, 8-bit data width. Using CubeMX (v4.26.1), the user is responsible for supplying the necessary code in the user_diskio.c to make things work. This seems like it should be very similar to the code generated in sd_diskio.c when using an sd card. So I started with that code and modified it for MMC use. Here's the result:

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 */
	/* USER CODE HERE */
 
    UNUSED(pdrv);
    DRESULT res = RES_ERROR;
    WriteStatus = 0;
    uint32_t timeout;
#if (ENABLE_SD_DMA_CACHE_MAINTENANCE == 1)
    uint32_t alignedAddr;
  /*
   the SCB_CleanDCache_by_Addr() requires a 32-Byte aligned address
   adjust the address and the D-Cache size to clean accordingly.
   */
  alignedAddr = (uint32_t)buff &  ~0x1F;
  SCB_CleanDCache_by_Addr((uint32_t*)alignedAddr, (int32_t) (count * BLOCKSIZE + ((uint32_t)buff - alignedAddr)));
#endif
 
    if(HAL_MMC_WriteBlocks_DMA(&hmmc, (uint8_t *) buff,
                               (uint32_t) (sector),
                               count) == HAL_OK)
    {
        /* Wait that writing process is completed or a timeout occurs */
 
        timeout = HAL_GetTick();
        while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
        {
        }
 
        /* incase of a timeout return error */
        if (WriteStatus == 0)
        {
            res = RES_ERROR;
        }
        else
        {
            WriteStatus = 0;
            timeout = HAL_GetTick();
 
            while((HAL_GetTick() - timeout) < SD_TIMEOUT)
            {
                if(HAL_MMC_GetCardState(&hmmc) == HAL_MMC_CARD_TRANSFER)
                {
                    res = RES_OK;
                    break;
                }
                HAL_Delay(6);
            }
        }
    }
    return res;
  /* USER CODE END WRITE */
}

...along with interrupt handlers:

static volatile UINT WriteStatus = 0;
static volatile UINT ReadStatus = 0;
 
void HAL_MMC_TxCpltCallback(MMC_HandleTypeDef *hmmc) {
    UNUSED(hmmc);
    WriteStatus = 1;
}
 
void HAL_MMC_RxCpltCallback(MMC_HandleTypeDef *hmmc) {
    UNUSED(hmmc);
    ReadStatus = 1;
}

My test code writes 100 blocks at a time, reads them back and does a memory compare to ensure the read and write buffers agree. The test continues incrementing the destination sector address by 100. Ultimately it will write/read/compare every sector on the eMMC.

You will notice in my code though, that I've added a HAL_Delay call. This is because simply checking for the card state to return to HAL_MMC_CARD_TRANSFER is not enough. If I only do that, it will fail fairly quickly with an error code of SDMMC_ERROR_CMD_CRC_FAIL. If I continue to try to write, I'll get another SDMMC_ERROR_CMD_CRC_FAIL, then any writes that follow will fail with a SDMMC_ERROR_CMD_RSP_TIMEOUT.

Referencing the JEDEC Embedded Multi-Media Card (e•MMC) Electrical Standard (5.0), I see that they recommend the following: "Some Devices may require long and unpredictable times to write a block of data. After receiving a block of data and completing the CRC check, the Device will begin writing and hold the DAT0 line low. The host may poll the status of the Device with a SEND_STATUS command (CMD13) at any time, and the Device will respond with its status (except in Sleep state). The status bit READY_FOR_DATA indicates whether the Device can accept new data or not. The host may deselect the Device by issuing CMD7 which will displace the Device into the Disconnect State and release the DAT0 line without interrupting the write operation. When reselecting the Device, it will reactivate busy indication by pulling DAT0 to low. See 6.15 for details of busy indication".

I have tried checking the READY_FOR_DATA bit (not shown in the code above), but find I still need a delay for things to continue working. It sounds like that READY_FOR_DATA bit should be reflecting the state of DAT0, but that isn't explicitly stated.

Have anyone else run into this problem? How did you solve it? It sure seems like there must be a better way than using a delay as I have.

As a follow-up question, what is the best way to handle an error from a command? Whenever I get an error like the ones I mentioned above, it seems the eMMC will stay in a bad state leaving it unusable. Perhaps deselecting/selecting the device or putting it in/out of idle state would get it usable again?

Thanks,

Craig

4 REPLIES 4

You might want to look at the FIFO depth on the write side, and wait for that to clear, DMA will complete relatively early.

Can't say I've run into issue with this, and I've written a lot of data to assorted cards/chips. Generally we write long LSFR sequences that don't align with sector/cluster boundaries.

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

After a write I poll the card until it's in the Tran state and READY_FOR_DATA is asserted. The delay between polls to the card need only be 8 or more SDMMC_CK cycles (MMC spec's "Nrc") (if I read all that stuff correctly).

I actually do the polling both before and after reading or writing - eMMC's live in their own little world and can go off and do internal stuff whenever they decide to, especially if their internal cache is enabled.

One other thing I noticed: the F7 V1.11 HAL's handling of ErrorCode is still sometimes inconsistent in the MMC code. If you're going to rely on it make sure to verify it's being set properly. In my case, I explicitly set ErrorCode to HAL_MMC_ERROR_NONE before calling HAL_MMC_GetCardState().

And yeah, once an eMMC gets in a bad state it seems to want to stay there. I haven't found a graceful recovery.

Craig B
Associate II

Thanks Clive and David. I'll try the suggestions and report back how it went.

did you find any way to recover the eMMC from bad state?