该函数就是用我们前面介绍的方法,激活VS1053的PCM模式,本章,我们使用的是8Khz采样率,16位单声道线性PCM模式,AGC通过函数参数设置。最后加载patch(用于修复VS1053录音BUG)。 第二个函数是初始化wav头的函数:recoder_wav_init,该函数代码如下: //初始化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; //数据大小,还需要计算 } 该函数初始化wav头的绝大部分数据,这里我们设置了该wav文件为8Khz采样率,16位线性PCM格式,另外由于录音还未真正开始,所以文件大小和数据大小都还是未知的,要等录音结束才能知道。该函数__WaveHeader结构体就是由前面介绍的三个Chunk组成,结构为: //wav头 typedef __packed struct { ChunkRIFF riff; //riff块 ChunkFMT fmt; //fmt块 //ChunkFACT fact; //fact块 线性PCM,没有这个结构体 ChunkDATA data; //data块 }__WaveHeader;
最后,我们介绍recoder_play函数,是录音机实现的主循环函数,该函数代码如下: //录音机 //所有录音文件,均保存在SD卡RECORDER文件夹内. u8 recoder_play(void) { u8 res, key, rval=0; __WaveHeader *wavhead=0; u32 sectorsize=0;u16 w; u16 idx=0; FIL* f_rec=0; //文件 DIR recdir; //目录 u8 *recbuf; //数据内存 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 while(f_opendir(&recdir,"0:/RECORDER"))//打开录音文件夹 { Show_Str(60,230,240,16,"RECORDER文件夹错误!",16,0); delay_ms(200); LCD_Fill(60,230,240,246,WHITE); delay_ms(200); //清除显示 f_mkdir("0:/RECORDER");//创建该目录 } f_rec=(FIL *)mymalloc(SRAMIN,sizeof(FIL)); //开辟FIL字节的内存区域 if(f_rec==NULL)rval=1; //申请失败 wavhead=(__WaveHeader*)mymalloc(SRAMIN,sizeof(__WaveHeader)); //开辟__WaveHeader字节的内存区域 if(wavhead==NULL)rval=1; recbuf=mymalloc(SRAMIN,512); if(recbuf==NULL)rval=1; pname=mymalloc(SRAMIN,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 KEY_LEFT: //STOP&SAVE 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 LCD_Fill(60,230,240,246,WHITE); //清除显示,清除之前显示的录音文件名 recoder_show_time(recsec); //显示时间 break; case KEY_RIGHT: //REC/PAUSE if(rec_sta&0X01)//原来是暂停,继续录音 { rec_sta&=0XFE;//取消暂停 }else if(rec_sta&0X80)//已经在录音了,暂停 { rec_sta|=0X01; //暂停 }else //还没开始录音 { rec_sta|=0X80; //开始录音 recoder_new_pathname(pname); //得到新的名字 Show_Str(60,230,240,16,pname+11,16,0); //显示录音文件名字 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);//写入头数据 } if(rec_sta&0X01)LED1=0; //提示正在暂停 else LED1=1; break; case KEY_UP: //AGC+ case KEY_DOWN: //AGC- if(key==KEY_UP)recagc++; else if(recagc)recagc--; if(recagc>15)recagc=15; //范围限定为0~15,自动AGC.其他AGC倍数 recoder_show_agc(recagc); VS_WR_Cmd(SPI_AICTRL1,1024*recagc); //设置增益,0,自动增益.1024相当于1倍,512相当于0.5倍 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) break;//写入出错. sectorsize++;//扇区数增加1,约为32ms } }else//没有开始录音,则检测TPAD按键 { if(TPAD_Scan(0)&&pname[0])//如果触摸按键被按下,且pname不为空 { Show_Str(60,230,240,16,"播放:",16,0); Show_Str(60+40,230,240,16,pname+11,16,0);//显示播放的文件名字 rec_play_wav(pname); 播放pname LCD_Fill(60,230,240,246,WHITE); /清除之前显示的录音文件名 recoder_enter_rec_mode(1024*recagc); //重新进入录音模式 while(VS_RD_Reg(SPI_HDAT1)>>8); //等到buf 较为空闲再开始 recoder_show_time(recsec); //显示时间 recoder_show_agc(recagc); //显示agc } delay_ms(5); timecnt++; if((timecnt%20)==0)LED0=!LED0;//DS0闪烁 } if(recsec!=(sectorsize*4/125))//录音时间显示 { LED0=!LED0;//DS0闪烁 recsec=sectorsize*4/125; recoder_show_time(recsec);//显示时间 } } } myfree(SRAMIN,wavhead); myfree(SRAMIN,recbuf); myfree(SRAMIN,f_rec); myfree(SRAMIN,pname); return rval; } 该函数实现了我们在硬件设计时介绍的功能,我们就不详细介绍了。recorder.c的其他代码和recorder.h的代码我们这里就不再贴出了,请大家参考光盘本实验的源码。保存recorder.c,最后,我们在test.c里面修改main函数如下: int main(void) { Stm32_Clock_Init(9); //系统时钟设置 delay_init(72); //延时初始化 uart_init(72,9600); //串口1初始化 LCD_Init(); //初始化液晶 LED_Init(); //LED初始化 KEY_Init(); //按键初始化 TPAD_Init(72); //初始化触摸按键 Audiosel_Init(); //初始化音源选择 usmart_dev.init(72); //usmart初始化 mem_init(SRAMIN); //初始化内部内存池 VS_Init(); exfuns_init(); //为fatfs相关变量申请内存 f_mount(0,fs[0]); //挂载SD卡 f_mount(1,fs[1]); //挂载FLASH. POINT_COLOR=RED; while(font_init()) //检查字库 { LCD_ShowString(60,50,200,16,16,"Font Error!"); delay_ms(200); LCD_Fill(60,50,240,66,WHITE);//清除显示 } Show_Str(60,50,200,16,"战舰 STM32开发板",16,0); Show_Str(60,70,200,16,"WAV录音机实验",16,0); Show_Str(60,90,200,16,"广州星翼电子",16,0); Show_Str(60,110,200,16,"2012年9月20日",16,0); Show_Str(60,130,200,16,"KEY0:REC/PAUSE",16,0); Show_Str(60,150,200,16,"KEY2:STOP&SAVE",16,0); Show_Str(60,170,200,16,"KEY_UP:AGC+ KEY1:AGC-",16,0); Show_Str(60,190,200,16,"TPAD:Play The File",16,0); while(1) { Audiosel_Set(0); //MP3通道 Show_Str(60,210,200,16,"存储器测试...",16,0); VS_Ram_Test(); Show_Str(60,210,200,16,"正弦波测试...",16,0); VS_Sine_Test(); Show_Str(60,210,200,16,"<<WAV录音机>>",16,0); recoder_play(); } } 该函数代码同上一章的main函数代码几乎一样,只是我们这里增加了TPAD初始化,然后修改了一些显示内容,其他两者就都差不多了,我们就不再细说了。 至此,本实验的软件设计部分结束。 50.4 下载验证 在代码编译成功之后,我们下载代码到ALIENTEK战舰STM32开发板上,程序先检测字库,然后对VS1053进行RAM测试和正弦测试,之后检测SD卡的RECORDER文件夹,一切顺利通过之后,激活VS1053的PCM录音模式,得到,如图50.4.1所示:
图50.4.1 录音机界面
此时,我们按下KEY0就开始录音了,此时看到屏幕显示录音文件的名字以及录音时长,如图50.4.2所示:
图50.4.2 录音进行中
在录音的时候按下KEY0则执行暂停/继续录音的切换,通过DS1指示录音暂停,按WK_UP和KEY1可以调节AGC,AGC越大,越灵敏,不过不建议设置太大,因为这可能导致失真。通过按下KEY2,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通过按TPAD按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。 我们将开发板的录音文件放到电脑上面,可以通过属性查看录音文件的属性,如图50.4.3所示:
图50.4.3 录音文件属性
这和我们预期的效果一样,通过电脑端的播放器(winamp/千千静听等)可以直接播放我们所录的音频。经实测,效果还是非常不错的。 |