kl800.com省心范文网

STM32在SPI模式下读写SD卡


3.20

SD卡实验

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘,FLASH 芯片, SD 卡等。他们各有优点,综合比较,最适合单片机系统的莫过于 SD 卡了,它不仅容量可以做 到很大(32Gb 以上) ,而且支持 SPI 接口,方便移动,有几种体积的尺寸可供选择(标准的 SD 卡尺寸,以及 TF 卡尺寸) ,能满足不同应用的要求。只需要 4 个 IO 口,就可以外扩一个最大 达 32GB 以上的外部存储器,容量选择尺度很大,更换也很方便,而且方便移动,编程也比较 简单,是单片机大容量外部存储器的首选。 ALIENTKE MiniSTM3 开发板就带有 SD 卡接口,利用 STM32 自带的 SPI 接口,最大通信 速度可达 18Mbps,每秒可传输数据 2M 字节以上,对于一般应用足够了。本节将向大家介绍, 如何在 ALIENTEK MiniSTM32 开发板上读取 SD 卡。本节分为如下几个部分: 3.20.1 SD 卡简介 3.20.2 硬件设计 3.20.3 软件设计 3.20.4 下载与测试

295

3.20.1 SD 卡简介
SD 卡(Secure Digital Memory Card)中文翻译为安全数码卡,是一种基于半导体快 闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助 理(PDA)和多媒体播放器等。SD 卡由日本松下、东芝及美国 SanDisk 公司于 1999 年 8 月 共同开发研制。大小犹如一张邮票的 SD 记忆卡,重量只有 2 克,但却拥有高记忆容量、快 速数据传输率、极大的移动灵活性以及很好的安全性。 SD 卡一般支持 2 种操作模式: 1,SD 卡模式; 2,SPI 模式; 主机可以选择以上任意一种模式同 SD 卡通信,SD 卡模式允许 4 线的高速数据传输。 SPI 模式允许简单的通过 SPI 接口来和 SD 卡通信,这种模式同 SD 卡模式相比就是丧失了 速度。 SD 卡的引脚排序如下图所示:

图 3.20.1.1 SD 卡引脚排序图 SD 卡引脚功能描述如下表所示:

表 3.20.1.1 SD 卡引脚功能表 SD 卡只能使用 3.3V 的 IO 电平,所以,MCU 一定要能够支持 3.3V 的 IO 端口输出。 注意:在 SPI 模式下,CS/MOSI/MISO/CLK 都需要加 10~100K 左右的上拉电阻。 SD 卡要进入 SPI 模式很简单,就是在 SD 卡收到复位命令(CMD0)时,CS 为有效电 平(低电平)则 SPI 模式被启用。不过在发送 CMD0 之前,要发送>74 个时钟,这是因为 SD 卡内部有个供电电压上升时间,大概为 64 个 CLK,剩下的 10 个 CLK 用于 SD 卡同步, 之后才能开始 CMD0 的操作,在卡初始化的时候,CLK 时钟最大不能超过 400Khz! 。 ALENTEK MiniSTM32 开发板使用的是 SPI 模式来读写 SD 卡, 下面我们就重点介绍一 下 SD 卡在 SPI 模式下的相关操作。 首先介绍 SPI 模式下几个重要的操作命令,如下表所示:

296

命令 CMD0(0X00) CMD9(0X09) CMD10(0X0A) CMD16(0X10) CMD17(0X11) CMD24(0X18) CMD41(0X29) CMD55(0X37) CMD59(0X3B)

参数 NONE NONE NONE 块大小 地址 地址 NONE NONE 仅最后一位有效

回应 R1 R1 R1 R1 R1 R1 R1 R1 R1

描述 复位 SD 卡 读取卡特定数据寄存器 读取卡标志数据寄存器 设置块大小(字节数) 读取一个块的数据 写入一个块的数据 引用命令的前命令 开始卡的初始化 设置 CRC 开启(1)或关闭(0)

表 3.20.1.2 SPI 模式下 SD 卡部分操作指令 其中 R1 的回应格式如下表所示:

表 3.20.1.3 SD 卡 R1 回应格式 接着我们看看 SD 卡的初始化,SD 卡的典型初始化过程如下: 1、初始化与 SD 卡连接的硬件条件(MCU 的 SPI 配置,IO 口配置) ; 2、上电延时(>74 个 CLK) ; 3、复位卡(CMD0) ; 4、激活卡,内部初始化并获取卡类型(CMD1(用于 MMC 卡) 、CMD55、CMD41) ; 5.、查询 OCR,获取供电状况(CMD58) ; 6、是否使用 CRC(CMD59) ; 7、设置读写块数据长度(CMD16) ; 8、读取 CSD,获取存储卡的其他信息(CMD9) ; 9、发送 8CLK 后,禁止片选; 这样我们就完成了对 SD 卡的初始化, 这里面我们一般设置读写块数据长度为 512 个字 节,并禁止使用 CRC。在完成了初始化之后,就可以开始读写数据了。 SD 卡读取数据,这里通过 CMD17 来实现,具体过程如下: 1、发送 CMD17; 2、接收卡响应 R1; 3、接收数据起始令牌 0XFE; 4、接收数据; 5、接收 2 个字节的 CRC,如果没有开启 CRC,这两个字节在读取后可以丢掉。 6、8CLK 之后禁止片选; 以上就是一个典型的读取 SD 卡数据过程,SD 卡的写于读数据差不多,写数据通过 CMD24 来实现,具体过程如下: 1、发送 CMD24; 2、接收卡响应 R1; 3、发送写数据起始令牌 0XFE;
297

4、发送数据; 5、发送 2 字节的伪 CRC; 6、8CLK 之后禁止片选; 以上就是一个典型的写 SD 卡过程。关于 SD 卡的介绍,我们就介绍到这里,更详细的 介绍请参考 SD 卡的参考资料。

3.20.2 硬件设计
本节实验功能简介:开机的时候先初始化 SD 卡,如果 SD 卡初始化完成,则读取扇区 0 的数据,然后通过串口打印到电脑上。如果没初始化通过,则在 LCD 上提示初始化失败。 同样用 DS0 来指示程序正在运行。 所要用到的硬件资源如下: 1)STM32F103RBT6。 2)DS0(外部 LED0) 。 3)串口 1。 4)TFTLCD 液晶模块。 5)SD 卡。 前面四部分,在之前的实例已经介绍过了,这里我们介绍一下SD卡在开发板上 的连接 方式,SD卡与MCU的连接原理图如下:

图3.20.2.1 SD卡与STM32连接电路图

298

3.20.3 软件设计
打开上一节的工程,首先在 HARDWARE 文件夹下新建一个 SD 的文件夹。然后新建一 个 MMC_SD.C 和 MMC_SD.H 的文件保存在 SD 文件夹下,并将这个文件夹加入头文件包 含路径。 打开 MMC_SD.C 文件,输入如下代码: #include "sys.h" #include "mmc_sd.h" #include "spi.h" #include "usart.h" #include "delay.h" u8 SD_Type=0;//SD 卡的类型 //Mini STM32 开发板 //SD 卡 驱动 //正点原子@ALIENTEK //2010/5/13 //增加了一些延时,实测可以支持 TF 卡(1G/2G),金士顿 2G,4G 16G SD 卡 //2010/6/24 //加入了 u8 SD_GetResponse(u8 Response)函数 //修改了 u8 SD_WaitDataReady(void)函数 //增加了 USB 读卡器支持的 u8 MSD_ReadBuffer(u8* pBuffer, u32 ReadAddr, u32 NumByteToRead); //和 u8 MSD_WriteBuffer(u8* pBuffer, u32 WriteAddr, u32 NumByteToWrite);两个 函数 //等待 SD 卡回应 //Response:要得到的回应值 //返回值:0,成功得到了该回应值 // 其他,得到回应值失败 u8 SD_GetResponse(u8 Response) { u16 Count=0xFFF;//等待次数 while ((SPIx_ReadWriteByte(0XFF)!=Response)&&Count)Count--;//等待得到准 确的回应 if (Count==0)return MSD_RESPONSE_FAILURE;//得到回应失败 else return MSD_RESPONSE_NO_ERROR;//正确回应 } //等待 SD 卡写入完成 //返回值:0,成功; // 其他,错误代码; u8 SD_WaitDataReady(void) { u8 r1=MSD_DATA_OTHER_ERROR;
299

u32 retry; retry=0; do { r1=SPIx_ReadWriteByte(0xFF)&0X1F;//读到回应 if(retry==0xfffe)return 1; retry++; switch (r1) { case MSD_DATA_OK://数据接收正确了 r1=MSD_DATA_OK; break; case MSD_DATA_CRC_ERROR: //CRC 校验错误 return MSD_DATA_CRC_ERROR; case MSD_DATA_WRITE_ERROR://数据写入错误 return MSD_DATA_WRITE_ERROR; default://未知错误 r1=MSD_DATA_OTHER_ERROR; break; } }while(r1==MSD_DATA_OTHER_ERROR); //数据错误时一直等待 retry=0; while(SPIx_ReadWriteByte(0XFF)==0)//读到数据为 0,则数据还未写完成 { retry++; //delay_us(10);//SD 卡写等待需要较长的时间 if(retry>=0XFFFFFFFE)return 0XFF;//等待失败了 }; return 0;//成功了 } //向 SD 卡发送一个命令 //输入: u8 cmd 命令 // u32 arg 命令参数 // u8 crc crc 校验值 //返回值:SD 卡返回的响应 u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc) { u8 r1; u8 Retry=0; SD_CS=1; SPIx_ReadWriteByte(0xff);//高速写命令延时 SPIx_ReadWriteByte(0xff); SPIx_ReadWriteByte(0xff);
300

//片选端置低,选中 SD 卡 SD_CS=0; //发送 SPIx_ReadWriteByte(cmd | 0x40);//分别写入命令 SPIx_ReadWriteByte(arg >> 24); SPIx_ReadWriteByte(arg >> 16); SPIx_ReadWriteByte(arg >> 8); SPIx_ReadWriteByte(arg); SPIx_ReadWriteByte(crc); //等待响应,或超时退出 while((r1=SPIx_ReadWriteByte(0xFF))==0xFF) { Retry++; if(Retry>200)break; } //关闭片选 SD_CS=1; //在总线上额外增加 8 个时钟,让 SD 卡完成剩下的工作 SPIx_ReadWriteByte(0xFF); //返回状态值 return r1; } //向 SD 卡发送一个命令(结束是不失能片选,还有后续数据传来) //输入:u8 cmd 命令 // u32 arg 命令参数 // u8 crc crc 校验值 //返回值:SD 卡返回的响应 u8 SD_SendCommand_NoDeassert(u8 cmd, u32 arg, u8 crc) { u8 Retry=0; u8 r1; SPIx_ReadWriteByte(0xff);//高速写命令延时 SPIx_ReadWriteByte(0xff); SD_CS=0;//片选端置低,选中 SD 卡 //发送 SPIx_ReadWriteByte(cmd | 0x40); //分别写入命令 SPIx_ReadWriteByte(arg >> 24); SPIx_ReadWriteByte(arg >> 16); SPIx_ReadWriteByte(arg >> 8); SPIx_ReadWriteByte(arg); SPIx_ReadWriteByte(crc);
301

//等待响应,或超时退出 while((r1=SPIx_ReadWriteByte(0xFF))==0xFF) { Retry++; if(Retry>200)break; } //返回响应值 return r1; } //把 SD 卡设置到挂起模式 //返回值:0,成功设置 // 1,设置失败 u8 SD_Idle_Sta(void) { u16 i; u8 retry; for(i=0;i<0xf00;i++);//纯延时,等待 SD 卡上电完成 //先产生>74 个脉冲,让 SD 卡自己初始化完成 for(i=0;i<10;i++)SPIx_ReadWriteByte(0xFF); //-----------------SD 卡复位到 idle 开始----------------//循环连续发送 CMD0,直到 SD 卡返回 0x01,进入 IDLE 状态 //超时则直接退出 retry = 0; do { //发送 CMD0,让 SD 卡进入 IDLE 状态 i = SD_SendCommand(CMD0, 0, 0x95); retry++; }while((i!=0x01)&&(retry<200)); //跳出循环后,检查原因:初始化成功?or 重试超时? if(retry==200)return 1; //失败 return 0;//成功 } //初始化 SD 卡 //如果成功返回,则会自动设置 SPI 速度为 18Mhz //返回值:0:NO_ERR // 1:TIME_OUT // 99:NO_CARD u8 SD_Init(void) { u8 r1; // 存放 SD 卡的返回值 u16 retry; // 用来进行超时计数
302

u8 buff[6]; //设置硬件上与 SD 卡相关联的控制引脚输出 //避免 NRF24L01/W25X16 等的影响 RCC->APB2ENR|=1<<2; //PORTA 时钟使能 GPIOA->CRL&=0XFFF000FF; GPIOA->CRL|=0X00033300;//PA2.3.4 推挽 GPIOA->ODR|=0X7<<2; //PA2.3.4 上拉 SPIx_Init(); SPIx_SetSpeed(SPI_SPEED_256);//设置到低速模式 SD_CS=1; if(SD_Idle_Sta()) return 1;//超时返回 1 设置到 idle 模式失败 //-----------------SD 卡复位到 idle 结束----------------//获取卡片的 SD 版本信息 SD_CS=0; r1 = SD_SendCommand_NoDeassert(8, 0x1aa,0x87); //如果卡片版本信息是 v1.0 版本的,即 r1=0x05,则进行以下初始化 if(r1 == 0x05) { //设置卡类型为 SDV1.0,如果后面检测到为 MMC 卡,再修改为 MMC SD_Type = SD_TYPE_V1; //如果是 V1.0 卡,CMD8 指令后没有后续数据 //片选置高,结束本次命令 SD_CS=1; //多发 8 个 CLK,让 SD 结束后续操作 SPIx_ReadWriteByte(0xFF); //-----------------SD 卡、MMC 卡初始化开始----------------//发卡初始化指令 CMD55+ACMD41 // 如果有应答,说明是 SD 卡,且初始化完成 // 没有回应,说明是 MMC 卡,额外进行相应初始化 retry = 0; do { //先发 CMD55,应返回 0x01;否则出错 r1 = SD_SendCommand(CMD55, 0, 0); if(r1 == 0XFF)return r1;//只要不是 0xff,就接着发送 //得到正确响应后,发 ACMD41,应得到返回值 0x00,否则重试 200 次 r1 = SD_SendCommand(ACMD41, 0, 0); retry++; }while((r1!=0x00) && (retry<400)); // 判断是超时还是得到正确回应 // 若有回应:是 SD 卡;没有回应:是 MMC 卡 //----------MMC 卡额外初始化操作开始-----------if(retry==400)
303

{ retry = 0; //发送 MMC 卡初始化命令(没有测试) do { r1 = SD_SendCommand(1,0,0); retry++; }while((r1!=0x00)&& (retry<400)); if(retry==400)return 1; //MMC 卡初始化超时 //写入卡类型 SD_Type = SD_TYPE_MMC; } //----------MMC 卡额外初始化操作结束-----------//设置 SPI 为高速模式 SPIx_SetSpeed(SPI_SPEED_4); SPIx_ReadWriteByte(0xFF); //禁止 CRC 校验 r1 = SD_SendCommand(CMD59, 0, 0x95); if(r1 != 0x00)return r1; //命令错误,返回 r1 //设置 Sector Size r1 = SD_SendCommand(CMD16, 512, 0x95); if(r1 != 0x00)return r1;//命令错误,返回 r1 //-----------------SD 卡、MMC 卡初始化结束----------------}//SD 卡为 V1.0 版本的初始化结束 //下面是 V2.0 卡的初始化 //其中需要读取 OCR 数据,判断是 SD2.0 还是 SD2.0HC 卡 else if(r1 == 0x01) { //V2.0 的卡,CMD8 命令后会传回 4 字节的数据,要跳过再结束本命令 buff[0] = SPIx_ReadWriteByte(0xFF); //should be 0x00 buff[1] = SPIx_ReadWriteByte(0xFF); //should be 0x00 buff[2] = SPIx_ReadWriteByte(0xFF); //should be 0x01 buff[3] = SPIx_ReadWriteByte(0xFF); //should be 0xAA SD_CS=1; SPIx_ReadWriteByte(0xFF);//the next 8 clocks //判断该卡是否支持 2.7V-3.6V 的电压范围 //if(buff[2]==0x01 && buff[3]==0xAA) //不判断,让其支持的卡更多 { retry = 0; //发卡初始化指令 CMD55+ACMD41 do {
304

r1 = SD_SendCommand(CMD55, 0, 0); if(r1!=0x01)return r1; r1 = SD_SendCommand(ACMD41, 0x40000000, 0); if(retry>200)return r1; //超时则返回 r1 状态 }while(r1!=0); //初始化指令发送完成,接下来获取 OCR 信息 //-----------鉴别 SD2.0 卡版本开始----------r1 = SD_SendCommand_NoDeassert(CMD58, 0, 0); if(r1!=0x00) { SD_CS=1;//释放 SD 片选信号 return r1; //如果命令没有返回正确应答,直接退出,返回应答 }//读 OCR 指令发出后,紧接着是 4 字节的 OCR 信息 buff[0] = SPIx_ReadWriteByte(0xFF); buff[1] = SPIx_ReadWriteByte(0xFF); buff[2] = SPIx_ReadWriteByte(0xFF); buff[3] = SPIx_ReadWriteByte(0xFF); //OCR 接收完成,片选置高 SD_CS=1; SPIx_ReadWriteByte(0xFF); //检查接收到的 OCR 中的 bit30 位(CCS) ,确定其为 SD2.0 还是 SDHC //如果 CCS=1:SDHC CCS=0:SD2.0 if(buff[0]&0x40)SD_Type = SD_TYPE_V2HC; //检查 CCS else SD_Type = SD_TYPE_V2; //-----------鉴别 SD2.0 卡版本结束----------//设置 SPI 为高速模式 SPIx_SetSpeed(SPI_SPEED_4); } } return r1; } //从 SD 卡中读回指定长度的数据,放置在给定位置 //输入: u8 *data(存放读回数据的内存>len) // u16 len(数据长度) // u8 release(传输完成后是否释放总线 CS 置高 0:不释放 1:释放) //返回值:0:NO_ERR // other:错误信息 u8 SD_ReceiveData(u8 *data, u16 len, u8 release) { // 启动一次传输
305

SD_CS=0; if(SD_GetResponse(0xFE))//等待 SD 卡发回数据起始令牌 0xFE { SD_CS=1; return 1; } while(len--)//开始接收数据 { *data=SPIx_ReadWriteByte(0xFF); data++; } //下面是 2 个伪 CRC(dummy CRC) SPIx_ReadWriteByte(0xFF); SPIx_ReadWriteByte(0xFF); if(release==RELEASE)//按需释放总线,将 CS 置高 { SD_CS=1;//传输结束 SPIx_ReadWriteByte(0xFF); } return 0; } //获取 SD 卡的 CID 信息,包括制造商信息 //输入: u8 *cid_data(存放 CID 的内存,至少 16Byte) //返回值:0:NO_ERR // 1:TIME_OUT // other:错误信息 u8 SD_GetCID(u8 *cid_data) { u8 r1; //发 CMD10 命令,读 CID r1 = SD_SendCommand(CMD10,0,0xFF); if(r1 != 0x00)return r1; //没返回正确应答,则退出,报错 SD_ReceiveData(cid_data,16,RELEASE);//接收 16 个字节的数据 return 0; } //获取 SD 卡的 CSD 信息,包括容量和速度信息 //输入:u8 *cid_data(存放 CID 的内存,至少 16Byte) //返回值:0:NO_ERR // 1:TIME_OUT // other:错误信息
306

u8 SD_GetCSD(u8 *csd_data) { u8 r1; r1=SD_SendCommand(CMD9,0,0xFF);//发 CMD9 命令,读 CSD if(r1)return r1; //没返回正确应答,则退出,报错 SD_ReceiveData(csd_data, 16, RELEASE);//接收 16 个字节的数据 return 0; } //获取 SD 卡的容量(字节) //返回值:0: 取容量出错 // 其他:SD 卡的容量(字节) u32 SD_GetCapacity(void) { u8 csd[16]; u32 Capacity; u8 r1; u16 i; u16 temp; //取 CSD 信息,如果期间出错,返回 0 if(SD_GetCSD(csd)!=0) return 0; //如果为 SDHC 卡,按照下面方式计算 if((csd[0]&0xC0)==0x40) { Capacity=((u32)csd[8])<<8; Capacity+=(u32)csd[9]+1; Capacity = (Capacity)*1024;//得到扇区数 Capacity*=512;//得到字节数 } else { i = csd[6]&0x03; i<<=8; i += csd[7]; i<<=2; i += ((csd[8]&0xc0)>>6); //C_SIZE_MULT r1 = csd[9]&0x03; r1<<=1; r1 += ((csd[10]&0x80)>>7); r1+=2;//BLOCKNR temp = 1; while(r1)
307

{ temp*=2; r1--; } Capacity = ((u32)(i+1))*((u32)temp); // READ_BL_LEN i = csd[5]&0x0f; //BLOCK_LEN temp = 1; while(i) { temp*=2; i--; } //The final result Capacity *= (u32)temp;//字节为单位 } return (u32)Capacity; } //读 SD 卡的一个 block //输入:u32 sector 取地址(sector 值,非物理地址) // u8 *buffer 数据存储地址(大小至少 512byte) //返回值:0: 成功 // other:失败 u8 SD_ReadSingleBlock(u32 sector, u8 *buffer) { u8 r1; //设置为高速模式 SPIx_SetSpeed(SPI_SPEED_4); //如果不是 SDHC,给定的是 sector 地址,将其转换成 byte 地址 if(SD_Type!=SD_TYPE_V2HC) { sector = sector<<9; } r1 = SD_SendCommand(CMD17, sector, 0);//读命令 if(r1 != 0x00)return r1; r1 = SD_ReceiveData(buffer, 512, RELEASE); if(r1 != 0)return r1; //读数据出错! else return 0; } /////////////////下面 2 个函数为 USB 读写所需要的/////////////////////////
308

//定义 SD 卡的块大小 #define BLOCK_SIZE 512 //写入 MSD/SD 数据 //pBuffer:数据存放区 //ReadAddr:写入的首地址 //NumByteToRead:要写入的字节数 //返回值:0,写入完成 // 其他,写入失败 u8 MSD_WriteBuffer(u8* pBuffer, u32 WriteAddr, u32 NumByteToWrite) { u32 i,NbrOfBlock = 0, Offset = 0; u32 sector; u8 r1; NbrOfBlock = NumByteToWrite / BLOCK_SIZE;//得到要写入的块的数目 SD_CS=0; while (NbrOfBlock--)//写入一个扇区 { sector=WriteAddr+Offset; if(SD_Type==SD_TYPE_V2HC)sector>>=9;//执行与普通操作相反的操作 r1=SD_SendCommand_NoDeassert(CMD24,sector,0xff);//写命令 if(r1) { SD_CS=1; return 1;//应答不正确,直接返回 } SPIx_ReadWriteByte(0xFE);//放起始令牌 0xFE //放一个 sector 的数据 for(i=0;i<512;i++)SPIx_ReadWriteByte(*pBuffer++); //发 2 个 Byte 的 dummy CRC SPIx_ReadWriteByte(0xff); SPIx_ReadWriteByte(0xff); if(SD_WaitDataReady())//等待 SD 卡数据写入完成 { SD_CS=1; return 2; } Offset += 512; } //写入完成,片选置 1 SD_CS=1; SPIx_ReadWriteByte(0xff); return 0;
309

} //读取 MSD/SD 数据 //pBuffer:数据存放区 //ReadAddr:读取的首地址 //NumByteToRead:要读出的字节数 //返回值:0,读出完成 // 其他,读出失败 u8 MSD_ReadBuffer(u8* pBuffer, u32 ReadAddr, u32 NumByteToRead) { u32 NbrOfBlock=0,Offset=0; u32 sector=0; u8 r1=0; NbrOfBlock=NumByteToRead/BLOCK_SIZE; SD_CS=0; while (NbrOfBlock --) { sector=ReadAddr+Offset; if(SD_Type==SD_TYPE_V2HC)sector>>=9;//执行与普通操作相反的操作 r1=SD_SendCommand_NoDeassert(CMD17,sector,0xff);//读命令 if(r1)//命令发送错误 { SD_CS=1; return r1; } r1=SD_ReceiveData(pBuffer,512,RELEASE); if(r1)//读数错误 { SD_CS=1; return r1; } pBuffer+=512; Offset+=512; } SD_CS=1; SPIx_ReadWriteByte(0xff); return 0; } ////////////////////////////////////////////////////////////////////////// //写入 SD 卡的一个 block(未实际测试过) //输入:u32 sector 扇区地址(sector 值,非物理地址)
310

// u8 *buffer 数据存储地址(大小至少 512byte) //返回值:0: 成功 // other:失败 u8 SD_WriteSingleBlock(u32 sector, const u8 *data) { u8 r1; u16 i; u16 retry; //设置为高速模式 //SPIx_SetSpeed(SPI_SPEED_HIGH); //如果不是 SDHC,给定的是 sector 地址,将其转换成 byte 地址 if(SD_Type!=SD_TYPE_V2HC) { sector = sector<<9; } r1 = SD_SendCommand(CMD24, sector, 0x00); if(r1 != 0x00) { return r1; //应答不正确,直接返回 } //开始准备数据传输 SD_CS=0; //先放 3 个空数据,等待 SD 卡准备好 SPIx_ReadWriteByte(0xff); SPIx_ReadWriteByte(0xff); SPIx_ReadWriteByte(0xff); //放起始令牌 0xFE SPIx_ReadWriteByte(0xFE); //放一个 sector 的数据 for(i=0;i<512;i++) { SPIx_ReadWriteByte(*data++); } //发 2 个 Byte 的 dummy CRC SPIx_ReadWriteByte(0xff); SPIx_ReadWriteByte(0xff); //等待 SD 卡应答 r1 = SPIx_ReadWriteByte(0xff);
311

if((r1&0x1F)!=0x05) { SD_CS=1; return r1; } //等待操作完成 retry = 0; while(!SPIx_ReadWriteByte(0xff)) { retry++; if(retry>0xfffe) //如果长时间写入没有完成,报错退出 { SD_CS=1; return 1; //写入超时返回 1 } } //写入完成,片选置 1 SD_CS=1; SPIx_ReadWriteByte(0xff); return 0; } //读 SD 卡的多个 block(实际测试过) //输入:u32 sector 扇区地址(sector 值,非物理地址) // u8 *buffer 数据存储地址(大小至少 512byte) // u8 count 连续读 count 个 block //返回值:0: 成功 // other:失败 u8 SD_ReadMultiBlock(u32 sector, u8 *buffer, u8 count) { u8 r1; //SPIx_SetSpeed(SPI_SPEED_HIGH);//设置为高速模式 //如果不是 SDHC,将 sector 地址转成 byte 地址 if(SD_Type!=SD_TYPE_V2HC)sector = sector<<9; //SD_WaitDataReady(); //发读多块命令 r1 = SD_SendCommand(CMD18, sector, 0);//读命令 if(r1 != 0x00)return r1; do//开始接收数据 { if(SD_ReceiveData(buffer, 512, NO_RELEASE) != 0x00)break;
312

buffer += 512; } while(--count); //全部传输完毕,发送停止命令 SD_SendCommand(CMD12, 0, 0); //释放总线 SD_CS=1; SPIx_ReadWriteByte(0xFF); if(count != 0)return count; //如果没有传完,返回剩余个数 else return 0; } //写入 SD 卡的 N 个 block(未实际测试过) //输入:u32 sector 扇区地址(sector 值,非物理地址) // u8 *buffer 数据存储地址(大小至少 512byte) // u8 count 写入的 block 数目 //返回值:0: 成功 // other:失败 u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count) { u8 r1; u16 i; //SPIx_SetSpeed(SPI_SPEED_HIGH);//设置为高速模式 if(SD_Type != SD_TYPE_V2HC)sector = sector<<9;//如果不是 SDHC,给定的是 sector 地址,将其转换成 byte 地址 if(SD_Type != SD_TYPE_MMC) r1 = SD_SendCommand(ACMD23, count, 0x00);// 如果目标卡不是 MMC 卡,启用 ACMD23 指令使能预擦除 r1 = SD_SendCommand(CMD25, sector, 0x00);//发多块写入指令 if(r1 != 0x00)return r1; //应答不正确,直接返回 SD_CS=0;//开始准备数据传输 SPIx_ReadWriteByte(0xff);//先放 3 个空数据,等待 SD 卡准备好 SPIx_ReadWriteByte(0xff); //--------下面是 N 个 sector 写入的循环部分 do { //放起始令牌 0xFC 表明是多块写入 SPIx_ReadWriteByte(0xFC); //放一个 sector 的数据 for(i=0;i<512;i++) { SPIx_ReadWriteByte(*data++); } //发 2 个 Byte 的 dummy CRC SPIx_ReadWriteByte(0xff);
313

SPIx_ReadWriteByte(0xff); //等待 SD 卡应答 r1 = SPIx_ReadWriteByte(0xff); if((r1&0x1F)!=0x05) { SD_CS=1; //如果应答为报错,则带错误代码直接退出 return r1; } //等待 SD 卡写入完成 if(SD_WaitDataReady()==1) { SD_CS=1; //等待 SD 卡写入完成超时,直接退出报错 return 1; } }while(--count);//本 sector 数据传输完成 //发结束传输令牌 0xFD r1 = SPIx_ReadWriteByte(0xFD); if(r1==0x00) { count = 0xfe; } if(SD_WaitDataReady()) //等待准备好 { SD_CS=1; return 1; } //写入完成,片选置 1 SD_CS=1; SPIx_ReadWriteByte(0xff); return count; //返回 count 值,如果写完则 count=0,否则 count=1 } //在指定扇区,从 offset 开始读出 bytes 个字节 //输入:u32 sector 扇区地址(sector 值,非物理地址) // u8 *buf 数据存储地址(大小<=512byte) // u16 offset 在扇区里面的偏移量 // u16 bytes 要读出的字节数 //返回值:0: 成功 // other:失败 u8 SD_Read_Bytes(unsigned long address,unsigned char *buf,unsigned int offset,unsigned int bytes) {
314

u8 r1;u16 i=0; r1=SD_SendCommand(CMD17,address<<9,0);//发送读扇区命令 if(r1)return r1; //应答不正确,直接返回 SD_CS=0;//选中 SD 卡 if(SD_GetResponse(0xFE))//等待 SD 卡发回数据起始令牌 0xFE { SD_CS=1; //关闭 SD 卡 return 1;//读取失败 } for(i=0;i<offset;i++)SPIx_ReadWriteByte(0xff);//跳过 offset 位 for(;i<offset+bytes;i++)*buf++=SPIx_ReadWriteByte(0xff);//读取有用数据 for(;i<512;i++) SPIx_ReadWriteByte(0xff); SPIx_ReadWriteByte(0xff);//发送伪 CRC 码 SPIx_ReadWriteByte(0xff); SD_CS=1;//关闭 SD 卡 return 0; //读出剩余字节

} 此部分代码我们在本节主要用到的就是 SD 卡初始化函数 SD_Init 和 SD 卡读取函数 SD_ReadSingleBlock,通过前面的介绍,相信大家不难理解,这里我们就不多说了,接下来 保存 MMC_SD.C 文件,并加入到 HARDWARE 组下,然后打开 MMC_SD.H,在该文件里 面输入如下代码: #ifndef _MMC_SD_H_ #define _MMC_SD_H_ #include <stm32f10x_lib.h> //Mini STM32开发板 //SD卡 驱动 //正点原子@ALIENTEK //2010/6/13 //SD传输数据结束后是否释放总线宏定义 #define NO_RELEASE 0 #define RELEASE 1 // SD卡类型定义 #define SD_TYPE_MMC 0 #define SD_TYPE_V1 1 #define SD_TYPE_V2 2 #define SD_TYPE_V2HC 4 // SD卡指令表 #define CMD0 0 //卡复位 #define CMD1 1 #define CMD9 9 //命令9 ,读CSD数据 #define CMD10 10 //命令10,读CID数据 #define CMD12 12 //命令12,停止数据传输
315

