【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的文件打开关闭,尽量打开一次后,后面只管写就行。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32F4基于SDIO总线的读写SD卡需要使用FATFS文件系统。要实现这个功能,首先需要配置STM32F4的SDIO接口以及相应的GPIO引脚。接下来,我们可以按照以下步骤进行读写SD卡: 1. 初始化SDIO接口:初始化SDIO控制器,并设置时钟和数据传输属性。 2. 初始化GPIO引脚:配置SDIO通信需要使用的GPIO引脚,包括CMD线、CLK线、以及数据线(D0-D3)。 3. 初始化SD卡使用SDIO发送SD卡初始化命令,以便与SD卡进行通信。这个过程包括等待SD卡响应、发送上电命令、以及读取SD卡的OCR(操作条件寄存器)。 4. 检查SD卡状态:通过发送CMD13命令,读取SD卡状态信息。这个过程可以确保SD卡正常工作并准备好读取或写入数据。 5. 挂载文件系统:使用FATFS库中的函数,将SD卡上的文件系统挂载到指定的逻辑驱动器。 6. 打开文件:使用FATFS库中的函数,打开指定的文件。 7. 读取或写入数据:使用FATFS库中的函数,读取或写入数据到SD卡上的文件。 8. 关闭文件:使用FATFS库中的函数,关闭文件。 9. 卸载文件系统:使用FATFS库中的函数,卸载SD卡上的文件系统。 需要注意的是,在进行读写操作时,需要确保SD卡已经插入并且正确连接到SDIO接口上。此外,还需要根据SD卡的规格以及相应的存储格式,进行适当的读写操作。 以上就是基于SDIO总线的读写SD卡的基本步骤。通过使用FATFS库,我们可以很方便地实现对SD卡读写操作,以便进行数据存储和读取。希望这个回答能够对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值