【STM32】解决STM32F407使用FATFS SDIO读写SD卡时可能出现卡死问题
问题描述
在使用STM32F407VGT6的FATFS+SDIO进行SD卡读写时,发现在第一次读写有概率卡死导致看门狗重启,还有种情况是第一次读写没卡死,但读写一段时间后也会卡死。我用的是正点原子的标准库的FATFS移植过来的,使用的是DMA模式
问题分析
问题1
通过打印分析,找到第一次上电有概率卡死是出现在正点原子提供的标准库FATFS文件的sdio_sdcard.c文件的while (DMA_GetCmdStatus(DMA2_Stream3) != DISABLE){}一句。
处理方法:增加超时处理,并手动清除标志位
//配置SDIO DMA
//mbuf:存储器地址
//bufsize:传输数据量
//dir:方向;DMA_DIR_MemoryToPeripheral 存储器-->SDIO(写数据);DMA_DIR_PeripheralToMemory SDIO-->存储器(读数据);
void SD_DMA_Config(u32*mbuf,u32 bufsize,u32 dir)
{
DMA_InitTypeDef DMA_InitStructure;
//while (DMA_GetCmdStatus(DMA2_Stream3) != DISABLE){}//等待DMA可配置
//DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_FEIF3 | DMA_FLAG_DMEIF3 | DMA_FLAG_TEIF3 | DMA_FLAG_HTIF3 | DMA_FLAG_TCIF3); //清除DMA(直接存储器访问)控制器中特定流的错误和状态标志
//DMA_Cmd(DMA2_Stream3, DISABLE);
uint32_t timeout = 5000; // 超时次数
while ((DMA_GetCmdStatus(DMA2_Stream3) != DISABLE) && (timeout > 0)) //添加超时机制避免因DMA错误导致卡死
{
timeout--;
}
if (timeout == 0)
{
DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_FEIF3 | DMA_FLAG_DMEIF3 | DMA_FLAG_TEIF3 | DMA_FLAG_HTIF3 | DMA_FLAG_TCIF3); //清除DMA(直接存储器访问)控制器中特定流的错误和状态标志
DMA_Cmd(DMA2_Stream3, DISABLE);
}
DMA_DeInit(DMA2_Stream3);//清空之前该stream3上的所有中断标志
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
DMA_Ini
这样,解决了第一次读写SD卡有概率卡死的问题解决。
问题2
下面来看,对于正常运行读写过程也有概率卡死的问题。
通过打印发现,卡死问题是f_read或者f_write处卡死。考虑是否是配置问题?查找网上,参考了硬汉嵌入式的一些配置方案:[FatFs] 带FatFS的SD卡写数据出错情况测试记录,及其解决办法,仍没能解决我的问题。
后面又参考了正点原子论坛一位大佬写的方案:
//最近在用STM32F103Vet6+STemwin做一个拼音输入法的项目,所以字库必须放到TF卡上,
//所以必使用到SD卡驱动,因为同时要使用USB访问SD卡,所以SD卡必须保证在最佳性能状态,
//在网上找了几种驱动测试,都出现卡死现像,包括原子哥的驱动同出现问题.还试了中断模式和查询模式,
//只有查询模式效果最好,但是要把SD卡的工作频率降到10M以下才能工作正常.就将就用查询模式吧,就在昨晚又开始死机了,
//没办法了,但是总不能放弃项目吧,只好分析代码了,再静下来想一想,SD的工作频率24M,CPU的频率才72M,
//在4位模式下那CPU起不是要在10个周期(72\24\2+开销)内处理完SD卡的传送?立该用printf在SD_Read上把结果打印出来,
//结果返回RXOVRRUN,明显了,就是CPU取数速度跟不上SDIO。好,问题找到了,就开始修改
DRESULT disk_read (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to read (1..255) */
)
{
BYTE i;
SD_Error Status;
if (!count) return RES_PARERR; //count????????0??·??ò·????????í?ó
if(drv==0)
{
//int i;
//Status = SD_ReadDisk(buff,sector,count);
/*for(i=0;i<512;i++)
{
Debug("0x%02x ",*(buff+i));
}
Debug("\r\n");*/
/*switch(SD_Mode)
{
case 0: //dma·???
if(count==1)// 1??sector??????×÷
{ */
for(i = 0; i < count; i++)
{
Status = SD_ReadBlock((sector + i) << 9,(u32 *)(&buff[i<<9]),BlockSize);
//Debug("Read:%d\r\n",Status);
}
/* }
else
{
Status = SD_ReadMultiBlocks(sector << 9,(u32 *)(&buff[0]),BlockSize,count);
}
break;
case 1:
DISABLE_INT();
if(count==1)
{
Status = SD_ReadBlock(sector<<9,(u32 *)(&buff[0]),BlockSize);
}
else
{
Status = SD_ReadMultiBlocks(sector<<9 ,(u32 *)(&buff[0]),BlockSize,count);
}
ENABLE_INT();*/
/* break;
default:
Status=SD_ERROR;
}*/
if(Status == SD_OK)
return RES_OK;
else
return RES_ERROR;
}
else//???§??????0????×÷
{
return RES_ERROR;
}
//从上面代码可以看到我把MutilReadBlock屏蔽了,为什么要屏蔽呢?我们来分析一吓,因为DMA SIZE只有512字节,
//如果我们用MutilReadBlock来读SD卡的话,由于CPU速度问题,CPU没有把512字节取完,SDIO就会继续向DMA里传数据,
//这里如果DMA满了,就会产生RXOVRRUN。改了以上代码测试,结果返回OK了,但是STemwin的界面还是没有正确,
//在这里估计是掉数据了,于是再分析ReadBlock方法
SD_Error SD_ReadBlock(uint32_t addr, uint32_t *readbuff, uint16_t BlockSize)
{
SD_Error errorstatus = SD_OK;
uint32_t count = 0, *tempbuff = readbuff;
uint8_t power = 0;
if (NULL == readbuff)
{
errorstatus = SD_INVALID_PARAMETER;
return(errorstatus);
}
TransferError = SD_OK;
TransferEnd = 0;
TotalNumberOfBytes = 0;
/* Clear all DPSM configuration */
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = 0;
SDIO_DataInitStructure.SDIO_DataBlockSize = SDIO_DataBlockSize_1b;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Disable;
SDIO_DataConfig(&SDIO_DataInitStructure);
SDIO_DMACmd(DISABLE);
if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED)
{
errorstatus = SD_LOCK_UNLOCK_FAILED;
return(errorstatus);
}
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
addr /= 512;
}
if ((BlockSize > 0) && (BlockSize <= 2048) && ((BlockSize & (BlockSize - 1)) == 0))
{
power = convert_from_bytes_to_power_of_two(BlockSize);
/* Set Block Size for Card */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SDIO_SET_BLOCKLEN);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
}
else
{
errorstatus = SD_INVALID_PARAMETER;
return(errorstatus);
}
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
if(DeviceMode == SD_INTERRUPT_MODE)
{
SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_RXFIFOHF | SDIO_IT_STBITERR, ENABLE);
}
else if(DeviceMode == SD_DMA_MODE)
{
SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE);
SDIO_DMACmd(ENABLE);
DMA_RxConfiguration(readbuff, BlockSize);
} //DMA配置代码和中断使能代码放这里
//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) power << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);
TotalNumberOfBytes = BlockSize;
StopCondition = 0;
DestBuffer = readbuff;
/* Send CMD17 READ_SINGLE_BLOCK */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)addr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_READ_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
//SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_Pend;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SDIO_READ_SINGLE_BLOCK);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* In case of single block transfer, no need of stop transfer at all.*/
if (DeviceMode == SD_POLLING_MODE)
{
/* Polling mode */
while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
{
if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
{
for (count = 0; count < 8; count++)
{
*(tempbuff + count) = SDIO_ReadData();
}
tempbuff += 8;
}
}
if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
errorstatus = SD_DATA_TIMEOUT;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
errorstatus = SD_DATA_CRC_FAIL;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
errorstatus = SD_RX_OVERRUN;
return(errorstatus);
}
else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
{
SDIO_ClearFlag(SDIO_FLAG_STBITERR);
errorstatus = SD_START_BIT_ERR;
return(errorstatus);
}
while (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)
{
*tempbuff = SDIO_ReadData();
tempbuff++;
}
/* Clear all the static flags */
SDIO_ClearFlag(SDIO_STATIC_FLAGS);
}
else if (DeviceMode == SD_INTERRUPT_MODE)
{
while ((TransferEnd == 0) && (TransferError == SD_OK))
{}
if (TransferError != SD_OK)
{
return(TransferError);
}
}
else if (DeviceMode == SD_DMA_MODE)
{
//这里原来放DMA配置和中断使能代码
while (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET)
{}
}
return(errorstatus);
}
//分析上面代码,发现官方源码是在SDIO开始传送后,DMA才配置,这样做,在高速的CPU上可能没问题,
//但在103上肯定掉数据,于上就修改成上面的方式,改好后,STemwin终于正常了,至此SD驱动应该完美解决卡死问题
貌似有些效果,但还是会卡死重启,查看硬汉嵌入式论坛,有人提到不要频繁打开关闭文件,这样也有可能出错。于是查看自己的代码,确实之前写的是每存10帧数据就重新打开关闭下文件,于是我注释并修改了这部分代码,使之在上电时创建文件后就不再频繁打开关闭该文件,一直写入或者读取就行:
void save_data(u8 *data,u8 len)
{
//FRESULT ret=FR_OK;
char ReadBuffer[36]={0};
FIL fp2;
UINT fnum=0,rnum;
if(time_unix>5)
{
if(data_num==0)
{
f_open(&fp2,"txtname",FA_OPEN_EXISTING | FA_READ);
f_read(&fp2,ReadBuffer,36,&rnum); //把txtname文件里面的txt文件名读取出来存到ReadBuffer
f_close(&fp2);
f_open(&fp1,ReadBuffer,FA_WRITE | FA_OPEN_ALWAYS | FA_READ); //创建并打开这个名字的txt文件
//printf("开始保存数据 保存到文件%s\r\n",ReadBuffer);
f_lseek(&fp1, f_size(&fp1)); //指向文件内容的最后
}
f_write(&fp1,data,len,&fnum); //写入数据到该文件
f_sync(&fp1);
//data_num++;
// if(data_num>=10)
// {
// //f_close(&fp1);//暂时先注释,考虑采用f_write+f_sync的方式,即文件打开后,不要关闭........................................
// data_num=0;
// }
}
}
最后运行发现,单片机运行几个小时都不会再因为SD卡而卡死重启了。
后续
后续一直运行比较稳定,不会出现卡死重启问题。只有当sd卡自身出现问题导致无法读写时,才会导致单片机卡死,此时只需要使用DiskGenius Pro v5.1.1.696工具重新格式化SD卡即可。总结来说,借鉴嵌入式硬汉哥的话,不要频繁对sd的文件打开关闭,尽量打开一次后,后面只管写就行。
作者:ℳ๓. Sweet