一:VS1053介绍
1.vs1053支持ogg/mp3/aac/wma/midi音频解码,IMA ADPCM编码
2.SPI时序图
二:WAV格式介绍
VS1053 MP3模块支持2种格式的WAV录音: PCM格式或者IMA ADPCM格式,其中PCM(脉冲编码调制)是最基本的WAVE 文件格式,这种文件直接存储采样的声音数
据没有经过任何的压缩。而IAM ADPCM则是使用了压缩算法,压缩比率为4:1。 我们主要讨论 PCM,因为这个最简单。我们将利用 VS1053 实现 16 位,8Khz
采样率的单声道WAV 录音(PCM格式)。
WAVE 文件是由若干个 Chunk 组成的。按照在文件中的出现位置包括:RIFF WAVE Chunk、 Format Chunk、 Fact Chunk(可选)和 Data Chunk。
每个Chunk 由块标识符、数据大小和数据三部分组成,如下所示:
其中块标识符由 4 个 ASCII 码构成,数据大小则标出紧跟其后的数据的长度(单位为字节),注意这个长度不包含块标识符和数据大小的长度,即不包含最前面的 8 个字节。所
以实际Chunk的大小为数据大小加 8。
1.首先,我们来看看 RIFF 块(RIFF WAVE Chunk),该块以“RIFF”作为标示,紧跟wav 文件大小(该大小是 wav 文件的总大小-8),然后数据段为“WAVE”,表示是 wav文件。RIFF块的Chunk 结构如下:
//RIFF块
typedef __packed struct
{
u32 ChunkID; //chunk id;这里固定为"RIFF",即 0X46464952
u32 ChunkSize ; //集合大小;文件总大小-8
u32 Format; //格式;WAVE,即0X45564157
}ChunkRIFF ;
2.Format块(Format Chunk),该块以“fmt ”作为标示(注意有个空格!),一般情况下,该段的大小为 16个字节,但是有些软件生成的 wav格式,该部分可能有18 个字节,含有 2个字节的附加信息。Format块的Chunk 结构如下:
//fmt块
typedef __packed struct
{
u32 ChunkID; //chunk id;这里固定为"fmt ",即 0X20746D66
u32 ChunkSize ; //子集合大小(不包括 ID和Size);这里为:20.
u16 AudioFormat; //音频格式;0X10,表示线性PCM;0X11 表示IMA AD
u16 NumOfChannels; //通道数量;1,表示单声道;2,表示双声道;
u32 SampleRate; //采样率;0X1F40,表示 8Khz
u32 ByteRate; //字节速率;
u16 BlockAlign; //块对齐(字节);
u16 BitsPerSample; //单个采样数据大小;4位ADPCM,设置为4
}ChunkFMT;
3.接下来,我们再看看 Fact 块(Fact Chunk),该块为可选块,以“fact”作为标示,不是每个 WAV 文件都有,在非 PCM 格式的文件中,一般会在 Format 结构后面加入一个
Fact块,该块Chunk 结构如下:
//fact块
typedef __packed struct
{
u32 ChunkID; //chunk id;这里固定为"fact",即 0X74636166;
u32 ChunkSize ; //子集合大小(不包括ID和Size);这里为:4.
u32 DataFactSize; //数据转换为PCM格式后的大小
}ChunkFACT;
4.最后,我们来看看数据块(Data Chunk),该块是真正保存wav数据的地方,以“data”
'作为该Chunk 的标示。然后是数据的大小。紧接着就是 wav数据。根据Format Chunk 中的
声道数以及采样bit数,wav数据的 bit位置可以分成如表所示的几种形式:
我们采用的是 16位,单声道,所以每个取样为 2 个字节,低字节在前,高字节在后。数据块的Chunk结构如下:
//data块
typedef __packed struct
{
u32 ChunkID; //chunk id;这里固定为"data",即0X61746164
u32 ChunkSize ; //子集合大小(不包括 ID和Size);文件大小-60.
}ChunkDATA;
三:激活PCM录音
VS1053激活PCM 录音需要设置的寄存器和相关位如表所示:
通过设置 SCI_MODE 寄存器的 2、12、14 位,来激活 PCM 录音,SCI_MODE 的各位描述见VS1053 的数据手册。SCI_AICTRL0 寄存器用于设置采样率,我们本文档用的是 8K
的采样率,所以设置这个值为 8000即可。SCI_AICTRL1 寄存器用于设置AGC,1024相当于数字增加1,这里建议大家设置 AGC 在4(4*1024)左右比较合适。SCI_AICTRL2 用于设置自动AGC的时候的最大值,当设置为0的时候表示最大 64(65536),这个大家按自己的需要设置即可。最后,SCI_AICTRL3,我们本文档用到的是咪头线性PCM单声道录音,所以设置该寄存器值为6。
通过这几个寄存器的设置,我们就激活VS1053 的PCM录音了。 不过, VS1053 的PCM录音有一个小BUG,必须通过加载 patch才能解决,如果不加载 patch,那么 VS1053 是不输出PCM数据的,VLSI 提供了我们这个patch,只需要通过软件加载即可。
1.读取PCM数据
在激活了PCM录音之后,SCI_HDAT0和 SCI_HDAT1 有了新的功能。VS1053的PCM采样缓冲区由1024个 16位数据组成,如果SCI_HDAT1 大于0,则说明可以从 SCI_HDAT0
读取至少 SCI_HDAT1 个 16 位数据,如果数据没有被及时读取,那么将溢出,并返回空的状态。
注意,如果 SCI_HDAT1≥896,最好等待缓冲区溢出,以免数据混叠。所以,对我们只需要判断SCI_HDAT1 的值非零,然后从 SCI_HDAT0读取对应长度的数据,即完
成一次数据读取,以此循环,即可实现PCM数据的持续采集。
四:实现WAV 录音需要经过哪些步骤:
1) 设置 VS1053 PCM采样参数
这一步,我们要设置 PCM 的格式(线性 PCM)、采样率(8K)、位数(16 位)、通道数(单声道)等重要参数,同时还要选择采样通道(咪头),还包括AGC设置等。可以
说这里的设置直接决定了我们 wav文件的性质。
2) 激活 VS1053的 PCM模式,加载patch
通过激活VS1053的PCM格式,让其开始PCM数据采集,同时,由于VS1053的 BUG,我们需要加载patch,以实现正常的PCM数据接收。
3) 创建WAV文件,并保存 wav头
在前两部设置成功之后,我们即可正常的从SCI_HDAT0 读取我们需要的PCM数据了,不过在这之前,我们需要先在创建一个新的文件,并写入 wav 头,然后才能开始写入我们
的PCM数据。
4) 读取PCM数据
经过前面几步的处理,这一步就比较简单了,只需要不停的从 SCI_HDAT0读取数据,然后存入 wav 文件即可,不过这里我们还需要做文件大小统计,在最后的时候写入 wav 头
里面。
5) 计算整个文件大小,重新保存 wav头并关闭文件
在结束录音的时候,我们必须知道本次录音的大小(数据大小和整个文件大小),然后更新 wav 头,重新写入文件,最后因为 FATFS,在文件创建之后,必须调用 f_close,文件
才会真正体现在文件系统里面,否则是不会写入的!所以最后还需要调用 f_close,以保存文件。
五:vs1053代码驱动程序
[cpp] view plaincopy
#include "vs10XX.h"
#include "delay.h"
#include "mmc_sd.h"
#include "spi.h"
#include "usart.h"
//VS10XX默认设置参数
_vs10xx_obj vsset=
{
210, //音量:210
6, //低音上线 60Hz
15, //低音提升 15dB
10, //高音下限 10Khz
15, //高音提升 10.5dB
0, //空间效果
};
////////////////////////////////////////////////////////////////////////////////
//移植时候的接口
//data:要写入的数据
//返回值:读到的数据
u8 VS_SPI_ReadWriteByte(u8 data)
{
return SPI1_ReadWriteByte(data);
}
//SD卡初始化的时候,需要低速
void VS_SPI_SpeedLow(void)
{
SPI1_SetSpeed(SPI_SPEED_64);//设置到低速模式
}
//SD卡正常工作的时候,可以高速了
void VS_SPI_SpeedHigh(void)
{
SPI1_SetSpeed(SPI_SPEED_8);//设置到高速模式
}
//初始化VS10XX的IO口
void VS_Init(void)
{
RCC->APB2ENR|=1<<2; //PORTA时钟使能
RCC->APB2ENR|=1<<3; //PORTB时钟使能
GPIOB->CRL&=0XFFFFFFF0; //PB0 XCS
GPIOB->CRL|=0X00000003;
GPIOA->CRL&=0XFFF00F0F; //PA3 XRESET;PA1 DQ;PA4 XDCS
GPIOA->CRL|=0X00033080;
GPIOA->ODR|=(1<<3)|(1<<1)|(1<<4); //PA2,1,4,上拉
GPIOB->ODR|=(1<<0); //PB0上拉
SPI1_Init(); //初始化SPI
}
////////////////////////////////////////////////////////////////////////////////
//软复位VS10XX
void VS_Soft_Reset(void)
{
u8 retry=0;
while(VS_DQ==0); //等待软件复位结束
VS_SPI_ReadWriteByte(0Xff);//启动传输
retry=0;
while(VS_RD_Reg(SPI_MODE)!=0x0800)// 软件复位,新模式
{
VS_WR_Cmd(SPI_MODE,0x0804);// 软件复位,新模式
delay_ms(2);//等待至少1.35ms
if(retry++>100)break;
}
while(VS_DQ==0);//等待软件复位结束
retry=0;
while(VS_RD_Reg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD
{
VS_WR_Cmd(SPI_CLOCKF,0X9800);//设置VS10XX的时钟,3倍频 ,1.5xADD
if(retry++>100)break;
}
delay_ms(20);
}
//硬复位MP3
//返回1:复位失败;0:复位成功
u8 VS_HD_Reset(void)
{
u8 retry=0;
VS_RST=0;
delay_ms(20);
VS_XDCS=1;//取消数据传输
VS_XCS=1;//取消数据传输
VS_RST=1;
while(VS_DQ==0&&retry<200)//等待DREQ为高
{
retry++;
delay_us(50);
};
delay_ms(20);
if(retry>=200)return 1;
else return 0;
}
//正弦测试
void VS_Sine_Test(void)
{
VS_HD_Reset();
VS_WR_Cmd(0x0b,0X2020); //设置音量
VS_WR_Cmd(SPI_MODE,0x0820);//进入VS10XX的测试模式
while(VS_DQ==0); //等待DREQ为高
//printf("mode sin:%x\n",VS_RD_Reg(SPI_MODE));
//向VS10XX发送正弦测试命令:0x53 0xef 0x6e n 0x00 0x00 0x00 0x00
//其中n = 0x24, 设定VS10XX所产生的正弦波的频率值,具体计算方法见VS10XX的datasheet
VS_SPI_SpeedLow();//低速
VS_XDCS=0;//选中数据传输
VS_SPI_ReadWriteByte(0x53);
VS_SPI_ReadWriteByte(0xef);
VS_SPI_ReadWriteByte(0x6e);
VS_SPI_ReadWriteByte(0x24);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
delay_ms(100);
VS_XDCS=1;
//退出正弦测试
VS_XDCS=0;//选中数据传输
VS_SPI_ReadWriteByte(0x45);
VS_SPI_ReadWriteByte(0x78);
VS_SPI_ReadWriteByte(0x69);
VS_SPI_ReadWriteByte(0x74);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
delay_ms(100);
VS_XDCS=1;
//再次进入正弦测试并设置n值为0x44,即将正弦波的频率设置为另外的值
VS_XDCS=0;//选中数据传输
VS_SPI_ReadWriteByte(0x53);
VS_SPI_ReadWriteByte(0xef);
VS_SPI_ReadWriteByte(0x6e);
VS_SPI_ReadWriteByte(0x44);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
delay_ms(100);
VS_XDCS=1;
//退出正弦测试
VS_XDCS=0;//选中数据传输
VS_SPI_ReadWriteByte(0x45);
VS_SPI_ReadWriteByte(0x78);
VS_SPI_ReadWriteByte(0x69);
VS_SPI_ReadWriteByte(0x74);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
delay_ms(100);
VS_XDCS=1;
}
//ram 测试
//返回值:RAM测试结果
// VS1003如果得到的值为0x807F,则表明完好;VS1053为0X83FF.
u16 VS_Ram_Test(void)
{
VS_HD_Reset();
VS_WR_Cmd(SPI_MODE,0x0820);// 进入VS10XX的测试模式
while (VS_DQ==0); // 等待DREQ为高
VS_SPI_SpeedLow();//低速
VS_XDCS=0; // xDCS = 1,选择VS10XX的数据接口
VS_SPI_ReadWriteByte(0x4d);
VS_SPI_ReadWriteByte(0xea);
VS_SPI_ReadWriteByte(0x6d);
VS_SPI_ReadWriteByte(0x54);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_SPI_ReadWriteByte(0x00);
VS_XDCS=1;
delay_ms(150);
return VS_RD_Reg(SPI_HDAT0);// VS1003如果得到的值为0x807F,则表明完好;VS1053为0X83FF.;
}
//向VS10XX写命令
//address:命令地址
//data:命令数据
void VS_WR_Cmd(u8 address,u16 data)
{
while(VS_DQ==0);//等待空闲
VS_SPI_SpeedLow();//低速
VS_XDCS=1;
VS_XCS=0;
VS_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
VS_SPI_ReadWriteByte(address); //地址
VS_SPI_ReadWriteByte(data>>8); //发送高八位
VS_SPI_ReadWriteByte(data); //第八位
VS_XCS=1;
VS_SPI_SpeedHigh();//高速
}
//向VS10XX写数据
//data:要写入的数据
void VS_WR_Data(u8 data)
{
VS_SPI_SpeedHigh();//高速,对VS1003B,最大值不能超过36.864/4Mhz,这里设置为9M
VS_XDCS=0;
VS_SPI_ReadWriteByte(data);
VS_XDCS=1;
}
//读VS10XX的寄存器
//address:寄存器地址
//返回值:读到的值
//注意不要用倍速读取,会出错
u16 VS_RD_Reg(u8 address)
{
u16 temp=0;
while(VS_DQ==0); //等待空闲
VS_SPI_SpeedLow();//低速
VS_XDCS=1;
VS_XCS=0;
VS_SPI_ReadWriteByte(VS_READ_COMMAND); //发送VS10XX的读命令
VS_SPI_ReadWriteByte(address); //地址
temp=VS_SPI_ReadWriteByte(0xff); //读取高字节
temp=temp<<8;
temp+=VS_SPI_ReadWriteByte(0xff); //读取低字节
VS_XCS=1;
VS_SPI_SpeedHigh();//高速
return temp;
}
//读取VS10xx的RAM
//addr:RAM地址
//返回值:读到的值
u16 VS_WRAM_Read(u16 addr)
{
u16 res;
VS_WR_Cmd(SPI_WRAMADDR, addr);
res=VS_RD_Reg(SPI_WRAM);
return res;
}
//设置播放速度(仅VS1053有效)
//t:0,1,正常速度;2,2倍速度;3,3倍速度;4,4倍速;以此类推
void VS_Set_Speed(u8 t)
{
VS_WR_Cmd(SPI_WRAMADDR,0X1E04); //速度控制地址
while(VS_DQ==0); //等待空闲
VS_WR_Cmd(SPI_WRAM,t); //写入播放速度
}
//FOR WAV HEAD0 :0X7761 HEAD1:0X7665
//FOR MIDI HEAD0 :other info HEAD1:0X4D54
//FOR WMA HEAD0 :data speed HEAD1:0X574D
//FOR MP3 HEAD0 :data speed HEAD1:ID
//比特率预定值,阶层III
const u16 bitrate[2][16]=
{
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0},
{0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}
};
//返回Kbps的大小
//返回值:得到的码率
u16 VS_Get_HeadInfo(void)
{
unsigned int HEAD0;
unsigned int HEAD1;
HEAD0=VS_RD_Reg(SPI_HDAT0);
HEAD1=VS_RD_Reg(SPI_HDAT1);
//printf("(H0,H1):%x,%x\n",HEAD0,HEAD1);
switch(HEAD1)
{
case 0x7665://WAV格式
case 0X4D54://MIDI格式
case 0X4154://AAC_ADTS
case 0X4144://AAC_ADIF
case 0X4D34://AAC_MP4/M4A
case 0X4F67://OGG
case 0X574D://WMA格式
case 0X664C://FLAC格式
{
////printf("HEAD0:%d\n",HEAD0);
HEAD1=HEAD0*2/25;//相当于*8/100
if((HEAD1%10)>5)return HEAD1/10+1;//对小数点第一位四舍五入
else return HEAD1/10;
}
default://MP3格式,仅做了阶层III的表
{
HEAD1>>=3;
HEAD1=HEAD1&0x03;
if(HEAD1==3)HEAD1=1;
else HEAD1=0;
return bitrate[HEAD1][HEAD0>>12];
}
}
}
//得到平均字节数
//返回值:平均字节数速度
u32 VS_Get_ByteRate(void)
{
return VS_WRAM_Read(0X1E05);//平均位速
}
//得到需要填充的数字
//返回值:需要填充的数字
u16 VS_Get_EndFillByte(void)
{
return VS_WRAM_Read(0X1E06);//填充字节
}
//发送一次音频数据
//固定为32字节
//返回值:0,发送成功
// 1,VS10xx不缺数据,本次数据未成功发送
u8 VS_Send_MusicData(u8* buf)
{
u8 n;
if(VS_DQ!=0) //送数据给VS10XX
{
VS_XDCS=0;
for(n=0;n<32;n++)
{
VS_SPI_ReadWriteByte(buf[n]);
}
VS_XDCS=1;
}else return 1;
return 0;//成功发送了
}
//切歌
//通过此函数切歌,不会出现切换“噪声”
void VS_Restart_Play(void)
{
u16 temp;
u16 i;
u8 n;
u8 vsbuf[32];
for(n=0;n<32;n++)vsbuf[n]=0;//清零
temp=VS_RD_Reg(SPI_MODE); //读取SPI_MODE的内容
temp|=1<<3; //设置SM_CANCEL位
temp|=1<<2; //设置SM_LAYER12位,允许播放MP1,MP2
VS_WR_Cmd(SPI_MODE,temp); //设置取消当前解码指令
for(i=0;i<2048;) //发送2048个0,期间读取SM_CANCEL位.如果为0,则表示已经取消了当前解码
{
if(VS_Send_MusicData(vsbuf)==0)//每发送32个字节后检测一次
{
i+=32; //发送了32个字节
temp=VS_RD_Reg(SPI_MODE); //读取SPI_MODE的内容
if((temp&(1<<3))==0)break; //成功取消了
}
}
if(i<2048)//SM_CANCEL正常
{
temp=VS_Get_EndFillByte()&0xff;//读取填充字节
for(n=0;n<32;n++)vsbuf[n]=temp;//填充字节放入数组
for(i=0;i<2052;)
{
if(VS_Send_MusicData(vsbuf)==0)i+=32;//填充
}
}else VS_Soft_Reset(); //SM_CANCEL不成功,坏情况,需要软复位
temp=VS_RD_Reg(SPI_HDAT0);
temp+=VS_RD_Reg(SPI_HDAT1);
if(temp) //软复位,还是没有成功取消,放杀手锏,硬复位
{
VS_HD_Reset(); //硬复位
VS_Soft_Reset(); //软复位
}
}
//重设解码时间
void VS_Reset_DecodeTime(void)
{
VS_WR_Cmd(SPI_DECODE_TIME,0x0000);
VS_WR_Cmd(SPI_DECODE_TIME,0x0000);//操作两次
}
//得到mp3的播放时间n sec
//返回值:解码时长
u16 VS_Get_DecodeTime(void)
{
u16 dt=0;
dt=VS_RD_Reg(SPI_DECODE_TIME);
return dt;
}
//vs10xx装载patch.
//patch:patch首地址
//len:patch长度
void VS_Load_Patch(u16 *patch,u16 len)
{
u16 i;
u16 addr, n, val;
for(i=0;i { addr = patch[i++]; n = patch[i++]; if(n & 0x8000U) //RLE run, replicate n samples { n &= 0x7FFF; val = patch[i++]; while(n--)VS_WR_Cmd(addr, val); }else //copy run, copy n sample { while(n--) { val = patch[i++]; VS_WR_Cmd(addr, val); } } } } //设定VS10XX播放的音量和高低音 //volx:音量大小(0~254) void VS_Set_Vol(u8 volx) { u16 volt=0; //暂存音量值 volt=254-volx; //取反一下,得到最大值,表示最大的表示 volt<<=8; volt+=254-volx; //得到音量设置后大小 VS_WR_Cmd(SPI_VOL,volt);//设音量 } //设定高低音控制 //bfreq:低频上限频率 2~15(单位:10Hz) //bass:低频增益 0~15(单位:1dB) //tfreq:高频下限频率 1~15(单位:Khz) //treble:高频增益 0~15(单位:1.5dB,小于9的时候为负数) void VS_Set_Bass(u8 bfreq,u8 bass,u8 tfreq,u8 treble) { u16 bass_set=0; //暂存音调寄存器值 signed char temp=0; if(treble==0)temp=0; //变换 else if(treble>8)temp=treble-8; else temp=treble-9; bass_set=temp&0X0F; //高音设定 bass_set<<=4; bass_set+=tfreq&0xf; //高音下限频率 bass_set<<=4; bass_set+=bass&0xf; //低音设定 bass_set<<=4; bass_set+=bfreq&0xf; //低音上限 VS_WR_Cmd(SPI_BASS,bass_set); //BASS } //设定音效 //eft:0,关闭;1,最小;2,中等;3,最大. void VS_Set_Effect(u8 eft) { u16 temp; temp=VS_RD_Reg(SPI_MODE); //读取SPI_MODE的内容 if(eft&0X01)temp|=1<<4; //设定LO else temp&=~(1<<5); //取消LO if(eft&0X02)temp|=1<<7; //设定HO else temp&=~(1<<7); //取消HO VS_WR_Cmd(SPI_MODE,temp); //设定模式 } /////////////////////////////////////////////////////////////////////////////// //设置音量,音效等. void VS_Set_All(void) { VS_Set_Vol(vsset.mvol); VS_Set_Bass(vsset.bflimit,vsset.bass,vsset.tflimit,vsset.treble); VS_Set_Effect(vsset.effect); } 六:录音代码 #include "recorder.h" #include "delay.h" #include "usart.h" #include "key.h" #include "led.h" //#include "lcd.h" #include "vs10xx.h" #include "malloc.h" #include "ff.h" #include "exfuns.h" #include "text.h" //VS1053的WAV录音有bug,这个plugin可以修正这个问题 const u16 wav_plugin[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 recoder_enter_rec_mode(u16 agc) { //如果是IMA ADPCM,采样率计算公式如下: //采样率=CLKI/256*d; //假设d=0,并2倍频,外部晶振为12.288M.那么Fc=(2*12288000)/256*6=16Khz //如果是线性PCM,采样率直接就写采样值 VS_WR_Cmd(SPI_BASS,0x0000); VS_WR_Cmd(SPI_AICTRL0,8000); //设置采样率,设置为8Khz VS_WR_Cmd(SPI_AICTRL1,agc); //设置增益,0,自动增益.1024相当于1倍,512相当于0.5倍,最大值65535=64倍 VS_WR_Cmd(SPI_AICTRL2,0); //设置增益最大值,0,代表最大值65536=64X VS_WR_Cmd(SPI_AICTRL3,6); //左通道(MIC单声道输入) VS_WR_Cmd(SPI_CLOCKF,0X2000); //设置VS10XX的时钟,MULT:2倍频;ADD:不允许;CLK:12.288Mhz VS_WR_Cmd(SPI_MODE,0x1804); //MIC,录音激活 delay_ms(5); //等待至少1.35ms VS_Load_Patch((u16*)wav_plugin,40);//VS1053的WAV录音需要patch } //初始化WAV头. void recoder_wav_init(__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; //数据大小,还需要计算 } //显示录音时长 //x,y:地址 //tsec:秒钟数. void recoder_show_time(u32 tsec) { //显示录音时间 printf("TIME:"); printf("%d",tsec/60); //分钟 printf(":"); printf("%d\r\n",tsec%60); //秒钟 } //通过时间获取文件名 //仅限在SD卡保存,不支持FLASH DISK保存 //组合成:形如"0:RECORDER/REC20120321210633.wav"的文件名 void recoder_new_pathname(u8 *pname) { u8 res; u16 index=0; while(index<0XFFFF) { sprintf((char*)pname,"0:RECORDER/REC%05d.wav",index); res=f_open(ftemp,(const TCHAR*)pname,FA_READ);//尝试打开这个文件 if(res==FR_NO_FILE)break; //该文件名不存在=正是我们需要的. index++; } } //显示AGC大小 //x,y:坐标 //agc:增益值 1~15,表示1~15倍;0,表示自动增益 void recoder_show_agc(u8 agc) { printf("AGC: "); //显示名称,同时清楚上次的显示 if(agc==0)printf("AUTO\r\n"); //自动agc else printf("%d\r\n",agc); //显示AGC值 } //播放pname这个wav文件(也可以是MP3等) u8 rec_play_wav(u8 *pname) { FIL* fmp3; u16 br; u8 res,rval=0; u8 *databuf; u16 i=0; fmp3=(FIL*)mymalloc(sizeof(FIL));//申请内存 databuf=(u8*)mymalloc(512); //开辟512字节的内存区域 if(databuf==NULL||fmp3==NULL)rval=0XFF ;//内存申请失败. if(rval==0) { VS_HD_Reset(); //硬复位 VS_Soft_Reset(); //软复位 VS_Set_Vol(220); //设置音量 VS_Reset_DecodeTime(); //复位解码时间 res=f_open(fmp3,(const TCHAR*)pname,FA_READ);//打开文件 if(res==0)//打开成功. { VS_SPI_SpeedHigh(); //高速 while(rval==0) { res=f_read(fmp3,databuf,512,(UINT*)&br);//读出4096个字节 i=0; do//主播放循环 { if(VS_Send_MusicData(databuf+i)==0)i+=32;//给VS10XX发送音频数据 else recoder_show_time(VS_Get_DecodeTime());//显示播放时间 }while(i<512);//循环发送4096个字节 if(br!=512||res!=0) { rval=0; break;//读完了. } } f_close(fmp3); }else rval=0XFF;//出现错误 } myfree(fmp3); myfree(databuf); return rval; } //录音机 //所有录音文件,均保存在SD卡RECORDER文件夹内. u8 recoder_play(void) { u8 res; u8 key; u8 rval=0; __WaveHeader *wavhead=0; u32 sectorsize=0; FIL* f_rec=0; //文件 DIR recdir; //目录 u8 *recbuf; //数据内存 u16 w; u16 idx=0; u8 rec_sta=0; //录音状态 //[7]:0,没有录音;1,有录音; //[6:1]:保留 //[0]:0,正在录音;1,暂停录音; u8 *pname=0; u8 timecnt=0; //计时器 u32 recsec=0; //录音时间 u8 recagc=4; //默认增益为4 u8 playFlag=0; //播放标志 while(f_opendir(&recdir,"0:/RECORDER"))//打开录音文件夹 { printf("RECORDER文件夹错误!\r\n"); delay_ms(200); f_mkdir("0:/RECORDER"); //创建该目录 } f_rec=(FIL *)mymalloc(sizeof(FIL)); //开辟FIL字节的内存区域 if(f_rec==NULL)rval=1; //申请失败 wavhead=(__WaveHeader*)mymalloc(sizeof(__WaveHeader));//开辟__WaveHeader字节的内存区域 if(wavhead==NULL)rval=1; recbuf=mymalloc(512); if(recbuf==NULL)rval=1; pname=mymalloc(30); //申请30个字节内存,类似"0:RECORDER/REC00001.wav" if(pname==NULL)rval=1; if(rval==0) //内存申请OK { recoder_enter_rec_mode(1024*recagc); while(VS_RD_Reg(SPI_HDAT1)>>8); //等到buf 较为空闲再开始 recoder_show_time(recsec); //显示时间 recoder_show_agc(recagc); //显示agc pname[0]=0; //pname没有任何文件名 while(rval==0) { key=KEY_Scan(0); switch(key) { case KEY0_PRES: //STOP&SAVE printf("key0 is down\r\n"); if(rec_sta&0X80)//有录音 { wavhead->riff.ChunkSize=sectorsize*512+36; //整个文件的大小-8; wavhead->data.ChunkSize=sectorsize*512; //数据大小 f_lseek(f_rec,0); //偏移到文件头. f_write(f_rec,(const void*)wavhead,sizeof(__WaveHeader),&bw);//写入头数据 f_close(f_rec); sectorsize=0; } rec_sta=0; recsec=0; LED1=1; //关闭DS1 recoder_show_time(recsec); //显示时间 break; case KEY1_PRES: //REC/PAUSE printf("key1 is down\r\n"); if(rec_sta&0X01)//原来是暂停,继续录音 { rec_sta&=0XFE;//取消暂停 }else if(rec_sta&0X80)//已经在录音了,暂停 { rec_sta|=0X01; //暂停 }else //还没开始录音 { rec_sta|=0X80; //开始录音 recoder_new_pathname(pname); //得到新的名字 printf("%s\r\n",pname+11); //显示当前录音文件名字 recoder_wav_init(wavhead); //初始化wav数据 res=f_open(f_rec,(const TCHAR*)pname, FA_CREATE_ALWAYS | FA_WRITE); if(res) //文件创建失败 { rec_sta=0; //创建文件失败,不能录音 rval=0XFE; //提示是否存在SD卡 }else res=f_write(f_rec,(const void*)wavhead,sizeof(__WaveHeader),&bw);//写入头数据 } break; case WKUP_PRES://播放录音(仅在非录音状态下有效) printf("wk_up is down\r\n"); if(rec_sta==0)playFlag=1; break; } /////////////////////////////////////////////////////////// //读取数据 if(rec_sta==0X80)//已经在录音了 { w=VS_RD_Reg(SPI_HDAT1); if((w>=256)&&(w<896)) { idx=0; while(idx<512) //一次读取512字节 { w=VS_RD_Reg(SPI_HDAT0); recbuf[idx++]=w&0XFF; recbuf[idx++]=w>>8; } res=f_write(f_rec,recbuf,512,&bw);//写入文件 if(res) { printf("err:%d\r\n",res); printf("bw:%d\r\n",bw); break;//写入出错. } sectorsize++;//扇区数增加1,约为32ms } }else//没有开始录音,按KEY0播放音频 { if(playFlag&&pname[0])//如果wk_up按键被按下,且pname不为空 { printf("播放:"); printf("%s\r\n",pname+11); //显示当播放的文件名字 rec_play_wav(pname); //播放pname recoder_enter_rec_mode(1024*recagc); //重新进入录音模式 while(VS_RD_Reg(SPI_HDAT1)>>8); //等到buf 较为空闲再开始 recoder_show_time(recsec); //显示时间 recoder_show_agc(recagc); //显示agc playFlag = 0; } delay_ms(5); timecnt++; if((timecnt%20)==0)LED1=!LED1;//DS1闪烁 } ///////////////////////////////////////////////////////////// if(recsec!=(sectorsize*4/125))//录音时间显示 { LED1=!LED1;//DS0闪烁 recsec=sectorsize*4/125; recoder_show_time(recsec);//显示时间 } } } myfree(wavhead); myfree(recbuf); myfree(f_rec); myfree(pname); return rval; }
上一篇:使用STM32控制无源蜂鸣器发声播放音乐
下一篇:STM32 PCM1770调试
推荐阅读最新更新时间:2024-03-16 16:09