#define CMD16 16 //命令16,设置SectorSize 应返回0x00 #define CMD17 17 //命令17,读sector #define CMD18 18 //命令18,读Multi sector #define ACMD23 23 //命令23,设置多sector写入前预先擦除N个block #define CMD24 24 //命令24,写sector #define CMD25 25 //命令25,写Multi sector #define ACMD41 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 //这部分应根据具体的连线来修改! //Mini STM32使用的是PA3作为SD卡的CS脚. #define SD_CS PAout(3) //SD卡片选引脚 extern u8 SD_Type;//SD卡的类型 //函数申明区 u8 SD_WaitReady(void); //等待SD卡就绪 u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc); //SD卡发送一个命令 u8 SD_SendCommand_NoDeassert(u8 cmd, u32 arg, u8 crc); //SD卡初始化 u8 SD_Init(void); u8 SD_Idle_Sta(void); //设置SD卡到挂起模式 u8 SD_ReceiveData(u8 *data, u16 len, u8 release);//SD卡读数据 u8 SD_GetCID(u8 *cid_data); //读SD卡CID u8 SD_GetCSD(u8 *csd_data); //读SD卡CSD u32 SD_GetCapacity(void); //取SD卡容量 //USB 读卡器 SD卡操作函数 u8 MSD_WriteBuffer(u8* pBuffer, u32 WriteAddr, u32 NumByteToWrite); u8 MSD_ReadBuffer(u8* pBuffer, u32 ReadAddr, u32 NumByteToRead);
316

