本帖最后由 RISCVLAR 于 2020-12-12 11:39 编辑
CH32V103应用教程——SD卡测试
本章教程将在CH32V103开发板上实现SD卡检测和SD卡容量信息读取。
1、SD卡简介及相关函数介绍 SD存储卡( Secure Digital Memory Card)是一种基于半导体快闪存储器的新一代高速存储设备。SD存储卡的技术是从MMC卡( MultiMedia Card格式上发展而来,在兼容SD存储卡基础上发展了SDIO( SD Input/ Output)卡,此兼容性包括机械,电子,电力,信号和软件,通常将SD、SDIO卡俗称SD存储卡。
SD卡具有高**容量、快速数据传输率、极大的移动灵活性以及很好的安全性,它被广泛地应用于便携式装置上,例如数码相机、平板电脑和多媒体播放器等。根据SD卡容量大小不同,可将SD卡分为以下几类: 0~2G:SD卡;2~32G:SDHC卡;32~2T:SDXC卡;
其中,SD卡协议和SDHC卡版本协议基本兼容,但SDXC卡与SD卡和SDHC卡相差较大,本章主要介绍使用SD卡和SDHC卡。
控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是SPI接口,另外一种是SDIO接口。 SPI协议驱动SD卡方式相较于SDIO驱动SD卡方式,使用引脚资源少,控制程序相对较为简单,但传输速度不如使用SDIO接口的快。由于CH32V103开发板上SD卡槽与芯片引脚的连接方式为SPI驱动方式,因此本章教程采用SPI协议驱动SD卡方式。 使用SPI方式驱动SD卡需要用到以下几个寄存器: ① CID:卡识别号(Card identification number),用来识别卡的唯一的个体号码; ② CSD:卡的特定数据(Card Specific Data),指卡的操作条件信息; ③ SCR:SD配置寄存器(SD Configuration Register),SD卡特殊性信息; ④ OCR:操作条件寄存器(Operation conditions register) 关于SD卡其他寄存器信息,可参考SD 简易规格文件《 Physical Layer Simplified Specification V2.0》。
关于SD命令格式,固定为48bit,通过命令线(CMD)连续传输,其具体命令格式以及命令解释如下: 起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为0,终止位为1。 传输标志:用于区分传输方向,该位为1时表示命令,方向为主机传输到SD卡,该位为0时表示响应,方向为SD卡传输到主机。 命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分。 1.命令号:它固定占用6bit,所以总共有64个命令(代号: CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于SPI总线,或不适用于SD卡操作,只是专门用于MMC卡或者SDI/O卡。 2.地址/参数:每个命令有32bit地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这32bit用于指定参数,而寻址命令这32bit用于指定目标SD卡的地址,使用SPI总线驱动时,通过片选引脚来选择不同的卡,所以使用这些命令时地址可填充任意值。 3.CRC7校验:长度为7bit的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。使用SPI驱动时,命令中的CRC7校验默认是关闭的,即这CRC7校验位中可以写入任意值而不影响通讯,仅在发送CMD0命令时需要强制带标准的CRC7校验。
SPI模式下有以下几个重要的操作指令: SD卡R1响应格式如下: 关于SD卡具体信息,可参考《 Physical Layer Simplified Specification V2.0》,关于SPI具体配置介绍,可参考CH32V103应用手册以及前面SPI教程介绍。
2、硬件设计 本章教程主要进**检测、初始化以及读取SD卡容量大小,需要用到CH32V103开发板TF CARD模块以及一张容量大小不超过32G的SD卡。
3、软件设计 本章教程由于使用SPI驱动方式,需要用到部分SPI程序,关于SPI内容在此不再过多介绍,SD卡具体程序如下: sd.h文件 - #ifndef __SD_H_
- #define __SD_H_
- #include "ch32v10x_conf.h"
- // SD卡类型定义
- #define SD_TYPE_ERR 0X00
- #define SD_TYPE_MMC 0X01
- #define SD_TYPE_V1 0X02
- #define SD_TYPE_V2 0X04
- #define SD_TYPE_V2HC 0X06
- // SD卡指令表
- #define CMD0 0 //卡复位
- #define CMD1 1
- #define CMD8 8 //命令8 ,SEND_IF_COND
- #define CMD9 9 //命令9 ,读CSD数据
- #define CMD10 10 //命令10,读CID数据
- #define CMD12 12 //命令12,停止数据传输
- #define CMD16 16 //命令16,设置SectorSize 应返回0x00
- #define CMD17 17 //命令17,读sector
- #define CMD18 18 //命令18,读Multi sector
- #define CMD23 23 //命令23,设置多sector写入前预先擦除N个block
- #define CMD24 24 //命令24,写sector
- #define CMD25 25 //命令25,写Multi sector
- #define CMD41 41 //命令41,应返回0x00
- #define CMD55 55 //命令55,应返回0x01
- #define CMD58 58 //命令58,读OCR信息
- #define CMD59 59 //命令59,使能/禁止CRC,应返回0x00
- //数据写入回应字意义
- #define MSD_DATA_OK 0x05
- #define MSD_DATA_CRC_ERROR 0x0B
- #define MSD_DATA_WRITE_ERROR 0x0D
- #define MSD_DATA_OTHER_ERROR 0xFF
- //SD卡回应标记字
- #define MSD_RESPONSE_NO_ERROR 0x00
- #define MSD_IN_IDLE_STATE 0x01
- #define MSD_ERASE_RESET 0x02
- #define MSD_ILLEGAL_COMMAND 0x04
- #define MSD_COM_CRC_ERROR 0x08
- #define MSD_ERASE_SEQUENCE_ERROR 0x10
- #define MSD_ADDRESS_ERROR 0x20
- #define MSD_PARAMETER_ERROR 0x40
- #define MSD_RESPONSE_FAILURE 0xFF
- //这部分应根据具体的连线来修改!
- //MiniSTM32开发板使用的是PA3作为SD卡的CS脚.
- #define SD_CS_H GPIO_SetBits(GPIOA,GPIO_Pin_3) //SD卡片选引脚拉高
- #define SD_CS_L GPIO_ResetBits(GPIOA,GPIO_Pin_3) //SD卡片选引脚拉低
- extern u8 SD_Type; //SD卡的类型
- //函数申明区
- u8 SD_SPI_ReadWriteByte(u8 data);
- u8 SD_Detect(void); //检测SD卡是否存在
- u8 SD_WaitReady(void); //等待SD卡准备
- u8 SD_GetResponse(u8 Response); //获得相应
- u8 SD_Initialize(void); //初始化
- u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt); //读块
- u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt); //写块
- u32 SD_GetSectorCount(void); //读扇区数
- u8 SD_GetCID(u8 *cid_data); //读SD卡CID
- u8 SD_GetCSD(u8 *csd_data); //读SD卡CSD
- #endif
sd.h文件主要包括各种宏定义及函数声明,包括SD卡类型定义、SD卡指令定义等以及函数声明;
sd.c文件
- #include "sd.h"
- #include "spi.h"
- u8 SD_Type=0;//SD卡的类型
- //data:要写入的数据
- //返回值:读到的数据
- u8 SD_SPI_ReadWriteByte(u8 data)
- {
- return SPI1_ReadWriteByte(data);
- }
- u8 SD_Detect(void)
- {
- u8 status = 1;
- /* Check GPIO to detect SD */
- if (GPIO_ReadInputData(GPIOA) & GPIO_Pin_8)
- {
- status = 0;
- }
- return status;
- }
- //SPI硬件层初始化
- void SD_SPI_Init(void)
- {
- //设置硬件上与SD卡相关联的控制引脚输出
- //禁止其他外设(NRF/W25Q64)对SD卡产生影响
- GPIO_InitTypeDef GPIO_InitStructure;
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); //PORTA时钟使能
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_SetBits(GPIOA,GPIO_Pin_3); //PA3上拉
- SPI1_Init();
- SD_CS_H;
- }
- //取消选择,释放SPI总线
- void SD_DisSelect(void)
- {
- SD_CS_H;
- SD_SPI_ReadWriteByte(0xff);//提供额外的8个时钟
- }
- //选择sd卡,并且等待卡准备OK
- //返回值:0,成功;1,失败;
- u8 SD_Select(void)
- {
- SD_CS_L;
- if(SD_WaitReady()==0)return 0;//等待成功
- SD_DisSelect();
- return 1;//等待失败
- }
- //等待卡准备好
- //返回值:0,准备好了;其他,错误代码
- u8 SD_WaitReady(void)
- {
- u32 t=0;
- do
- {
- if(SD_SPI_ReadWriteByte(0XFF)==0XFF)return 0;//OK
- t++;
- }while(t<0XFFFFFF);//等待
- return 1;
- }
- //等待SD卡回应
- //Response:要得到的回应值
- //返回值:0,成功得到了该回应值
- // 其他,得到回应值失败
- u8 SD_GetResponse(u8 Response)
- {
- u16 Count=0xFFFF;//等待次数
- while ((SD_SPI_ReadWriteByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应
- if (Count==0)return MSD_RESPONSE_FAILURE;//得到回应失败
- else return MSD_RESPONSE_NO_ERROR;//正确回应
- }
- //从sd卡读取一个数据包的内容
- //buf:数据缓存区
- //len:要读取的数据长度.
- //返回值:0,成功;其他,失败;
- u8 SD_RecvData(u8*buf,u16 len)
- {
- if(SD_GetResponse(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
- while(len--)//开始接收数据
- {
- *buf=SPI1_ReadWriteByte(0xFF);
- buf++;
- }
- //下面是2个伪CRC(dummy CRC)
- SD_SPI_ReadWriteByte(0xFF);
- SD_SPI_ReadWriteByte(0xFF);
- return 0;//读取成功
- }
- //向sd卡写入一个数据包的内容 512字节
- //buf:数据缓存区
- //cmd:指令
- //返回值:0,成功;其他,失败;
- u8 SD_SendBlock(u8*buf,u8 cmd)
- {
- u16 t;
- if(SD_WaitReady())return 1;//等待准备失效
- SD_SPI_ReadWriteByte(cmd);
- if(cmd!=0XFD)//不是结束指令
- {
- for(t=0;t<512;t++)SPI1_ReadWriteByte(buf[t]);//提高速度,减少函数传参时间
- SD_SPI_ReadWriteByte(0xFF);//忽略crc
- SD_SPI_ReadWriteByte(0xFF);
- t=SD_SPI_ReadWriteByte(0xFF);//接收响应
- if((t&0x1F)!=0x05)return 2;//响应错误
- }
- return 0;//写入成功
- }
- //向SD卡发送一个命令
- //输入: u8 cmd 命令
- // u32 arg 命令参数
- // u8 crc crc校验值
- //返回值:SD卡返回的响应
- u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)
- {
- u8 r1;
- u8 Retry=0;
- SD_DisSelect();//取消上次片选
- if(SD_Select())return 0XFF;//片选失效
- //发送
- SD_SPI_ReadWriteByte(cmd | 0x40);//分别写入命令
- SD_SPI_ReadWriteByte(arg >> 24);
- SD_SPI_ReadWriteByte(arg >> 16);
- SD_SPI_ReadWriteByte(arg >> 8);
- SD_SPI_ReadWriteByte(arg);
- SD_SPI_ReadWriteByte(crc);
- if(cmd==CMD12)SD_SPI_ReadWriteByte(0xff);//Skip a stuff byte when stop reading
- //等待响应,或超时退出
- Retry=0X1F;
- do
- {
- r1=SD_SPI_ReadWriteByte(0xFF);
- }while((r1&0X80) && Retry--);
- //返回状态值
- return r1;
- }
- //获取SD卡的CID信息,包括制造商信息
- //输入: u8 *cid_data(存放CID的内存,至少16Byte)
- //返回值:0:NO_ERR
- // 1:错误
- u8 SD_GetCID(u8 *cid_data)
- {
- u8 r1;
- //发CMD10命令,读CID
- r1=SD_SendCmd(CMD10,0,0x01);
- if(r1==0x00)
- {
- r1=SD_RecvData(cid_data,16);//接收16个字节的数据
- }
- SD_DisSelect();//取消片选
- if(r1)return 1;
- else return 0;
- }
- //获取SD卡的CSD信息,包括容量和速度信息
- //输入:u8 *cid_data(存放CID的内存,至少16Byte)
- //返回值:0:NO_ERR
- // 1:错误
- u8 SD_GetCSD(u8 *csd_data)
- {
- u8 r1;
- r1=SD_SendCmd(CMD9,0,0x01);//发CMD9命令,读CSD
- if(r1==0)
- {
- r1=SD_RecvData(csd_data, 16);//接收16个字节的数据
- }
- SD_DisSelect();//取消片选
- if(r1)return 1;
- else return 0;
- }
- //获取SD卡的总扇区数(扇区数)
- //返回值:0: 取容量出错
- // 其他:SD卡的容量(扇区数/512字节)
- //每扇区的字节数必为512,因为如果不是512,则初始化不能通过.
- u32 SD_GetSectorCount(void)
- {
- u8 csd[16];
- uint64_t Capacity;
- u8 n;
- u16 csize;
- //取CSD信息,如果期间出错,返回0
- if(SD_GetCSD(csd)!=0) return 0;
- //如果为SDHC卡,按照下面方式计算
- if((csd[0]&0xC0)==0x40) //V2.00的卡
- {
- csize = csd[9] + ((u16)csd[8] << 8) + 1;
- Capacity = (u32)csize << 10;//得到扇区数
- }else//V1.XX的卡
- {
- n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
- csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
- Capacity= (u32)csize << (n - 9);//得到扇区数
- }
- return Capacity;
- }
- //初始化SD卡
- u8 SD_Initialize(void)
- {
- u8 r1; // 存放SD卡的返回值
- u16 retry; // 用来进行超时计数
- u8 buf[4];
- u16 i;
- SD_SPI_Init(); //初始化IO
- for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XFF);//发送最少74个脉冲
- retry=20;
- do
- {
- r1=SD_SendCmd(CMD0,0,0x95);//进入IDLE状态
- }while((r1!=0X01) && retry--);
- SD_Type=0;//默认无卡
- if(r1==0X01)
- {
- if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0
- {
- for(i=0;i<4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF); //Get trailing return value of R7 resp
- if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支持2.7~3.6V
- {
- retry=0XFFFE;
- do
- {
- SD_SendCmd(CMD55,0,0X01); //发送CMD55
- r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41
- }while(r1&&retry--);
- if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
- {
- for(i=0;i<4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF);//得到OCR值
- if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC; //检查CCS
- else SD_Type=SD_TYPE_V2;
- }
- }
- }else//SD V1.x/ MMC V3
- {
- SD_SendCmd(CMD55,0,0X01); //发送CMD55
- r1=SD_SendCmd(CMD41,0,0X01); //发送CMD41
- if(r1<=1)
- {
- SD_Type=SD_TYPE_V1;
- retry=0XFFFE;
- do //等待退出IDLE模式
- {
- SD_SendCmd(CMD55,0,0X01); //发送CMD55
- r1=SD_SendCmd(CMD41,0,0X01);//发送CMD41
- }while(r1&&retry--);
- }else//MMC卡不支持CMD55+CMD41识别
- {
- SD_Type=SD_TYPE_MMC;//MMC V3
- retry=0XFFFE;
- do //等待退出IDLE模式
- {
- r1=SD_SendCmd(CMD1,0,0X01);//发送CMD1
- }while(r1&&retry--);
- }
- if(retry==0||SD_SendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;//错误的卡
- }
- }
- SD_DisSelect();//取消片选
- if(SD_Type)return 0;
- else if(r1)return r1;
- return 0xaa;//其他错误
- }
- //读SD卡
- //buf:数据缓存区
- //sector:扇区
- //cnt:扇区数
- //返回值:0,ok;其他,失败.
- u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
- {
- u8 r1;
- if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//转换为字节地址
- if(cnt==1)
- {
- r1=SD_SendCmd(CMD17,sector,0X01);//读命令
- if(r1==0)//指令发送成功
- {
- r1=SD_RecvData(buf,512);//接收512个字节
- }
- }else
- {
- r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令
- do
- {
- r1=SD_RecvData(buf,512);//接收512个字节
- buf+=512;
- }while(--cnt && r1==0);
- SD_SendCmd(CMD12,0,0X01); //发送停止命令
- }
- SD_DisSelect();//取消片选
- return r1;//
- }
- //写SD卡
- //buf:数据缓存区
- //sector:起始扇区
- //cnt:扇区数
- //返回值:0,ok;其他,失败.
- u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
- {
- u8 r1;
- if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//转换为字节地址
- if(cnt==1)
- {
- r1=SD_SendCmd(CMD24,sector,0X01);//写命令
- if(r1==0)//指令发送成功
- {
- r1=SD_SendBlock(buf,0xFE);//写512个字节,0xFE表示开始写
- }
- }else
- {
- if(SD_Type!=SD_TYPE_MMC)
- {
- SD_SendCmd(CMD55,0,0X01);
- SD_SendCmd(CMD23,cnt,0X01);//发送指令
- }
- r1=SD_SendCmd(CMD25,sector,0X01);//连续写命令
- if(r1==0)
- {
- do
- {
- r1=SD_SendBlock(buf,0xFC);//0xFC表示多块数据写入开始
- buf+=512;
- }while(--cnt && r1==0);
- r1=SD_SendBlock(0,0xFD);//0xFD表示多块数据写入结束
- }
- }
- SD_DisSelect();//取消片选
- return r1;//
- }
sd.c文件主要进行SD初始化等操作,关于SD卡初始化函数SD_Initialize,其具体操作流程如下: (1)调用SD_SPI_Init函数初始化GPIO口及SPI工作模式,同时控制片选引脚使之输出高电平; (2)利用for循环并调用SD_SPI_ReadWriteByte函数进行10读写操作,,同时产生80个时钟信号,满足SD卡初始化至少需要74个时钟的要求; (3)调用SD_SendCmd函数发送CMD0命令,复位SD卡同时进入空闲状态; (4)调用SD_SendCmd函数发送相关命令对SD卡版本类型进行判断,同时返回响应信息。 关于sd.c文件中其他函数,程序中都有相应注释,可结合注释以及《 Physical Layer Simplified Specification V2.0》手册进行理解。
main.c文件 - int main(void)
- {
- u32 sd_size;
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
- Delay_Init();
- USART_Printf_Init(115200);
- printf("SystemClk:%d\r\n",SystemCoreClock);
- printf("This is SD Test example\r\n");
- if(SD_Detect()==0)
- {
- printf("未检测SD卡插入!\n");
- }
- else
- {
- printf("已检测SD卡插入!\n");
- if(SD_Initialize())
- {
- printf("SD卡初始化出错,请检查!!\n");
- Delay_Ms(500);
- }
- else
- {
- printf("SD卡初始化完成!\n");
- sd_size=SD_GetSectorCount();//得到扇区数
- printf("SD Card Size(MB):%d\n",sd_size>>11);
- }
- }
- while(1)
- {
- }
- }
main.c文件主要进行相关函数初始化以及SD卡插入检测、初始化检测以及读取SD卡容量大小。
4、下载验证 将编译好的程序下载到开发板并复位,当未插入SD卡时,串口打印情况具体如下: 插入SD卡并复位后,串口打印情况如下:
|