基于 STM32 的录音机设计 (STM32F103+VS1053B)
- 2022 年 8 月 23 日 重庆
本文字数:11228 字
阅读完需:约 37 分钟
这是基于 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,启动自动回放功能。自动扫描目录,按顺序播放录音文件。
技术介绍:
(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 卡上存放的文件
SD 卡上有两个目录:font 目录和 wav 目录。 font 目录下存放 16x16 字库文件。 wav 目录下存放录音的音频文件。
六、部分源码
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;//
}
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/b65267836eed037c350108fc3】。文章转载请联系作者。
DS小龙哥
之所以觉得累,是因为说的比做的多。 2022.01.06 加入
熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域
评论