u8 SD_ReadSingleBlock(u32 sector, u8 *buffer); //读一个sector u8 SD_WriteSingleBlock(u32 sector, const u8 *buffer); //写一个sector u8 SD_ReadMultiBlock(u32 sector, u8 *buffer, u8 count); //读多个sector u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count);//写多个sector u8 SD_Read_Bytes(unsigned long address,unsigned char *buf,unsigned int offset,unsigned int bytes);//读取一byte #endif 该部分代码主要是一些命令的宏定义以及函数声明,在这里我们设定了 SD 卡的 CS 管 脚为 PA3。 保存 MMC_SD.H, 就可以在主函数里面编写我们的应用代码了, 打开 test.c 文件, 在该文件中修改 main 函数如下: u8 buf[512];//SD 卡数据缓存区 int main(void) { u32 sd_size; u8 t=0; Stm32_Clock_Init(9);//系统时钟设置 delay_init(72); //延时初始化 uart_init(72,9600); //串口 1 初始化 LCD_Init(); //初始化液晶 LED_Init(); //LED 初始化 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(60,50,"Mini STM32"); LCD_ShowString(60,70,"SD Card TEST"); LCD_ShowString(60,90,"ATOM@ALIENTEK"); LCD_ShowString(60,110,"2010/6/17"); while(SD_Init()!=0)//检测不到 SD 卡 { LCD_ShowString(60,130,"SD Card Failed!"); delay_ms(500); LCD_ShowString(60,130,"Please Check! "); delay_ms(500); LED0=!LED0;//DS0 闪烁 } //检测 SD 卡成功 LCD_ShowString(60,130,"SD Card Checked OK "); LCD_ShowString(60,150,"SD Card Size: Mb"); sd_size=SD_GetCapacity(); LCD_ShowNum(164,150,sd_size>>20,4,16);//显示 SD 卡容量 while(1) { if(t==30)//每 6s 钟执行一次 {
317

