底层架构 rinetd Nmap performance object animation struct architecture vue中文 vue学习教程 android常用布局 html好看的字体样式 idea整理代码 centos查看python版本 kubernetes入门 python下载安装教程 python中set的用法 python文件读取 java入门编程 java获得当前日期 java集合图 凯立德地图免费下载 德鲁伊武器 props 数据库系统概论第五版 python队列 js延迟加载 spoonwep 无法打开搜索页 mssql js跳出for循环 jq改变css样式 怎么看淘龄 头条视频解析 字典生成器 马颂德 浣海之核 方正兰亭大黑简体 CST软件 战地4配置
当前位置: 首页 > 学习教程  > 编程语言

STM32F407基于SPI用FATFS文件系统(版本 R0.09b)读写SD卡的移植和实现

2020/11/4 14:29:08 文章标签:

一、序言 经常在网上、群里看到很多人问关于STM32的FATFS文件系统移植的问题,刚好自己最近的工程项目需要使用SD卡,为了让大家少走弯路,我把我的学习过程和方法贡献给大家。 二、SD卡简介 安全数字卡(简称SD卡),最初引进应用于手…

一、序言

    经常在网上、群里看到很多人问关于STM32的FATFS文件系统移植的问题,刚好自己最近的工程项目需要使用SD卡,为了让大家少走弯路,我把我的学习过程和方法贡献给大家。

二、SD卡简介

    安全数字卡(简称SD卡),最初引进应用于手持式可携带电子产品,在一个小尺寸产品上可靠的存储数据,如移动电话,数码相机等。

     1、SD卡简介请参考如下博文

            https://blog.csdn.net/ba_wang_mao/article/details/108665692

    2、SD卡种类请参考如下博文

            https://blog.csdn.net/kris_fei/article/details/77150967

   3、SD卡简介和种类请参考如下博文

             https://blog.csdn.net/ba_wang_mao/article/details/108467489

三、SPI总线读取SD卡简介

         SD卡支持2种总线协议,即SDIO总线协议和SPI总线协议。SDIO总线协议速度快,SPI总线相对SDIO总线速度要慢很多,但是目前市面上很多单片机不支持SDIO总线协议,只有中高端单片机(例如:STM32F407)才支持SDIO总线协议。

      1、SDIO总线协议

         利用该总线协议,可以使用最多四条数据线实现主机与SD卡之间的数据传输,所以速度相对而言可以达到最高但是需要主机具有SDIO控制器,才可以使用该协议。

      2、SPI总线协议

         如果主机不支持SDIO协议,那么可以使用SPI协议对SD卡进行操作。虽然速度比SDIO慢,但是硬件上更加简单,只需要四根线便可以实现与SD卡进行通讯。

     3、SDIO协议与SPI协议的比较

       SDIO协议与SPI协议相较而言,SDIO协议读写SD卡的速度更快,再加上其支持4线模式,即利用4条数据线,同时发送4Bits数据,数据的传输效率就更高了,但是由于使用的引脚较多,所以也导致了控制相对比较困难。
       而SPI外设只具有两条数据线MISO和MOSI,分别用作数据的输入和输出,由于引脚较少,所以控制相对较容易。但是,数据的传输效率相对而言就比较低了。
