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,启动自动回放功能。自动扫描目录,按顺序播放录音文件。
技术介绍:
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系统板:
OLED显示屏:
VS1053:
SD卡卡槽:
开发板有一个复位键和一个K0按键。
程序下载:
程序支持三种模式:
因为开发板只有一个K0按键,所以三种模式都是通过一个按键进行切换的。
一个按键是通过按下的计数方式进行切换的,切换的顺序是自动录音模式、手动录音模式、回放模式。
(1)自动录音模式:按下一次按键后,进入自动录音模式,自动录音模式下,录音5秒自动退出,退出后自动启动播放状态,就是播放刚才5秒录制的音频,播放过程中按下按键可以退出播放状态。
(2)手动录音模式:第二次按下K0按键后,进入手动录音模式,手动录音模式下,可以长时间录音,如果要结束录音,按下K0按键即可结束;结束后自动启动播放状态,就是播放刚才录制的音频,播放过程中按下按键可以退出播放状态。
(3)回放模式:第三次按下K0按键后,进入回放模式,自动扫描wav目录,进行顺序播放音频文件。
播放过程中可以按下K0按键退出回放模式。
每次录音后的文件是存放在SD卡根目录下的wav目录下。
每个状态都会在OLED显示屏上显示
也会同时通过串口打印到串口调试助手终端。
SD卡上有两个目录:font目录和wav目录。
font目录下存放16x16字库文件。
wav目录下存放录音的音频文件。
#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;iriff.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 } ;)>
#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;// }
全部0条评论
快来发表一下你的评论吧 !