STM32--vs1053 WAV录音实现(保存在SD卡)

发布者:SereneSoul55最新更新时间:2018-07-24 来源: eefocus关键字:STM32  vs1053  WAV录音 手机看文章 扫描二维码
随时随地手机看文章

一: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  vs1053  WAV录音 引用地址:STM32--vs1053 WAV录音实现(保存在SD卡)

上一篇:使用STM32控制无源蜂鸣器发声播放音乐
下一篇:STM32 PCM1770调试

推荐阅读最新更新时间:2024-03-16 16:09

STM32】5—UART串口(中断模式)
0 实验预期效果 完成串口数据的接收和发送 1 相关原理图 2 软件配置 STM32CubeMX配置USART1: 在NVIC中配置USART中断优先级: 3 代码编写 3.1 函数认识 见博客【STM32】HAL库学习 2—hal_uart_kokoのadventure的博客-CSDN博客 3.1.1 串口发送 /** * @brief Sends an amount of data in non blocking mode. * @note When UART parity is not enabled (PCE = 0), and Wo
[单片机]
【<font color='red'>STM32</font>】5—UART串口(中断模式)
关于STM32复位的那些事
1. 硬件复位 硬件复位电路如下图,直接将RESET引脚拉低即可,如下: 2. 软件复位 软件复位库函数: NVIC_SystemReset(); STM32F1XX系列中,复位函数在core_cm3.h文件中: /** * @brief Initiate a system reset request. * * Initiate a system reset request to reset the MCU */static __INLINE void NVIC_SystemReset(void){ SCB- AIRCR = ((0x5FA SCB_AIRCR_VECTKEY_Po
[单片机]
关于<font color='red'>STM32</font>复位的那些事
STM32的FSMC仿真可运行
调试了几天STM32的FSMC驱动液晶的程序,原先在MDK上编译下载后可以运行的程序,移植到IAR上后就出现了问题,(以下描述的是在从新上电复位后运行的现象,但在jlink调试过程中运行都是正常的) 问题是这样的:程序运行完 *(__IO uint16_t *) (Bank1_LCD_C)= index; 后就不在往下运行了,反复实验了N次,只有一次进入了Hardfault, 而剩下的情况都是mcu不运行了。查了很多资料终于在一片《STM32F103 FSMC 同步模式 学习笔记2》文章中找到了答案,原因是在FSMC初始化过程中出现的,我们初学者编程序都有以个缺点,就是定义申请的变量后都不进行变量初始化操作(特别是定义了一些较为复
[单片机]
stm32学习笔记 系统时钟
在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 ①、HSI是高速内部时钟,RC振荡器,频率为8MHz。 ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。 ③、LSI是低速内部时钟,RC振荡器,频率为40kHz。 ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。 ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍, 但是其输出频率最大不得超过72MHz。 其中40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外, 实时时钟RTC的时钟源还可以选择LSE
[单片机]
<font color='red'>stm32</font>学习笔记 系统时钟
STM32学习之路-LCD(2)
这些天一直在研究LCD的初始化函数,因为不过是用IO口模拟时序还是用FSMC来驱动LCD,都必须要弄好LCD的初始化 其实LCD的初始化就是跟着LCD IC的datasheet来写寄存器,大部分都使用上面的默认值,网上有很多修改的例子,这里就不 放出来了.但是我想写下一些比较重要的东西。 这是从网上下载来的一个文件的前半部分: 当然,别的型号的IC也是有这些东西的,不过可能有些地址不同而已. R0,这个命令有两个功能,如果对它写,则最低位为OSC,用于开启和关闭振荡器。而如果对它读操作, 则返回的是控制器的型号。这个命令最大的功能是通过读它可以得到控制器的型号,而我们的代码知道了控制器的型号之后, 可以针对不同型号的控制器
[单片机]
STM32出现“Internal command error”错误无法下载程序的解决方法
方法一: Options for target-- Debug-- 右边的settings--- Debug分栏的Connect & Reset option, Nornal改为With pre-reset. 效果: 有时候可以有时候不行. 方法二: 有复位按钮的话,将复位按钮按住不放,然后在Keil点击程序下载/调试按钮,约0.5~1秒后松开复位按钮; 没有复位按钮的话,可用镊子夹住复位电路的电容两端,然后在Keil点击程序下载/调试按钮,约0.5~1秒后松开夹子; 效果:基本成功,暂未有失败的情况。 注意:在做这些操作之前,必须先对硬件做一次彻底断电操作,否则有可能失败。特别是在软件有进入低功耗模式
[单片机]
基于STM32的多种printf用法 你都知道吗?
在调试代码的时候,最常用的就是使用printf函数来输出一些打印信息,提示自己代码的执行情况。 如果你的UART串口不够用,还要用printf,此时该怎么办? 方法其实是有的,那就是:使用SWO/SWV。 SWO:Serial Wire Output,串行线输出 SWD:Serial Wire Viewer,串行线查看器 一、常见printf输出 1.UART打印 这种使用UART串口输出,需要占用一个 硬件 串口。 2. Keil MDK- ARM Viewer输出 3.IAR EWARM终端输出 4.ST-LINK U ti lity SWV输出 这后面三种不占用硬件UART,使用ITM机制。 二、UA
[单片机]
基于<font color='red'>STM32</font>的多种printf用法 你都知道吗?
基于STM32的实时心率检测仪设计
一、开发环境介绍 主控芯片: STM32F103ZET6 代码编程软件: keil5 心率检测模块: PulseSensor WIFI模块: ESP8266 --可选的。直接使用串口有线传输给上位机也可以。 上位机: C++(QT) 设计的。 支持PC机电脑、Android手机显示。 与上位机的传输协议: 支持串口传输、WIFI网络传输两种。 如果是PC就可以直接连接串口传输数据,如果不方便可以直接通过WIFI---TCP协议传输。 二、PulseSensor心率模块介绍 PulseSensor 是一款用于脉搏心率测量的光电反射式模拟传感器。 可以将其佩戴于手指、耳垂、手腕等处,通过杜邦线--导线将引脚连接到单片机,可将采集到
[单片机]
基于<font color='red'>STM32</font>的实时心率检测仪设计
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
更多每日新闻
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved