基于STM32的录音机设计(STM32F103+VS1053B)

228 阅读13分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情

这是基于STM32F103C8T6设计的录音机功能,支持录音保存,录音回放,录音界面显示等功能,录音芯片采用VS1053。

一、环境介绍

MCU: STM32F103C8T6

开发软件: Keil5

音频模块: VS1053B

录音文件存储设备: SD卡,采用SPI协议驱动

显示屏: SPI接口的0.96寸OLED

代码风格: 采用寄存器编程,代码简洁、执行效率高、注释到位、移植方便。

二、功能介绍

这是基于STM32F103C8T6设计的录音机功能,支持的功能如下:

(1). 按下按键1启动自动录音,默认为5秒录音一次,录音完毕自动保存在SD指定目录下。文件名称采用当前时间命名;音频文件格式采用WAV格式存储。

(2). 按下按键2启动手动录音,按键按下之后开始录音,再次按下结束录音,录音完毕之后,文件也是一样的保存在SD卡里。

(3). SD卡文件系统采用FAT32格式,STM32移植了FATFS开源文件系统对SD卡进行读写操作。

(4). OLED显示屏用于显示当前录音机的状态: 空闲、录音、回放等状态。

(5). 按下按键3,启动自动回放功能。自动扫描目录,按顺序播放录音文件。

img

img

技术介绍:

(1). SD卡采用SPI协议驱动,因为对速度没有很高要求,SPI协议已经完全满足;如果要更高的速度,可以采用SDIO协议。

(2). 音频模块采用VS1053B,这个芯片支持IIS和SPI协议。我这里采用的是SPI协议驱动,SPI比较简单,代码也好移植,可以很方便的移植到其他单片机上运行。VS1053功能比较强大,支持录音、解码播放。

(3). 文件系统采用的是FATFS文件系统,这个文件系统功能比较完善,使用免费,支持FAT16、FAT32等格式。底层也比较好适配移植。当前,除了FATFS以外,还有很多其他的嵌入式文件系统可以选择,移植都大同小异。

(4). OLED显示屏是0.96寸的。采用SPI协议驱动,主要是显示一些状态,SPI刷屏比较快,这款OLED也支持IIC接口。

(5). VS1053模块上没有喇叭设备,可以适应耳机或者音箱听回放的录音。

硬件与STM32的接线说明:

OLED显示屏:

D0----SCK-----PB14 
D1----MOSI----PB13 
RES—复位(低电平有效)—PB12 
DC---数据和命令控制管脚—PB1 
CS---片选引脚-----PA7

VS1053:

#define VS1053_DREQ PAin(11) //DREQ 数据请求 
#define VS1053_RESET PAout(12) //RST 硬件复位--低电平有效 
#define VS1053_XCS PAout(13) //XCS 片选--低电平有效 
#define VS1053_XDCS PAout(14) //XDCS 用于数据片选、字节同步 
#define VS1053_SCLK PAout(15) 
#define VS1053_OUTPUT PBout(3) 
#define VS1053_INPUT PBin(4)

SD卡接口:

5V----5V 
GND---GND 
SPI1_MOSI---PA7 
SPI1_MISO---PA6 
SPI1_CS---PA4 
SPI1_SCK--PA5

三、使用的相关硬件

STM32F103C8T6系统板:

img

OLED显示屏:

img

VS1053:

img

SD卡卡槽:

img

四、操作说明

开发板有一个复位键和一个K0按键。

img

程序下载:

img

程序支持三种模式:

因为开发板只有一个K0按键,所以三种模式都是通过一个按键进行切换的。

一个按键是通过按下的计数方式进行切换的,切换的顺序是自动录音模式、手动录音模式、回放模式。

(1)自动录音模式:按下一次按键后,进入自动录音模式,自动录音模式下,录音5秒自动退出,退出后自动启动播放状态,就是播放刚才5秒录制的音频,播放过程中按下按键可以退出播放状态。

(2)手动录音模式:第二次按下K0按键后,进入手动录音模式,手动录音模式下,可以长时间录音,如果要结束录音,按下K0按键即可结束;结束后自动启动播放状态,就是播放刚才录制的音频,播放过程中按下按键可以退出播放状态。

(3)回放模式:第三次按下K0按键后,进入回放模式,自动扫描wav目录,进行顺序播放音频文件。

播放过程中可以按下K0按键退出回放模式。 每次录音后的文件是存放在SD卡根目录下的wav目录下。 每个状态都会在OLED显示屏上显示 也会同时通过串口打印到串口调试助手终端。

五、SD卡上存放的文件

SD卡上有两个目录:font目录和wav目录。 font目录下存放16x16字库文件。 wav目录下存放录音的音频文件。

img

img

img

六、部分源码

img

6.1 VS1053.c 这是VS1053的驱动代码

 #include "vs1053b.h"    
 ​
 /*
 函数功能:移植接口--SPI时序读写一个字节
 函数参数:data:要写入的数据
 返 回 值:读到的数据
 */
 u8 VS1053_SPI_ReadWriteByte(u8 tx_data)
 {                
     u8 rx_data=0;                
   u8 i;
   for(i=0;i<8;i++)
     {
         VS1053_SCLK=0;  
         if(tx_data&0x80){VS1053_OUTPUT=1;}
         else {VS1053_OUTPUT=0;}
         tx_data<<=1;    
         VS1053_SCLK=1;
         rx_data<<=1;
         if(VS1053_INPUT)rx_data|=0x01;
     }
     return rx_data; 
 }
 ​
 ​
 /*
 函数功能:初始化VS1053的IO口   
 */
 void VS1053_Init(void)
 {
      RCC->APB2ENR|=1<<0;
      AFIO->MAPR&=~(0x7<<24);  //释放PA13/14/15
      AFIO->MAPR|=0x4<<24;
     
      RCC->APB2ENR|=1<<2;
      RCC->APB2ENR|=1<<3;
      
      GPIOA->CRH&=0x00000FFF;
      GPIOA->CRH|=0x33338000;
       
      GPIOB->CRL&=0xFFF00FFF;
      GPIOB->CRL|=0x00083000;
     
      VS1053_SCLK=1;
      VS1053_XCS=1;
      VS1053_RESET=1;
 ​
 }   
 ​
 ​
 /*
 函数功能:软复位VS10XX
 */
 void VS1053_SoftReset(void)
 {    
     u8 retry=0;                    
     while(VS1053_DREQ==0);                          //等待软件复位结束     
     VS1053_SPI_ReadWriteByte(0Xff);         //启动传输
     retry=0;
     while(VS1053_ReadReg(SPI_MODE)!=0x0800) // 软件复位,新模式  
     {
         VS1053_WriteCmd(SPI_MODE,0x0804);       // 软件复位,新模式     
         DelayMs(2);//等待至少1.35ms 
         if(retry++>100)break;     
     }   
     while(VS1053_DREQ==0);//等待软件复位结束     
     retry=0;
     while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD 
     {
         VS1053_WriteCmd(SPI_CLOCKF,0X9800); //设置VS10XX的时钟,3倍频 ,1.5xADD
         if(retry++>100)break;       
     }    
     DelayMs(20);
 } 
 ​
 ​
 /*
 函数 功 能:硬复位MP3
 函数返回值:1:复位失败;0:复位成功 
 */
 u8 VS1053_Reset(void)
 {
     u8 retry=0;
     VS1053_RESET=0;
     DelayMs(20);
     VS1053_XDCS=1;//取消数据传输
     VS1053_XCS=1; //取消数据传输
     VS1053_RESET=1;    
     while(VS1053_DREQ==0&&retry<200)//等待DREQ为高
     {
         retry++;
         DelayUs(50);
     }
     DelayMs(20);    
     if(retry>=200)return 1;
     else return 0;               
 }
 ​
 ​
 /*
 函数功能:向VS10XX写命令
 函数参数:
                 address:命令地址
                 data   :命令数据
 */
 void VS1053_WriteCmd(u8 address,u16 data)
 {  
     while(VS1053_DREQ==0);  //等待空闲             
     VS1053_XDCS=1;   
     VS1053_XCS=0;    
     VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
     VS1053_SPI_ReadWriteByte(address);  //地址
     VS1053_SPI_ReadWriteByte(data>>8);  //发送高八位
     VS1053_SPI_ReadWriteByte(data);         //第八位
     VS1053_XCS=1;            
 } 
 ​
 ​
 /*
 函数参数:向VS1053写数据
 函数参数:data:要写入的数据
 */
 void VS1053_WriteData(u8 data)
 {
     VS1053_XDCS=0;   
     VS1053_SPI_ReadWriteByte(data);
     VS1053_XDCS=1;      
 }
 ​
 ​
 /*
 函数功能:读VS1053的寄存器 
 函数参数:address:寄存器地址
 返回值:读到的值
 */
 u16 VS1053_ReadReg(u8 address)
 { 
     u16 temp=0;     
   while(VS1053_DREQ==0);//非等待空闲状态       
     VS1053_XDCS=1;       
     VS1053_XCS=0;        
     VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令
     VS1053_SPI_ReadWriteByte(address);          //地址
     temp=VS1053_SPI_ReadWriteByte(0xff);          //读取高字节
     temp=temp<<8;
     temp+=VS1053_SPI_ReadWriteByte(0xff);       //读取低字节
     VS1053_XCS=1;      
    return temp; 
 }  
 ​
 ​
 /*
 函数功能:读取VS1053的RAM
 函数参数:addr:RAM地址
 返 回 值:读到的值
 */
 u16 VS1053_ReadRAM(u16 addr) 
 { 
     u16 res;                  
     VS1053_WriteCmd(SPI_WRAMADDR, addr); 
     res=VS1053_ReadReg(SPI_WRAM);  
     return res;
 } 
 ​
 ​
 /*
 函数功能:写VS1053的RAM
 函数参数:
                 addr:RAM地址
                 val:要写入的值 
 */
 void VS1053_WriteRAM(u16 addr,u16 val) 
 {             
     VS1053_WriteCmd(SPI_WRAMADDR,addr); //写RAM地址 
     while(VS1053_DREQ==0);                          //等待空闲     
     VS1053_WriteCmd(SPI_WRAM,val);          //写RAM值 
 } 
 ​
 ​
 /*
 函数参数:发送一次音频数据,固定为32字节
 返 回 值:0,发送成功
                   1,本次数据未成功发送   
 */ 
 u8 VS1053_SendMusicData(u8* buf)
 {
     u8 n;
     if(VS1053_DREQ!=0)  //送数据给VS10XX
     {                
         VS1053_XDCS=0;  
     for(n=0;n<32;n++)
         {
             VS1053_SPI_ReadWriteByte(buf[n]);               
         }
         VS1053_XDCS=1;                     
     }else return 1;
     return 0;//成功发送了
 }
 ​
 ​
 /*
 函数参数:发送一次音频数据,固定为32字节
 返 回 值:0,发送成功
                   1,本次数据未成功发送   
 */ 
 void VS1053_SendMusicByte(u8 data)
 {
     u8 n;
     while(VS1053_DREQ==0){}      
     VS1053_XDCS=0;  
     VS1053_SPI_ReadWriteByte(data);             
     VS1053_XDCS=1;                     
 }
 ​
 ​
 /*
 函数功能:设定VS1053播放的音量
 函数参数:volx:音量大小(0~254)
 */
 void VS1053_SetVol(u8 volx)
 {
     u16 volt=0;                   //暂存音量值
     volt=254-volx;              //取反一下,得到最大值,表示最大的表示 
       volt<<=8;
     volt+=254-volx;                 //得到音量设置后大小
     VS1053_WriteCmd(SPI_VOL,volt);//设音量 
 }
 ​
 ​
 /*--------------------------------------录音功能-----------------------------------------------------*/
 ​
 ​
 /*
 函数功能:vs10xx装载patch
 函数参数:
                 patch:patch首地址
                 len  :patch长度
 */
 void VS1053_LoadPatch(u16 *patch,u16 len) 
 {
     u16 i; 
     u16 addr, n, val;                  
     for(i=0;i<len;) 
     { 
         addr = patch[i++]; 
         n    = patch[i++]; 
         if(n & 0x8000U) //RLE run, replicate n samples 
         { 
             n  &= 0x7FFF; 
             val = patch[i++]; 
             while(n--)VS1053_WriteCmd(addr, val);  
         }
         else //copy run, copy n sample 
         { 
             while(n--) 
             { 
                 val = patch[i++]; 
                 VS1053_WriteCmd(addr, val); 
             } 
         } 
     }   
 }
 ​
 ​
 /*
 函数参数:初始化WAV头
 */
 void VS1053_RecoderWavInit(__WaveHeader* wavhead) //初始化WAV头            
 {
     wavhead->riff.ChunkID=0X46464952;   //"RIFF"
     wavhead->riff.ChunkSize=0;              //还未确定,最后需要计算
     wavhead->riff.Format=0X45564157;    //"WAVE"
     wavhead->fmt.ChunkID=0X20746D66;    //"fmt "
     wavhead->fmt.ChunkSize=16;              //大小为16个字节
     wavhead->fmt.AudioFormat=0X01;      //0X01,表示PCM;0X01,表示IMA ADPCM
     wavhead->fmt.NumOfChannels=1;           //单声道
     wavhead->fmt.SampleRate=8000;           //8Khz采样率 采样速率
     wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*2;//16位,即2个字节
     wavhead->fmt.BlockAlign=2;              //块大小,2个字节为一个块
     wavhead->fmt.BitsPerSample=16;      //16位PCM
   wavhead->data.ChunkID=0X61746164; //"data"
     wavhead->data.ChunkSize=0;              //数据大小,还需要计算  
 }
 ​
 //VS1053的WAV录音有bug,这个plugin可以修正这个问题                                 
 const u16 VS1053_WavPlugin[40]=/* Compressed plugin */ 
 { 
         0x0007, 0x0001, 0x8010, 0x0006, 0x001c, 0x3e12, 0xb817, 0x3e14, /* 0 */ 
         0xf812, 0x3e01, 0xb811, 0x0007, 0x9717, 0x0020, 0xffd2, 0x0030, /* 8 */ 
         0x11d1, 0x3111, 0x8024, 0x3704, 0xc024, 0x3b81, 0x8024, 0x3101, /* 10 */ 
         0x8024, 0x3b81, 0x8024, 0x3f04, 0xc024, 0x2808, 0x4800, 0x36f1, /* 18 */ 
         0x9811, 0x0007, 0x0001, 0x8028, 0x0006, 0x0002, 0x2a00, 0x040e,  
 }; 
 ​
 ​
 /*
 函数功能:激活PCM 录音模式
 函数参数:
                 agc:0,自动增益
         1024相当于1倍
         512相当于0.5倍
         最大值65535=64倍          
 */
 void VS1053_RecoderInit(u16 agc)
 {
     //如果是IMA ADPCM,采样率计算公式如下:
     //采样率=CLKI/256*d;   
     //假设d=0,并2倍频,外部晶振为12.288M.那么Fc=(2*12288000)/256*6=16Khz
     //如果是线性PCM,采样率直接就写采样值 
   VS1053_WriteCmd(SPI_BASS,0x0000);    
     VS1053_WriteCmd(SPI_AICTRL0,8000);  //设置采样率,设置为8Khz
     VS1053_WriteCmd(SPI_AICTRL1,agc);       //设置增益,0,自动增益.1024相当于1倍,512相当于0.5倍,最大值65535=64倍 
     VS1053_WriteCmd(SPI_AICTRL2,0);       //设置增益最大值,0,代表最大值65536=64X
     VS1053_WriteCmd(SPI_AICTRL3,6);       //左通道(MIC单声道输入)
     VS1053_WriteCmd(SPI_CLOCKF,0X2000); //设置VS10XX的时钟,MULT:2倍频;ADD:不允许;CLK:12.288Mhz
     VS1053_WriteCmd(SPI_MODE,0x1804);       //MIC,录音激活    
     DelayMs(5);                 //等待至少1.35ms 
     VS1053_LoadPatch((u16*)VS1053_WavPlugin,40);//VS1053的WAV录音需要patch
 }