if(SD_ReadSingleBlock(0,buf)==0)//读取 MBR 扇区 { LCD_ShowString(60,170,"USART1 Sending Data..."); printf("SECTOR 0 DATA:\n"); for(sd_size=0;sd_size<512;sd_size++)//打印 MBR 扇区数据 { printf("%x ",buf[sd_size]); } printf("\nDATA ENDED\n"); LCD_ShowString(60,170,"USART1 Send Data Over!"); } t=0; } t++; delay_ms(200); LED0=!LED0; } } 这里我们通过 SD_GetCapacity 函数来得到 SD 卡的容量, 然后在液晶上显示出来, 接着 我们读取 SD 卡的扇区 0,然后把这部分数据通过串口打印出来。每 6s 钟执行一次。至此, 我们的软件设计就结束了。

3.20.4 下载与测试
在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,可以看 到 LCD 显示如下内容(默认 SD 卡已经接上了) :

图 3.19.4.1 SD 卡实验 LCD 显示内容 此时我们打开串口调试助手,就可以看到从开发板发回来的数据了,如下图示:

318

图 3.19.4.2 串口收到的 SD 卡扇区 0 内容

319


赞助商链接

STM32之FATFS文件系统(SPI方式)笔记

SPI 模式下 STM32 读写 SD 卡的工程结构 在确定 STM32 使用 SPI 模式读写 SD 卡没有问题后,进入 FATSF 文件系统的实验,另源代码 在文档最后。 三、FATSF...