但是,两中协议的共同之处在于:均是通过命令实现对SD卡的控制,仍然是结合状态机实现编程。

     4、SD卡如何工作在SPI模式下

      当SD卡上电之后,只有第一次发送的CMD0命令才可以选择SD卡工作在SPI模式下。这意味着,当SD卡处于SPI模式下时,仅能通过重新上下电,才能再次选择SD卡的通讯模式,即选择SDIO模式或者SPI模式,否则SD卡将一直处于SPI模式下。并且,SPI模式下的SD卡不支持 V2.0版本之后新增的命令。

     5、SD卡 SPI模式读写要点

       5.1 上电时要延时足够长的时间给 SD 卡一个准备过程,在我的程序里是 5 秒,5秒后才能初始化SD卡。根据不同的卡设置不同的延时时间。 SD 卡初始化第一步在发送 CMD 命令之前,在片选有效的情况下首先要发送至少 74 个时钟,否则将有可能出现 SD 卡不能初始化的问题。

       5.2. SD 卡发送复位命令 CMD0 后,要发送版本查询命令 CMD8 ,返回状态一般分两种,若返回 0x01 表示此 SD 卡接受 CMD8, 也就是说此 SD 卡支持版本 2 ;若返回 0x05 则表示此 SD 卡支持版本 1 。因为不同版本的 SD 卡操作要求有不一样的地方,所以务必查询 SD 卡的版本号,否则也会出现 SD 卡无法正常工作的问题。

       5.3. 理论上要求发送 CMD58 获得 SD 卡电压参数,但实际过程中由于事先都知道了 SD 卡的工作电压,因此可省略这一步简化程序。协议书上也建议尽量不要用这个命令。

      5.4. SD 卡读写超时时间要按照协议说明书书上的给定值 ( 读超时: 100ms ;写超时: 250ms) ,这个值要在程序中准确计算出来,否则将会出现不能正常读写数据的问题。我自己定义了一个计算公式:超时时间 =(8/clk )*arg 。

     5.5. 2GB 以内的 SD 卡 ( 标准卡 ) 和 2GB 以上的 SD 卡 ( 大容量卡 ) 在地址访问形式上不同,这一点尤其要注意,否则将会出现无法读写数据的问题。如标准卡在读写操作时,对读或写命令令牌当中的地址域符初值0x10 ,表示对第 16 个字节以后的地址单元进行操作 ( 前提是此 SD 卡支持偏移读写操作 ) ,而对大容量卡读或写命令令牌当中的地址域符初值 0x10 时,则表示对第 16 块进行读写操作,而且大容量卡只支持块读写操作,块大小固定为 512 字节,对其进行字节操作将会出错。

    5.6. 对某一块要进行写操作时最好先执行擦出命令,这样写入的速度就能大大提高。进行擦除操作时不管是标准卡还是大容量卡都按块操作执行,也就是一次擦除至少 512 字节。

   5.7.  对标准卡进行字节操作时,起始和终止必须在一个物理扇区内,否则将不能进行读写操作。实际操作过程中建议用块操作以提高效率。不管是标准卡还是大容量卡一个读写命令只能对一个块进行操作,不允许跨物理层地址操作。

   5.8. 在写数据块前要先写入若干个 dummy data 字节,写完一个块数据时,主机要监测 MISO 数据线,如果从机处于忙状态这根数据线会保持低电平,这样主机就可以根据这根数据线的状态以决定是否发送下一个命令,在从机没有释放 MISO 数据线之前,主机绝对不能执行其他命令,否则将会导致写入的数据出错,而且从机也不会响应主机的命令。

   5.9. 在 SPI 模式下,CRC 校验是被忽略的,但依然要求主从机发送 CRC 码,只是数值可以是任意值,一般主机的 CRC 码通常设为 0x00 或 0xFF 。

6、SD卡SPI读写流程参考如下博文

          https://blog.csdn.net/ba_wang_mao/article/details/108455980

7、SD卡SPI读写时序图参考如下博文

      https://blog.csdn.net/ba_wang_mao/article/details/108467050

四、移植前准备工作编写SPI总线驱动程序

         我使用的是STM32F407IG单片机的SPI1,引脚如下。

                            PA4  ----> NSS   
                            PA5   ---> SCK     --->SPI1
                            PA6   ---> MISO   --->SPI1
                            PA7  --->  MOSI   --->SPI1

       警告:SD卡上电时要延时足够长的时间后才允许调用SD 卡初始化程序(SD_Initialize(void))。

       1、SPI1初始化程序

void SPI1_Configuration(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef SPI_InitStructure;

	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

	GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
	
	//Configure SPI1 Pins: SCK, MISO and MOSI
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//Configure NSS Pin
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;	
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
  	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;		//CS/NSS
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOA, GPIO_Pin_4);//不选中(关闭片选)

	SPI_I2S_DeInit(SPI1);
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//主器件
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//8位数据长度
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;   		//这里要注意,一定要配置为上升沿数据有效,因为SD卡为上升沿数据有效
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;		//这里要注意,一定要配置为SPI_CPHA_2Edge(数据捕获于第2个时钟沿),参见SD卡协议要求
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			//NSS信号由外部管脚管理
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//SPI速度为低速
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输的第一个字节为MSB
	SPI_InitStructure.SPI_CRCPolynomial = 7;			//CRC的多项式
	SPI_Init(SPI1,&SPI_InitStructure);
	SPI_Cmd(SPI1,DISABLE);
	SPI_Cmd(SPI1,ENABLE);
}

