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

一:VS1053介绍

1.vs1053支持ogg/mp3/aac/wma/midi音频解码,IMA ADPCM编码

2.SPI时序图

STM32--vs1053 WAV录音实现(保存在SD卡)_第1张图片

二: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 录音需要设置的寄存器和相关位如表所示: 

STM32--vs1053 WAV录音实现(保存在SD卡)_第2张图片

通过设置 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 plain copy
  1. #include "vs10XX.h"   
  2. #include "delay.h"  
  3. #include "mmc_sd.h"  
  4. #include "spi.h"  
  5. #include "usart.h"            
  6.   
  7. //VS10XX默认设置参数  
  8. _vs10xx_obj vsset=  
  9. {  
  10.     210,    //音量:210  
  11.     6,      //低音上线 60Hz  
  12.     15,     //低音提升 15dB   
  13.     10,     //高音下限 10Khz      
  14.     15,     //高音提升 10.5dB  
  15.     0,      //空间效果    
  16. };  
  17.   
  18. ////////////////////////////////////////////////////////////////////////////////  
  19. //移植时候的接口  
  20. //data:要写入的数据  
  21. //返回值:读到的数据  
  22. u8 VS_SPI_ReadWriteByte(u8 data)  
  23. {                  
  24.     return SPI1_ReadWriteByte(data);        
  25. }  
  26. //SD卡初始化的时候,需要低速  
  27. void VS_SPI_SpeedLow(void)  
  28. {                                  
  29.     SPI1_SetSpeed(SPI_SPEED_64);//设置到低速模式   
  30. }  
  31. //SD卡正常工作的时候,可以高速了  
  32. void VS_SPI_SpeedHigh(void)  
  33. {                           
  34.     SPI1_SetSpeed(SPI_SPEED_8);//设置到高速模式           
  35. }  
  36. //初始化VS10XX的IO口    
  37. void VS_Init(void)  
  38. {  
  39.     RCC->APB2ENR|=1<<2;    //PORTA时钟使能   
  40.     RCC->APB2ENR|=1<<3;    //PORTB时钟使能   
  41.     GPIOB->CRL&=0XFFFFFFF0;  //PB0 XCS  
  42.     GPIOB->CRL|=0X00000003;   
  43.     GPIOA->CRL&=0XFFF00F0F;  //PA3 XRESET;PA1 DQ;PA4 XDCS  
  44.     GPIOA->CRL|=0X00033080;     
  45.     GPIOA->ODR|=(1<<3)|(1<<1)|(1<<4);  //PA2,1,4,上拉     
  46.     GPIOB->ODR|=(1<<0);    //PB0上拉   
  47.     SPI1_Init();                                //初始化SPI    
  48. }       
  49. ////////////////////////////////////////////////////////////////////////////////            
  50. //软复位VS10XX  
  51. void VS_Soft_Reset(void)  
  52. {      
  53.     u8 retry=0;                      
  54.     while(VS_DQ==0); //等待软件复位结束      
  55.     VS_SPI_ReadWriteByte(0Xff);//启动传输  
  56.     retry=0;  
  57.     while(VS_RD_Reg(SPI_MODE)!=0x0800)// 软件复位,新模式    
  58.     {  
  59.         VS_WR_Cmd(SPI_MODE,0x0804);// 软件复位,新模式        
  60.         delay_ms(2);//等待至少1.35ms   
  61.         if(retry++>100)break;          
  62.     }              
  63.     while(VS_DQ==0);//等待软件复位结束     
  64.     retry=0;  
  65.     while(VS_RD_Reg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD   
  66.     {  
  67.         VS_WR_Cmd(SPI_CLOCKF,0X9800);//设置VS10XX的时钟,3倍频 ,1.5xADD  
  68.         if(retry++>100)break;          
  69.     }                                                         
  70.     delay_ms(20);  
  71. }   
  72. //硬复位MP3  
  73. //返回1:复位失败;0:复位成功        
  74. u8 VS_HD_Reset(void)  
  75. {  
  76.     u8 retry=0;  
  77.     VS_RST=0;  
  78.     delay_ms(20);  
  79.     VS_XDCS=1;//取消数据传输  
  80.     VS_XCS=1;//取消数据传输  
  81.     VS_RST=1;        
  82.     while(VS_DQ==0&&retry<200)//等待DREQ为高  
  83.     {  
  84.         retry++;  
  85.         delay_us(50);  
  86.     };  
  87.     delay_ms(20);     
  88.     if(retry>=200)return 1;  
  89.     else return 0;                 
  90. }  
  91. //正弦测试   
  92. void VS_Sine_Test(void)  
  93. {                                                 
  94.     VS_HD_Reset();     
  95.     VS_WR_Cmd(0x0b,0X2020);   //设置音量       
  96.     VS_WR_Cmd(SPI_MODE,0x0820);//进入VS10XX的测试模式       
  97.     while(VS_DQ==0);     //等待DREQ为高  
  98.     //printf("mode sin:%x\n",VS_RD_Reg(SPI_MODE));  
  99.     //向VS10XX发送正弦测试命令:0x53 0xef 0x6e n 0x00 0x00 0x00 0x00  
  100.     //其中n = 0x24, 设定VS10XX所产生的正弦波的频率值,具体计算方法见VS10XX的datasheet  
  101.     VS_SPI_SpeedLow();//低速   
  102.     VS_XDCS=0;//选中数据传输  
  103.     VS_SPI_ReadWriteByte(0x53);  
  104.     VS_SPI_ReadWriteByte(0xef);  
  105.     VS_SPI_ReadWriteByte(0x6e);  
  106.     VS_SPI_ReadWriteByte(0x24);  
  107.     VS_SPI_ReadWriteByte(0x00);  
  108.     VS_SPI_ReadWriteByte(0x00);  
  109.     VS_SPI_ReadWriteByte(0x00);  
  110.     VS_SPI_ReadWriteByte(0x00);  
  111.     delay_ms(100);  
  112.     VS_XDCS=1;   
  113.     //退出正弦测试  
  114.     VS_XDCS=0;//选中数据传输  
  115.     VS_SPI_ReadWriteByte(0x45);  
  116.     VS_SPI_ReadWriteByte(0x78);  
  117.     VS_SPI_ReadWriteByte(0x69);  
  118.     VS_SPI_ReadWriteByte(0x74);  
  119.     VS_SPI_ReadWriteByte(0x00);  
  120.     VS_SPI_ReadWriteByte(0x00);  
  121.     VS_SPI_ReadWriteByte(0x00);  
  122.     VS_SPI_ReadWriteByte(0x00);  
  123.     delay_ms(100);  
  124.     VS_XDCS=1;         
  125.   
  126.     //再次进入正弦测试并设置n值为0x44,即将正弦波的频率设置为另外的值  
  127.     VS_XDCS=0;//选中数据传输        
  128.     VS_SPI_ReadWriteByte(0x53);  
  129.     VS_SPI_ReadWriteByte(0xef);  
  130.     VS_SPI_ReadWriteByte(0x6e);  
  131.     VS_SPI_ReadWriteByte(0x44);  
  132.     VS_SPI_ReadWriteByte(0x00);  
  133.     VS_SPI_ReadWriteByte(0x00);  
  134.     VS_SPI_ReadWriteByte(0x00);  
  135.     VS_SPI_ReadWriteByte(0x00);  
  136.     delay_ms(100);  
  137.     VS_XDCS=1;  
  138.     //退出正弦测试  
  139.     VS_XDCS=0;//选中数据传输  
  140.     VS_SPI_ReadWriteByte(0x45);  
  141.     VS_SPI_ReadWriteByte(0x78);  
  142.     VS_SPI_ReadWriteByte(0x69);  
  143.     VS_SPI_ReadWriteByte(0x74);  
  144.     VS_SPI_ReadWriteByte(0x00);  
  145.     VS_SPI_ReadWriteByte(0x00);  
  146.     VS_SPI_ReadWriteByte(0x00);  
  147.     VS_SPI_ReadWriteByte(0x00);  
  148.     delay_ms(100);  
  149.     VS_XDCS=1;     
  150. }      
  151. //ram 测试   
  152. //返回值:RAM测试结果  
  153. // VS1003如果得到的值为0x807F,则表明完好;VS1053为0X83FF.                                                                                
  154. u16 VS_Ram_Test(void)  
  155. {   
  156.     VS_HD_Reset();       
  157.     VS_WR_Cmd(SPI_MODE,0x0820);// 进入VS10XX的测试模式  
  158.     while (VS_DQ==0); // 等待DREQ为高                
  159.     VS_SPI_SpeedLow();//低速   
  160.     VS_XDCS=0;                  // xDCS = 1,选择VS10XX的数据接口  
  161.     VS_SPI_ReadWriteByte(0x4d);  
  162.     VS_SPI_ReadWriteByte(0xea);  
  163.     VS_SPI_ReadWriteByte(0x6d);  
  164.     VS_SPI_ReadWriteByte(0x54);  
  165.     VS_SPI_ReadWriteByte(0x00);  
  166.     VS_SPI_ReadWriteByte(0x00);  
  167.     VS_SPI_ReadWriteByte(0x00);  
  168.     VS_SPI_ReadWriteByte(0x00);  
  169.     VS_XDCS=1;  
  170.     delay_ms(150);    
  171.   
  172.     return VS_RD_Reg(SPI_HDAT0);// VS1003如果得到的值为0x807F,则表明完好;VS1053为0X83FF.;         
  173. }                            
  174. //向VS10XX写命令  
  175. //address:命令地址  
  176. //data:命令数据  
  177. void VS_WR_Cmd(u8 address,u16 data)  
  178. {    
  179.     while(VS_DQ==0);//等待空闲          
  180.     VS_SPI_SpeedLow();//低速       
  181.     VS_XDCS=1;     
  182.     VS_XCS=0;      
  183.     VS_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令  
  184.     VS_SPI_ReadWriteByte(address); //地址  
  185.     VS_SPI_ReadWriteByte(data>>8); //发送高八位  
  186.     VS_SPI_ReadWriteByte(data);  //第八位  
  187.     VS_XCS=1;             
  188.     VS_SPI_SpeedHigh();//高速      
  189. }   
  190. //向VS10XX写数据  
  191. //data:要写入的数据  
  192. void VS_WR_Data(u8 data)  
  193. {  
  194.     VS_SPI_SpeedHigh();//高速,对VS1003B,最大值不能超过36.864/4Mhz,这里设置为9M   
  195.     VS_XDCS=0;     
  196.     VS_SPI_ReadWriteByte(data);  
  197.     VS_XDCS=1;        
  198. }           
  199. //读VS10XX的寄存器             
  200. //address:寄存器地址  
  201. //返回值:读到的值  
  202. //注意不要用倍速读取,会出错  
  203. u16 VS_RD_Reg(u8 address)  
  204. {   
  205.     u16 temp=0;        
  206.     while(VS_DQ==0);    //等待空闲       
  207.     VS_SPI_SpeedLow();//低速   
  208.     VS_XDCS=1;         
  209.     VS_XCS=0;          
  210.     VS_SPI_ReadWriteByte(VS_READ_COMMAND);  //发送VS10XX的读命令  
  211.     VS_SPI_ReadWriteByte(address);          //地址  
  212.     temp=VS_SPI_ReadWriteByte(0xff);        //读取高字节  
  213.     temp=temp<<8;  
  214.     temp+=VS_SPI_ReadWriteByte(0xff);       //读取低字节  
  215.     VS_XCS=1;       
  216.     VS_SPI_SpeedHigh();//高速     
  217.    return temp;   
  218. }    
  219. //读取VS10xx的RAM  
  220. //addr:RAM地址  
  221. //返回值:读到的值  
  222. u16 VS_WRAM_Read(u16 addr)   
  223. {   
  224.     u16 res;                    
  225.     VS_WR_Cmd(SPI_WRAMADDR, addr);   
  226.     res=VS_RD_Reg(SPI_WRAM);    
  227.     return res;  
  228. }   
  229. //设置播放速度(仅VS1053有效)   
  230. //t:0,1,正常速度;2,2倍速度;3,3倍速度;4,4倍速;以此类推  
  231. void VS_Set_Speed(u8 t)  
  232. {  
  233.     VS_WR_Cmd(SPI_WRAMADDR,0X1E04); //速度控制地址    
  234.     while(VS_DQ==0);                //等待空闲       
  235.     VS_WR_Cmd(SPI_WRAM,t);          //写入播放速度  
  236. }  
  237. //FOR WAV HEAD0 :0X7761 HEAD1:0X7665      
  238. //FOR MIDI HEAD0 :other info HEAD1:0X4D54  
  239. //FOR WMA HEAD0 :data speed HEAD1:0X574D  
  240. //FOR MP3 HEAD0 :data speed HEAD1:ID  
  241. //比特率预定值,阶层III  
  242. const u16 bitrate[2][16]=  
  243. {   
  244. {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,0},   
  245. {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}  
  246. };  
  247. //返回Kbps的大小  
  248. //返回值:得到的码率  
  249. u16 VS_Get_HeadInfo(void)  
  250. {  
  251.     unsigned int HEAD0;  
  252.     unsigned int HEAD1;    
  253.     HEAD0=VS_RD_Reg(SPI_HDAT0);   
  254.     HEAD1=VS_RD_Reg(SPI_HDAT1);  
  255.     //printf("(H0,H1):%x,%x\n",HEAD0,HEAD1);  
  256.     switch(HEAD1)  
  257.     {          
  258.         case 0x7665://WAV格式  
  259.         case 0X4D54://MIDI格式   
  260.         case 0X4154://AAC_ADTS  
  261.         case 0X4144://AAC_ADIF  
  262.         case 0X4D34://AAC_MP4/M4A  
  263.         case 0X4F67://OGG  
  264.         case 0X574D://WMA格式  
  265.         case 0X664C://FLAC格式  
  266.         {  
  267.             ////printf("HEAD0:%d\n",HEAD0);  
  268.             HEAD1=HEAD0*2/25;//相当于*8/100  
  269.             if((HEAD1%10)>5)return HEAD1/10+1;//对小数点第一位四舍五入  
  270.             else return HEAD1/10;  
  271.         }  
  272.         default://MP3格式,仅做了阶层III的表  
  273.         {  
  274.             HEAD1>>=3;  
  275.             HEAD1=HEAD1&0x03;   
  276.             if(HEAD1==3)HEAD1=1;  
  277.             else HEAD1=0;  
  278.             return bitrate[HEAD1][HEAD0>>12];  
  279.         }  
  280.     }    
  281. }  
  282. //得到平均字节数  
  283. //返回值:平均字节数速度  
  284. u32 VS_Get_ByteRate(void)  
  285. {  
  286.     return VS_WRAM_Read(0X1E05);//平均位速  
  287. }  
  288. //得到需要填充的数字  
  289. //返回值:需要填充的数字  
  290. u16 VS_Get_EndFillByte(void)  
  291. {  
  292.     return VS_WRAM_Read(0X1E06);//填充字节  
  293. }    
  294. //发送一次音频数据  
  295. //固定为32字节  
  296. //返回值:0,发送成功  
  297. //       1,VS10xx不缺数据,本次数据未成功发送      
  298. u8 VS_Send_MusicData(u8* buf)  
  299. {  
  300.     u8 n;  
  301.     if(VS_DQ!=0)  //送数据给VS10XX  
  302.     {                  
  303.         VS_XDCS=0;    
  304.         for(n=0;n<32;n++)  
  305.         {  
  306.             VS_SPI_ReadWriteByte(buf[n]);                 
  307.         }  
  308.         VS_XDCS=1;                       
  309.     }else return 1;  
  310.     return 0;//成功发送了  
  311. }  
  312. //切歌  
  313. //通过此函数切歌,不会出现切换“噪声”                  
  314. void VS_Restart_Play(void)  
  315. {  
  316.     u16 temp;  
  317.     u16 i;  
  318.     u8 n;       
  319.     u8 vsbuf[32];  
  320.     for(n=0;n<32;n++)vsbuf[n]=0;//清零  
  321.     temp=VS_RD_Reg(SPI_MODE);   //读取SPI_MODE的内容  
  322.     temp|=1<<3;                   //设置SM_CANCEL位  
  323.     temp|=1<<2;                   //设置SM_LAYER12位,允许播放MP1,MP2  
  324.     VS_WR_Cmd(SPI_MODE,temp);   //设置取消当前解码指令  
  325.     for(i=0;i<2048;)         //发送2048个0,期间读取SM_CANCEL位.如果为0,则表示已经取消了当前解码  
  326.     {  
  327.         if(VS_Send_MusicData(vsbuf)==0)//每发送32个字节后检测一次  
  328.         {  
  329.             i+=32;                      //发送了32个字节  
  330.             temp=VS_RD_Reg(SPI_MODE);   //读取SPI_MODE的内容  
  331.             if((temp&(1<<3))==0)break;    //成功取消了  
  332.         }     
  333.     }  
  334.     if(i<2048)//SM_CANCEL正常  
  335.     {  
  336.         temp=VS_Get_EndFillByte()&0xff;//读取填充字节  
  337.         for(n=0;n<32;n++)vsbuf[n]=temp;//填充字节放入数组  
  338.         for(i=0;i<2052;)  
  339.         {  
  340.             if(VS_Send_MusicData(vsbuf)==0)i+=32;//填充       
  341.         }         
  342.     }else VS_Soft_Reset();      //SM_CANCEL不成功,坏情况,需要软复位        
  343.     temp=VS_RD_Reg(SPI_HDAT0);   
  344.     temp+=VS_RD_Reg(SPI_HDAT1);  
  345.     if(temp)                    //软复位,还是没有成功取消,放杀手锏,硬复位  
  346.     {  
  347.         VS_HD_Reset();          //硬复位  
  348.         VS_Soft_Reset();        //软复位   
  349.     }   
  350. }  
  351. //重设解码时间                            
  352. void VS_Reset_DecodeTime(void)  
  353. {  
  354.     VS_WR_Cmd(SPI_DECODE_TIME,0x0000);  
  355.     VS_WR_Cmd(SPI_DECODE_TIME,0x0000);//操作两次  
  356. }  
  357. //得到mp3的播放时间n sec  
  358. //返回值:解码时长  
  359. u16 VS_Get_DecodeTime(void)  
  360. {         
  361.     u16 dt=0;      
  362.     dt=VS_RD_Reg(SPI_DECODE_TIME);        
  363.     return dt;  
  364. }                               
  365. //vs10xx装载patch.  
  366. //patch:patch首地址  
  367. //len:patch长度  
  368. void VS_Load_Patch(u16 *patch,u16 len)   
  369. {  
  370.     u16 i;   
  371.     u16 addr, n, val;                    
  372.     for(i=0;i<len;)   
  373.     {   
  374.         addr = patch[i++];   
  375.         n    = patch[i++];   
  376.         if(n & 0x8000U) //RLE run, replicate n samples   
  377.         {   
  378.             n  &= 0x7FFF;   
  379.             val = patch[i++];   
  380.             while(n--)VS_WR_Cmd(addr, val);    
  381.         }else //copy run, copy n sample   
  382.         {   
  383.             while(n--)   
  384.             {   
  385.                 val = patch[i++];   
  386.                 VS_WR_Cmd(addr, val);   
  387.             }   
  388.         }   
  389.     }     
  390. }               
  391. //设定VS10XX播放的音量和高低音  
  392. //volx:音量大小(0~254)  
  393. void VS_Set_Vol(u8 volx)  
  394. {  
  395.     u16 volt=0;             //暂存音量值  
  396.     volt=254-volx;          //取反一下,得到最大值,表示最大的表示   
  397.     volt<<=8;  
  398.     volt+=254-volx;         //得到音量设置后大小  
  399.     VS_WR_Cmd(SPI_VOL,volt);//设音量   
  400. }  
  401. //设定高低音控制  
  402. //bfreq:低频上限频率  2~15(单位:10Hz)  
  403. //bass:低频增益         0~15(单位:1dB)  
  404. //tfreq:高频下限频率  1~15(单位:Khz)  
  405. //treble:高频增益       0~15(单位:1.5dB,小于9的时候为负数)  
  406. void VS_Set_Bass(u8 bfreq,u8 bass,u8 tfreq,u8 treble)  
  407. {  
  408.     u16 bass_set=0; //暂存音调寄存器值  
  409.     signed char temp=0;        
  410.     if(treble==0)temp=0;            //变换  
  411.     else if(treble>8)temp=treble-8;  
  412.     else temp=treble-9;    
  413.     bass_set=temp&0X0F;             //高音设定  
  414.     bass_set<<=4;  
  415.     bass_set+=tfreq&0xf;            //高音下限频率  
  416.     bass_set<<=4;  
  417.     bass_set+=bass&0xf;             //低音设定  
  418.     bass_set<<=4;  
  419.     bass_set+=bfreq&0xf;            //低音上限      
  420.     VS_WR_Cmd(SPI_BASS,bass_set);   //BASS   
  421. }  
  422. //设定音效  
  423. //eft:0,关闭;1,最小;2,中等;3,最大.  
  424. void VS_Set_Effect(u8 eft)  
  425. {  
  426.     u16 temp;      
  427.     temp=VS_RD_Reg(SPI_MODE);   //读取SPI_MODE的内容  
  428.     if(eft&0X01)temp|=1<<4;       //设定LO  
  429.     else temp&=~(1<<5);           //取消LO  
  430.     if(eft&0X02)temp|=1<<7;       //设定HO  
  431.     else temp&=~(1<<7);           //取消HO                           
  432.     VS_WR_Cmd(SPI_MODE,temp);   //设定模式      
  433. }       
  434. ///////////////////////////////////////////////////////////////////////////////  
  435. //设置音量,音效等.  
  436. void VS_Set_All(void)                 
  437. {              
  438.     VS_Set_Vol(vsset.mvol);  
  439.     VS_Set_Bass(vsset.bflimit,vsset.bass,vsset.tflimit,vsset.treble);    
  440.     VS_Set_Effect(vsset.effect);  
  441. }  

六:录音代码
#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;
}



你可能感兴趣的:(mp3,录音,stm32,wav,VS1053)