STM32F407移植FATFS⽂件系统(版本R0.09b)到SD卡
(硬件SPI总线)
⼀、序⾔
经常在⽹上、群⾥看到很多⼈问关于STM32的FATFS⽂件系统移植的问题,刚好⾃⼰最近的⼯程项⽬需要使⽤SD卡,为了让⼤家少⾛弯路,我把我的学习过程和⽅法贡献给⼤家。
⼆、SD卡简介
安全数字卡(简称SD卡),最初引进应⽤于⼿持式可携带电⼦产品,在⼀个⼩尺⼨产品上可靠的存储数据,如移动电话,数码相机等。
1、SD卡简介请参考如下博⽂
2、SD卡种类请参考如下博⽂
3、SD卡简介和种类请参考如下博⽂
4、MMC、SD、TF、SDIO、SDMMC简介
三、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 。
5.10. SD卡初始化的时候,SPI速度必须为低速模式,当SD卡初始化成功以后,才能切换到⾼速模式。
5.10. SD卡读写时的SPI速度必须要综合硬件考虑,假如:单⽚机和SD卡之间的连线距离⽐较长,就不太适合特别⾼的SPI速度模
式,最多适合中等速度模式。
6、SD卡SPI读写流程参考如下博⽂
7、SD卡SPI读写时序图参考如下博⽂
8、深⼊理解SD卡协议
9、SD卡初始化以及命令详解
四、移植前准备⼯作:编写硬件SPI总线驱动程序
我使⽤的是STM32F407IG单⽚机的SPI1,引脚如下。
PA4 ----> NSS
PA5 ---> SCK --->SPI1
PA6 ---> MISO --->SPI1
PA7 ---> MOSI --->SPI1
一念之差的意思需要编写硬件SPI1初始化程序,硬件SPI读程序,硬件SPI写程序、硬件SPI读写程序,设置SPI⾼速模式和低速模式程序。
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_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; //NSS = PA4
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_4);//低电平选通SD卡,⾼电平隔离SD卡
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⾼速模式必须要适应你的板⼦硬件布线要求以及SD卡本⾝的要求,如果速度太⾼,则SD卡读写会出现不稳定。我刚开始就将SPI速度设置为8分频时,时常出现读写SD卡不稳定,将SPI速度设置为16分频后,读写SD卡⼀切正常。
//SPI 速度设置函数
游东田void SPI1_SetSpeed(uint8_t SpeedSet)
{
SPI_InitTypeDef SPI_InitStructure;
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信号由外部管脚管理
switch (SpeedSet)
{
ca enum_SPI_SPEED_LOW:
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//设置到低速模式
break;
ca enum_SPI_SPEED_HIGH:
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; //设置到⾼速模式
break;
default:
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//设置到低速模式
break;
}
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);
}
五、移植前准备⼯作:编写SPI 总线操作SD卡驱动程序
编写SPI总线操作SD卡驱动程序需要⽤到刚刚编写的硬件SPI总线驱动程序。
编写SPI总线操作SD卡驱动程序需要完成如下程序:
1、等待卡准备好 u8 SD_WaitReady(void)
普通话测试
2、等待SD卡回应 u8 SD_GetRespon(u8 Respon)
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 ctor,u8 cnt)
11、写SD卡 u8 SD_WriteDisk(u8*buf,u32 ctor,u8 cnt)