2、SPI1读写程序


void SPI_WriteByte(uint8_t _ucByte)
{
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE )==RESET);	//等待数据发送寄存器清空
	SPI_I2S_SendData(SPI1 , _ucByte);								//通过SPI发送出去一个字节数据
	while(SPI_I2S_GetFlagStatus(SPI1 , SPI_I2S_FLAG_RXNE )==RESET);	//等待接收到一个数据(接收到一个数据就相当于发送一个数据完毕)
	SPI_I2S_ReceiveData(SPI1);										//返回接收到的数据
}



uint8_t SPI_ReadByte(void)
{
	uint8_t ch;
	
	
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE )==RESET);
	SPI_I2S_SendData(SPI1 , 0xFF);
	while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE )==RESET);
	ch = SPI_I2S_ReceiveData(SPI1);
	return (ch);
}



uint8_t SPI_ReadWriteByte(uint8_t _ucByte)
{  
	uint8_t ch;
	
	
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);		// 等待发送缓冲区空
	SPI_I2S_SendData(SPI1, _ucByte);
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);		// 等待数据接收完毕
	ch = SPI_I2S_ReceiveData(SPI1);
	return (ch);
}

3、SPI速度模式设置程序

    我们知道SD卡初始化时,必须要设置为SPI低速模式,只有当SD卡初始化完毕,进行文件读写时,才允许SPI高速模式。


//SPI 速度设置函数
//SpeedSet:
//SPI_BaudRatePrescaler_2   2分频   (SPI 36M@sys 72M)
//SPI_BaudRatePrescaler_8   8分频   (SPI 9M@sys 72M)
//SPI_BaudRatePrescaler_16  16分频  (SPI 4.5M@sys 72M)
//SPI_BaudRatePrescaler_256 256分频 (SPI 281.25K@sys 72M)
void SPI1_SetSpeed(uint8_t SpeedSet)
{
	SPI1->CR1&=0XFFC7;//位3-5清零,用来设置波特率
	//SPI1->CR1|=SPI_BaudRatePrescaler; //设置SPI1速度
	switch (SpeedSet)
	{
		case 0:			
			SPI1->CR1|= SPI_BaudRatePrescaler_256;//设置到低速模式
			break;
		case 1:
			SPI1->CR1|= SPI_BaudRatePrescaler_8;//设置到高速模式
			break;	
		default:
			SPI1->CR1|= SPI_BaudRatePrescaler_256;//设置到低速模式
			break;			
	}
	SPI_Cmd(SPI1,ENABLE);
}

 五、移植前准备工作编写SPI 总线操作SD卡驱动程序

需要编写如下SPI总线操作SD卡驱动程序:

1、等待卡准备好 u8 SD_WaitReady(void)

2、等待SD卡回应 u8 SD_GetResponse(u8 Response)

3、从sd卡读取一个数据包的内容  u8 SD_RecvData(u8*buf,u16 len)

4、向sd卡写入一个数据包的内容 512字节 u8 SD_SendBlock(u8*buf,u8 cmd)

5、向SD卡发送一个命令 u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)

6、获取SD卡的CID信息,包括制造商信息 u8 SD_GetCID(u8 *cid_data)

7、获取SD卡的CSD信息,包括容量和速度信息 u8 SD_GetCSD(u8 *csd_data)

8、获取SD卡的总扇区数(扇区数) u32 SD_GetSectorCount(void)

9、初始化SD卡  u8 SD_Initialize(void)

10、读SD卡   u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)

11、写SD卡   u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)

 

SPI总线操作SD卡驱动程序可参考如下博文

https://blog.csdn.net/ba_wang_mao/article/details/108455941

https://blog.csdn.net/ba_wang_mao/article/details/108370483

https://blog.csdn.net/ba_wang_mao/article/details/108330261

还可参考如下博文:SD卡在SPI模式下的初始化和详细的代码分析

https://blog.csdn.net/ba_wang_mao/article/details/108475977

六、FATFS文件系统移植

 

 

 

 

 

            

     


本文链接:
http://www.dtmao.cc/news_show_350156.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?