1、问题描述
使用FAT32 f_write 多次执行写操作时,会报FR_DISK_ERR错误,而且是刚开始写不报错,写几次后会一直报错。
设断点跟踪到HAL_SD_WriteBlocks中,在调用SDMMC_CmdWriteMultiBlock时,会报SDMMC_ERROR_TX_UNDERRUN,意思 是Transmit FIFO underrun
2、原因分析
如下图所示,SDMMC开始写操作时,首先要将数据写入FIFO,然后再将FIFO中的数据取出,通过Data path写入到SD卡。因为时钟频率比较快(最高可达20MHz),因此往FIFO里装填数据必须非常快,否则后面从FIFO取数时就会取不到数,报出Transmit FIFO underrun错误。
可能原因1:
可能是通过APB2总线往FIFO装填数时,发生了中断,导致总线被占用或其他资源被占用,从而无法及时完成FIFO的装填,而后面从FIFO取数的频率又非常快,导致无法取到数。
可能原因2:
SD卡写的时候出现了坏块,导致写不成功,在写失败时需要重试一下。
可能原因3:
写速度太快,导致FIFO来不及装填,产生错误,需要降速。
3、解决办法之禁用中断
在调用f_write之前调用 __disable_irq() 接口关闭中断,写操作完成后调用 __enable_irq()接口启用中断:
__disable_irq();
if(FR_OK ==f_write())
{ }
else
{}
__disable_irq() ;
优点:简单
缺点:在写操作期间CPU无法响应中断
4、解决办法之写重试
重写bsp_driver_sd.c文件中的BSP_SD_WriteBlocks函数是_weak 函数,可以在自己的源文件myfile.c中重写该函数,当写发生错误后,清除错误标记,再重试几次即可,在我的单片机上,一般第一次重试即可成功
uint8_t BSP_SD_WriteBlocks(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout)
{
int i;
for (i = 1; i <= TY_SDIO_RETRY_MAX; i ++)
{
if (HAL_SD_WriteBlocks(&hsd, (uint8_t *)pData, WriteAddr, NumOfBlocks, Timeout) == HAL_OK)
{
if (i > 1)
printf("[INFO] SDIO writing succeeded: retry %d.\n", i);
return (MSD_OK); // Succeeded
}
else
{
printf("[ERROR] SDIO writing failure: retry %d, error code %#x, addr %#x, %u blocks.\n",
i, hsd.ErrorCode, WriteAddr, NumOfBlocks);
HAL_SD_Abort(&hsd); //clear error flag
}
}
return (MSD_ERROR);
}
优点:上层应用软件不需要更改,也不需要禁用中断
缺点:频率较高时(我的是30MHZ)时,经常会发生写重试
5、解决办法之降低写频率(降速)
更改sdio.c中的MX_SDIO_SD_Init函数,将hsd.Init.ClockDiv = 0改为hsd.Init.ClockDiv = 2,写频率会变成原来的1/2,在我的单片机上降频1/2之后,写错误将不再发生。
优点:非常简单;无需禁用中断;无需对上层软件进行修改。
缺点:降低了写速度
6、参考资料
1、https://blog.frankvh.com/2011/12/30/stm32f2xx-stm32f4xx-sdio-interface-part-2/
2、STM32F405/415 Reference manual