硬件平台:stm32
编译环境:MDK401
驱动方式:SPI总线
编写的函数如下:
1、
u8 SD_Init() SD卡初始化(复位和激活)
2、
u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc) 向SD卡发送一个命令
3、
u32 SD_GetCapacity(void) 获取SD卡的容量
4、
u8 SD_GetCID(u8 *cid_data) 获取SD卡的CID信息,包括制造商信息
5、
u8 SD_GetCSD(u8 *csd_data) 获取SD卡的CSD信息,包括容量和速度信息
6、
u8 SD_ReadMultiBlock(u32 sector, u8 *buffer, u8 count) 读SD卡的多个block
7、
u8 SD_ReadSingleBlock(u32 sector, u8 *buffer) 读SD卡的一个block
8、
u8 SD_ReceiveData(u8 *data, u16 len, u8 release) 从SD卡中读回指定长度的数据,放置在给定位置
9、
u8 SD_WaitReady(void) 等待SD卡Ready
10、
u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count) 写入SD卡的N个block
11、
u8 SD_WriteSingleBlock(u32 sector, const u8 *data) 写入SD卡的一个block
SD卡命令 推荐SD卡命令共分为12类,分别为class0到class11,不同的SDd卡,主控根据其功能,支持不同的命令集 如下:
Class0 (卡的识别、初始化等基本命令集)
CMD0:复位SD 卡.
CMD1:读OCR寄存器.
CMD9:读CSD寄存器.
CMD10:读CID寄存器.
CMD12:停止读多块时的数据传输
CMD13:读 Card_Status 寄存器
Class2 (读卡命令集):
CMD16:设置块的长度
CMD17:读单块.
CMD18:读多块,直至主机发送CMD12为止 .
Class4(写卡命令集) :
CMD24:写单块.
CMD25:写多块.
CMD27:写CSD寄存器 .
Class5 (擦除卡命令集):
CMD32:设置擦除块的起始地址.
CMD33:设置擦除块的终止地址.
CMD38: 擦除所选择的块.
Class6(写保护命令集):
CMD28:设置写保护块的地址.
CMD29:擦除写保护块的地址.
CMD30: Ask the card for the status of the write protection bits
class7:卡的锁定,解锁功能命令集
class8:申请特定命令集 。
class10 -11 :保留
其中 class1, class3,class9:SPI模式不支持SD卡命令格式如下:
解释:6个字节,第一个字节最高两个BIT为01,2-5BYTE为参数,还有一个字节的CRC校验SD卡SPI模式硬件连接:(典型连接)
SD卡两种模式,引角使用情况(
注意:本帖所用SPI模式)
SPI时序如下:
SD卡时序如下:
时序分析(这里我举一个例子)
SD卡读扇区时序
1、拉低CS,选中SD卡2、发送命令17(从本帖的上面查)3、命令发送完毕后不断的读取输出线,读到0xFE,表示命令成功4、准备接收数据,0xFE是数据的开始,此后就可以接收512个字节了 (当然在初始化的时候有用命令16设置块的大小为512)5、数据接收完毕,拉高CS线6、最后等8个时钟,你可以随便发一个BYTE就可以了(严格按照时序,大家别偷懒哦)SD卡读扇区时序
- u8 SD_ReceiveData(u8 *data, u16 len, u8 release)
- {
- u16 retry;
- u8 r1;
- SD_CS_Reset(); // 启动一次传输
- //等待SD卡发回数据起始令牌0xFE
- retry = 0;
- do
- {
- r1 = SPI_ReadWriteByte(0xFF);
- retry++;
- if(retry>200) //200次等待后没有应答,退出报错
- {
- SD_CS_Set();
- return 1;
- }
- }while(r1 != 0xFE);
- //开始接收数据
- while(len--)
- {
- *data = SPI_ReadWriteByte(0xFF);
- data++;
- }
- //下面是2个伪CRC(dummy CRC)
- SPI_ReadWriteByte(0xFF);
- SPI_ReadWriteByte(0xFF);
- //按需释放总线,将CS置高
- if(release == RELEASE)
- {
- //传输结束
- SD_CS_Set();
- SPI_ReadWriteByte(0xFF);
- }
- return 0;
- }
复制代码 SD_SendCommand
- u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc)
- {
- unsigned char r1;
- unsigned char Retry = 0;
- // SPI_ReadWriteByte(0xff);
- //片选端置低,选中SD卡
- SD_CS_Reset();
- //发送
- SPI_ReadWriteByte(cmd | 0x40); //分别写入命令
- SPI_ReadWriteByte(arg >> 24);
- SPI_ReadWriteByte(arg >> 16);
- SPI_ReadWriteByte(arg >> 8);
- SPI_ReadWriteByte(arg);
- SPI_ReadWriteByte(crc);
- //等待响应,或超时退出
- while((r1 = SPI_ReadWriteByte(0xFF))==0xFF)
- {
- Retry++;
- if(Retry > 200)
- {
- break;
- }
- }
- //关闭片选
- //在总线上额外增加8个时钟,让SD卡完成剩下的工作
- SPI_ReadWriteByte(0xFF);
- SD_CS_Set();
- //返回状态值
- return r1;
- }
复制代码 SD卡初始化代码:这里我不敢保证大家的卡都能初始化成功,我自己的是可以,我发送CMD0和CMD1就可以初始化啦网上有更兼容的初始化代码,大家可以看看哦!调试的时候大家看返回值就可以了,哪里没成功就修改哪里哦!
- u8 SD_Init()
- {
- unsigned char time,temp,i;
- SD_CS_Set(); //关闭片选
- for(i=0;i<0x0A;i++) //初始时,首先要发送最少74个时钟信号,这是必须的!!!
- {
- SPI_ReadWriteByte(0xff); //120个时钟
- }
- SD_CS_Reset(); //打开片选
- time=0;
- do
- {
- temp=SD_SendCommand(CMD0, 0 ,0x95);//写入CMD0 复位SD卡
- time++;
- if(time==200)
- {
- SD_CS_Set(); //关闭片选
- }
- }while(temp!=0x01);
- time=0;
- do
- {
- temp=SD_SendCommand(CMD1, 0 , 0xff); //写入CMD1 激活SD卡
- time++;
- if(time==200)
- {
- SD_CS_Set(); //关闭片选
- }
- }while(temp!=0);
- SPI_SetSpeed(1);
- temp = SD_SendCommand(CMD59, 0, 0x01);
- if(temp != 0x00)
- {
- return temp; //命令错误,返回r1
- }
- temp=SD_SendCommand(CMD16,512,0xff);
- if(temp!=0x00)
- {
- return temp ;
- //命令错误,返回r1
- }
- SD_CS_Set(); //关闭片选
- SPI_ReadWriteByte(0xff); //按照SD卡的操作时序在这里补8个时钟
- return 0;
- }
复制代码 写入SD卡的N个block
- u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count)
- {
- u8 r1;
- u16 i;
- //设置为高速模式
- SPI_SetSpeed(SPI_SPEED_HIGH);
- //如果不是SDHC,给定的是sector地址,将其转换成byte地址
- if(SD_Type != SD_TYPE_V2HC)
- {
- sector = sector<<9;
- }
- //如果目标卡不是MMC卡,启用ACMD23指令使能预擦除
- if(SD_Type != SD_TYPE_MMC)
- {
- r1 = SD_SendCommand(ACMD23, count, 0x00);
- }
- //发多块写入指令
- r1 = SD_SendCommand(CMD25, sector, 0x00);
- if(r1 != 0x00)
- {
- return r1; //应答不正确,直接返回
- }
- //开始准备数据传输
- SD_CS_Reset();
- //先放3个空数据,等待SD卡准备好
- SPI_ReadWriteByte(0xff);
- SPI_ReadWriteByte(0xff);
- //--------下面是N个sector写入的循环部分
- do
- {
- //放起始令牌0xFC 表明是多块写入
- SPI_ReadWriteByte(0xFC);
- //放一个sector的数据
- for(i=0;i<512;i++)
- {
- SPI_ReadWriteByte(*data++);
- }
- //发2个Byte的dummy CRC
- SPI_ReadWriteByte(0xff);
- SPI_ReadWriteByte(0xff);
- //等待SD卡应答
- r1 = SPI_ReadWriteByte(0xff);
- if((r1&0x1F)!=0x05)
- {
- SD_CS_Set(); //如果应答为报错,则带错误代码直接退出
- return r1;
- }
- //等待SD卡写入完成
- if(SD_WaitReady()==1)
- {
- SD_CS_Set(); //等待SD卡写入完成超时,直接退出报错
- return 1;
- }
- //本sector数据传输完成
- }while(--count);
- //发结束传输令牌0xFD
- r1 = SPI_ReadWriteByte(0xFD);
- if(r1==0x00)
- {
- count = 0xfe;
- }
- if(SD_WaitReady())
- {
- while(1)
- {
- }
- }
- //写入完成,片选置1
- SD_CS_Set();
- SPI_ReadWriteByte(0xff);
- return count; //返回count值,如果写完则count=0,否则count=1
- }
复制代码 后记:关于SPI读写,我就不发了,大家自己写就是了,当然读扇区,写扇区都是差不多的,只是命令不同,所以我只列举了其中一个,大家把函数都写好。IO设置好,初始化成功后,就可以读一个扇区了,大家取出来,可以用WINHEX对比一下数据是否一致,一般命令成功后就没有什么大问题的。祝大家成功!如果本帖对你有任何帮助,你要支持我哦!如果有任何不懂的地方,请联系我 E-mail:lovelyboy_zjb@163.com QQ:331799388