STM32F103调试读SD卡经验总结

WL 板子 EK-STM32F103 调试读 SD 卡经验总结 一开始碰到的问题:发送 CMD0 ...STM32在SPI模式下读写SD... 25页 免费 冰凌科技STM32F103ZET6开... 115...

STM32笔记(六)SD卡的读写和FatFS文件系统

笔记( STM32 笔记(六)SD 卡读写和 FatFS 文件系统 因为要用,学习了一下 SPI 操作 SD 卡,同时移植了一个免费开源的 FAT 文件系统: FatFS。感觉挺好,在...

STM32的SPI通信总结(含DMA)

SPI 通信初始化(以 STM32 为从机,LPC1114 为主机介绍) 3. SPI读写函数...” 5. SPI_BaudRatePrescaler 波特率的设置 这在主机模式中,这一位的设置直接...

STM32学习之SPI

本节,我们将利用STM32SPI读取外部SPI FLASH芯片(W25X16),这节,我们使 用STM32SPI1的主模式, STM32的主模式配置步骤如下: 1)配置相关引脚的复用功能,...

STM32外设SDIO应用之SD卡

STM32外设SDIO应用之SD卡_计算机软件及应用_IT/...卡允许在两种模式下工作, 即 SD 模式和 SPI 模式...使 CMD15 ac - 用该命令不会影响内存的读写 ...

stm32-SD卡FatFS文件系统

stm32-SD卡FatFS文件系统_计算机软件及应用_IT/计算机_专业资料。以STM32为例,介绍SPI方式操作SD卡,FatFS文件系统的开发,实现文件的读写,可以在PC机上查看结果。...

stm32spi配置

STM32 SPI模式下配置(神州三号开发板 spi.c ...SPI1 对 W25X16 进行读写操作,对 SPI1 进行初始...访问 SD Card/W25X16/24L01/JF24C ***...

STM32 SPI接口的简单实现

STM32 SPI 接口的简单实现通常 SPI 通过 4 个引脚与外部器件相连: ● MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式 下接收数据。 ● ...

STM32使用SPI外设时如何设定NSS为通用IO口

答:主模式和从模式下均可以由软件或硬件进行 NSS 管理; 将 SPI_CR1 寄存器的 SSM 位置 为 1 时,NSS 引脚将被释放出来用作 GPIO 口; 使用 STM32 软件库时...