一、序言
经常在网上、群里看到很多人问关于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文件系统移植
共有条评论 网友评论