6.2 SD.c 这是SD卡的驱动代码

 #include "sdcard.h"            
 static u8  SD_Type=0;  //存放SD卡的类型
 ​
 /*
 函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节
 函数参数:data是要写入的数据
 返 回 值:读到的数据
 */
 u8 SDCardReadWriteOneByte(u8 DataTx)
 {        
     u16 cnt=0;               
     while((SPI1->SR&1<<1)==0)        //等待发送区空--等待发送缓冲为空 
     {
       cnt++;
       if(cnt>=65530)return 0;     //超时退出  u16=2个字节
     }   
     SPI1->DR=DataTx;                      //发送一个byte 
     cnt=0;
     while((SPI1->SR&1<<0)==0)       //等待接收完一个byte   
     {
       cnt++;
       if(cnt>=65530)return 0;      //超时退出
     }                               
     return SPI1->DR;                //返回收到的数据
 }
 ​
 ​
 /*
 函数功能:底层SD卡接口初始化
 SPI1接口---SD卡接线原理
 5V----5V
 GND---GND
 SPI1_MOSI---PA7
 SPI1_MISO---PA6
 SPI1_CS---PA4
 SPI1_SCK--PA5
 */
 void SDCardSpiInit(void)
 {
   /*1. 开启时钟*/
     RCC->APB2ENR|=1<<2;         //使能PORTA时钟
   
   /*2. 配置GPIO口模式*/
   GPIOA->CRL&=0x0000FFFF;
   GPIOA->CRL|=0xB8B30000;
   
   /*3. 上拉*/
   GPIOA->ODR|=1<<4;
     
         /*SPI1基本配置*/
     RCC->APB2ENR|=1<<12;    //开启SPI1时钟
     RCC->APB2RSTR|=1<<12;
     RCC->APB2RSTR&=~(1<<12);
     
     SPI1->CR1=0X0;      //清空寄存器
     SPI1->CR1|=0<<15; //选择“双线双向”模式
     SPI1->CR1|=0<<11; //使用8位数据帧格式进行发送/接收;
     SPI1->CR1|=0<<10; //全双工(发送和接收);
     SPI1->CR1|=1<<9;  //启用软件从设备管理
     SPI1->CR1|=1<<8;  //NSS
     SPI1->CR1|=0<<7;  //帧格式,先发送高位
     SPI1->CR1|=0x0<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。
     SPI1->CR1|=1<<2;  //配置为主设备
     SPI1->CR1|=1<<1;  //空闲状态时, SCK保持高电平。
     SPI1->CR1|=1<<0;  //数据采样从第二个时钟边沿开始。
     SPI1->CR1|=1<<6;  //开启SPI设备。
 }
 ​
 /*
 函数功能:取消选择,释放SPI总线
 */
 void SDCardCancelCS(void)
 {
     SDCARD_CS=1;
     SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
 }
 ​
 ​
 /*
 函数 功 能:选择sd卡,并且等待卡准备OK
 函数返回值:0,成功;1,失败;
 */
 u8 SDCardSelectCS(void)
 {
     SDCARD_CS=0;
     if(SDCardWaitBusy()==0)return 0;//等待成功
     SDCardCancelCS();
     return 1;//等待失败
 }
 ​
 ​
 /*
 函数 功 能:等待卡准备好
 函数返回值:0,准备好了;其他,错误代码
 */
 u8 SDCardWaitBusy(void)
 {
     u32 t=0;
     do
     {
         if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK
         t++;          
     }while(t<0xFFFFFF);//等待 
     return 1;
 }
 ​
 ​
 /*
 函数功能:等待SD卡回应
 函数参数:
                     Response:要得到的回应值
 返 回 值:
                     0,成功得到了该回应值
                     其他,得到回应值失败
 */
 u8 SDCardGetAck(u8 Response)
 {
     u16 Count=0xFFFF;//等待次数                           
     while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应     
     if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败   
     else return SDCard_RESPONSE_NO_ERROR;//正确回应
 }
 ​
 ​
 /*
 函数功能:从sd卡读取一个数据包的内容
 函数参数:
                 buf:数据缓存区
                 len:要读取的数据长度.
 返回值:
             0,成功;其他,失败; 
 */
 u8 SDCardRecvData(u8*buf,u16 len)
 {                 
     if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
     while(len--)//开始接收数据
     {
         *buf=SDCardReadWriteOneByte(0xFF);
         buf++;
     }
     //下面是2个伪CRC(dummy CRC)
     SDCardReadWriteOneByte(0xFF);
     SDCardReadWriteOneByte(0xFF);                                                           
     return 0;//读取成功
 }
 ​
 ​
 /*
 函数功能:向sd卡写入一个数据包的内容 512字节
 函数参数:
                     buf 数据缓存区
                     cmd 指令
 返 回 值:0表示成功;其他值表示失败;
 */
 u8 SDCardSendData(u8*buf,u8 cmd)
 {   
     u16 t;            
     if(SDCardWaitBusy())return 1;  //等待准备失效
     SDCardReadWriteOneByte(cmd);
     if(cmd!=0XFD)//不是结束指令
     {
         for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间
         SDCardReadWriteOneByte(0xFF); //忽略crc
         SDCardReadWriteOneByte(0xFF);
           t=SDCardReadWriteOneByte(0xFF); //接收响应
         if((t&0x1F)!=0x05)return 2;   //响应错误                                                            
     }                                                                                   
     return 0;//写入成功
 }
 ​
 ​
 ​
 /*
 函数功能:向SD卡发送一个命令
 函数参数:
                 u8 cmd   命令 
                 u32 arg  命令参数
                 u8 crc   crc校验值 
 返回值:SD卡返回的响应
 */                                                
 u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc)
 {
     u8 r1;  
     u8 Retry=0; 
         
     SDCardCancelCS();               //取消上次片选
     if(SDCardSelectCS())return 0XFF;//片选失效 
     //发送数据
     SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令
     SDCardReadWriteOneByte(arg >> 24);
     SDCardReadWriteOneByte(arg >> 16);
     SDCardReadWriteOneByte(arg >> 8);
     SDCardReadWriteOneByte(arg);      
     SDCardReadWriteOneByte(crc); 
     if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
     Retry=0X1F;
     do
     {
         r1=SDCardReadWriteOneByte(0xFF);
     }while((r1&0X80) && Retry--);     //等待响应,或超时退出
    return r1;   //返回状态值
 }   
 ​
 ​
 ​
 /*
 函数功能:获取SD卡的CID信息,包括制造商信息
 函数参数:u8 *cid_data(存放CID的内存,至少16Byte)      
 返 回 值:
                     0:成功,1:错误               
 */
 u8 GetSDCardCISDCardOutnfo(u8 *cid_data)
 {
     u8 r1;     
     //发SDCard_CMD10命令,读CID
     r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
     if(r1==0x00)
       {
             r1=SDCardRecvData(cid_data,16);//接收16个字节的数据  
     }
     SDCardCancelCS();//取消片选
     if(r1)return 1;
     else return 0;
 }   
 ​
 ​
 /*
 函数说明:
                     获取SD卡的CSD信息,包括容量和速度信息
 函数参数:
                     u8 *cid_data(存放CID的内存,至少16Byte)     
 返 回 值:
                     0:成功,1:错误   
 */
 u8 GetSDCardCSSDCardOutnfo(u8 *csd_data)
 {
     u8 r1;   
     r1=SendSDCardCmd(SDCard_CMD9,0,0x01);    //发SDCard_CMD9命令,读CSD
     if(r1==0)
     {
         r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 
     }
     SDCardCancelCS();//取消片选
     if(r1)return 1;
     else return 0;
 }  
 ​
 ​
 /*
 函数功能:获取SD卡的总扇区数(扇区数)   
 返 回 值:
                 0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节)
 说   明:
                 每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过.  
 */
 u32 GetSDCardSectorCount(void)
 {
     u8 csd[16];
     u32 Capacity;  
       u16 csize;                        
     if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;   //取CSD信息,如果期间出错,返回0
     if((csd[0]&0xC0)==0x40)  //SDHC卡,按照下面方式计算
     {   
             csize = csd[9] + ((u16)csd[8] << 8) + 1;
             Capacity = (u32)csize << 10;//得到扇区数            
     }
     return Capacity;
 }
 ​
 ​
 ​
 /*
 函数功能: 初始化SD卡
 返 回 值: 非0表示初始化失败!
 */
 u8 SDCardDeviceInit(void)
 {
   u8 r1;      // 存放SD卡的返回值
   u16 retry;  // 用来进行超时计数
   u8 buf[4];  
     u16 i;
     SDCardSpiInit();        //初始化底层IO口
     
     for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲
     retry=20;
     do
     {
         r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置
     }while((r1!=0X01) && retry--);
     SD_Type=0;   //默认无卡
     if(r1==0X01)
     {
         if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1)  //SD V2.0
         {
             for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);
             if(buf[2]==0X01&&buf[3]==0XAA)    //卡是否支持2.7~3.6V
             {
                 retry=0XFFFE;
                 do
                 {
                     SendSDCardCmd(SDCard_CMD55,0,0X01);     //发送SDCard_CMD55
                     r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41
                 }while(r1&&retry--);
                 if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
                 {
                     for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
                     if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC;    //检查CCS
                     else SD_Type=SDCard_TYPE_V2;   
                 }
             }
         }
     }
     SDCardCancelCS();       //取消片选
     if(SD_Type)return 0;  //初始化成功返回0
     else if(r1)return r1; //返回值错误值     
     return 0xaa;          //其他错误
 }
 ​
 ​
 /*
 函数功能:读SD卡
 函数参数:
                 buf:数据缓存区
                 sector:扇区
                 cnt:扇区数
 返回值:
                 0,ok;其他,失败.
 说  明:
                 SD卡一个扇区大小512字节
 */
 u8 SDCardReadData(u8*buf,u32 sector,u32 cnt)
 {
     u8 r1;
     if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址
     if(cnt==1)
     {
         r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令
         if(r1==0)                                                 //指令发送成功
         {
             r1=SDCardRecvData(buf,512);         //接收512个字节     
         }
     }else
     {
         r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令
         do
         {
             r1=SDCardRecvData(buf,512);//接收512个字节    
             buf+=512;  
         }while(--cnt && r1==0);     
         SendSDCardCmd(SDCard_CMD12,0,0X01); //发送停止命令
     }   
     SDCardCancelCS();//取消片选
     return r1;//
 }
 ​
 /*
 函数功能:向SD卡写数据
 函数参数:
                 buf:数据缓存区
                 sector:起始扇区
                 cnt:扇区数
 返回值:
                 0,ok;其他,失败.
 说  明:
                 SD卡一个扇区大小512字节
 */
 u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt)
 {
     u8 r1;
     if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址
     if(cnt==1)
     {
         r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令
         if(r1==0)//指令发送成功
         {
             r1=SDCardSendData(buf,0xFE);//写512个字节      
         }
     }
     else
     {
         if(SD_Type!=SDCard_TYPE_MMC)
         {
             SendSDCardCmd(SDCard_CMD55,0,0X01); 
             SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令 
         }
         r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令
         if(r1==0)
         {
             do
             {
                 r1=SDCardSendData(buf,0xFC);//接收512个字节   
                 buf+=512;  
             }while(--cnt && r1==0);
             r1=SDCardSendData(0,0xFD);//接收512个字节 
         }
     }   
     SDCardCancelCS();//取消片选
     return r1;//
 }