第四十九章 录音机实验
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0[/mw_shl_code]
上一章,我们实现了一个简单的音乐播放器,本章我们将在上一章的基础上,实现一个简单的录音机,实现WAV录音。本章分为如下几个部:
49.1 I2S录音简介
49.2 硬件设计
49.3 软件设计
49.4 下载验证
49.1 I2S录音简介
本章涉及的知识点基本上在上一章都有介绍。本章要实现WAV录音,还是和上一章一样,要了解:WAV文件格式、WM8978和I2S。WAV文件格式,我们在上一章已经做了详细介绍了,这里就不作介绍了。
ALIENTEK探索者STM32F4开发板将板载的一个MIC分别接入到了WM8978的2个差分输入通道(LIP/LIN和RIP/RIN,原理图见:图48.2.1)。代码上,我们采用立体WAV声录音,不过,左右声道的音源都是一样的,录音出来的WAV文件,听起来就是个单声道效果。
WM8978上一章也做了比较详细的介绍,本章我们主要看一下要进行MIC录音,WM8978的配置步骤:
1,寄存器R0(00h),该寄存器用于控制WM8978的软复位,写任意值到该寄存器地址,即可实现软复位WM8978。
2,寄存器R1(01h),该寄存器主要要设置MICBEN(bit4)和BIASEN(bit3)两个位为1,开启麦克风(MIC)偏置,以及使能模拟部分放大器。
3,寄存器R2(02h),该寄存器要设置SLEEP(bit6)、INPGAENR(bit3)、INPGAENL(bit2)、ADCENR(bit1)和ADCENL(bit0)等五个位。SLEEP设置为0,进入正常工作模式;INPGAENR和INPGAENL设置为1,使能IP PGA放大器;ADCENL和ADCENR设置为1,使能左右通道ADC。
4,寄存器R4(04h),该寄存器要设置WL(bit6:5)和FMT(bit4:3)等4个位。WL(bit6:5)用于设置字长(即设置音频数据有效位数),00表示16位音频,10表示24位音频;FMT(bit4:3)用于设置I2S音频数据格式(模式),我们一般设置为10,表示I2S格式,即飞利浦模式。
5,寄存器R6(06h),该寄存器我们直接全部设置为0即可,设置MCLK和BCLK都来自外部,即由STM32F4提供。
6,寄存器R14(0Eh),该寄存器要设置ADCOSR128(bit3)为1,ADC得到最好的SNR。
7,寄存器R44(2Ch),该寄存器我们要设置LIP2INPPGA(bit0)、LIN2INPPGA(bit1)、RIP2INPPGA(bit4)和RIN2INPPGA(bit5)等4个位,将这4个位都设置为1,将左右通道差分输入接入IN PGA。
ADCOSR128(bit3)为1,ADC得到最好的SNR。
8,寄存器R45(2Dh)和R46(2Eh),这两个寄存器用于设置PGA增益(调节麦克风增益),一个用于设置左通道(R45),另外一个用于设置右通道(R46)。这两个寄存器的最高位(INPPGAUPDATE)用于设置是否更新左右通道的增益,最低6位用于设置左右通道的增益,我们可以先设置好两个寄存器的增益,最后设置其中一个寄存器最高位为1,即可更新增益设置。
9,寄存器R47(2Fh)和R48(30h),这两个寄存器也类似,我们只关心其最高位(bit8),都设置为1,可以让左右通道的MIC各获得20dB的增益。
10,寄存器R49(31h),该寄存器我们要设置TSDEN(bit1)这个位,设置为1,开启过热保护。
以上,就是我们用WM8978录音时的设置,按照以上所述,对各个寄存器进行相应的配置,即可使用WM8978正常录音了。不过我们本章还要用到播放录音的功能,WM8978的播放配置在48.1.2节已经介绍过了,请大家参考这个章节。
上一章我们向大家介绍了STM32F4的I2S放音,通过上一章的了解,我们知道:STM32F4的全双工需要用到扩展的I2Sx_ext(x=2/3),和I2Sx组成全双工I2S。在全双工模式下,I2Sx向I2Sx_ext提供CK和WS时钟信号。
本章我们必须向WM8978提供WS,CK和MCK等时钟,同时又要录音,所以只能使用全双工模式。主I2Sx循环发送数据0X0000,给WM8978,以产生CK、WS和MCK等信号,从I2Sx_ext,则接收来自WM8978的ADC数据(I2Sxext_SD),并保存到SD卡,实现录音。
本章我们还是采用I2S2的全双工模式来录音,I2S2的相关寄存器,我们在上一章已经介绍的差不多了。至于I2S2ext的寄存器,则有一套和I2S2一样的寄存器,不过仅仅少数几个对我们有用,他们是:I2S2ext_I2SCFGR、I2S2ext_CR2和I2S2ext_DR,这三个寄存器对应为的功能和描述,完全同I2S2。寄存器描述,我们这里就不再介绍了,大家可以看前面章节,也可以看《STM32F4xx中文参考手册》第27.5节。
最后,我们看看要通过STM32F4的I2S,驱动WM8978实现WAV录音的简要步骤。这一章用到的硬件部分知识点实际上一章节已经讲解,这里我们主要讲解一下步骤:
1)初始化WM8978
这个过程就是前面所讲的WM8978 MIC录音配置步骤,让WM8978的ADC以及其模拟部分工作起来。
2)初始化I2S2和I2S2ext
本章要用到I2S2的全双工模式,所以,I2S2和I2S2ext都需要配置,其中I2S2配置为主机发送模式,I2S2ext设置为从机接收模式。他们的其他配置(I2S标准、时钟空闲电平和数据帧长)基本一样,只是一个是发送一个是接收,且都要使能DMA。同时,还需要设置音频采样率,不过这个只需要设置I2S2即可,还是通过上一章介绍的查表法设置。
3)设置发送和接收DMA
放音和录音都是采用DMA传输数据的,本章放音起始就是个幌子,不过也得设置DMA(使用DMA1数据流4的通道0),配置同上一章一模一样,不过不需要开启DMA传输完成中断。对于录音,则使用的是DMA1数据流3的通道3实现的DMA数据接收,我们需要配置DMA1的数据流3,本章将DMA1数据流3设置为:双缓冲循环模式,外设和存储器都是16位宽,并开启DMA传输完成中断(方便接收数据)。
4)编写接收通道DMA传输完成中断服务函数
为了方便接收音频数据,我们使用DMA传输完成中断,每当一个缓冲接数据满了,硬件自动切换为下一个缓冲,同时进入中断服务函数,将已满缓冲的数据写入SD卡的wav文件。过程如图49.1.1所示:
图49.1.1 DMA双缓冲接收音频数据流框图
5)创建WAV文件,并保存wav头
前面4步完成,其实就可以开始读取音频数据了,不过在录音之前,我们需要先在创建一个新的文件,并写入wav头,然后才能开始写入我们读取到的的PCM音频数据。
6)开启DMA传输,接收数据
然后,我们就只需要开启DMA传输,然后及时将I2S2ext读到的数据写入到SD卡之前新建的wav文件里面,就可以实现录音了。
7)计算整个文件大小,重新保存wav头并关闭文件
在结束录音的时候,我们必须知道本次录音的大小(数据大小和整个文件大小),然后更新wav头,重新写入文件,最后因为FATFS,在文件创建之后,必须调用f_close,文件才会真正体现在文件系统里面,否则是不会写入的!所以最后还需要调用f_close,以保存文件。
49.2 硬件设计
本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,再检测SD卡根目录是否存在RECORDER文件夹,如果不存在则创建,如果创建失败,则报错。在找到SD卡的RECORDER文件夹后,即进入录音模式(包括配置WM8978和I2S等),此时可以在耳机(或喇叭)听到采集到的音频。KEY0用于开始/暂停录音,KEY2用于保存并停止录音,KEY_UP用于播放最近一次的录音。
当我们按下KEY0的时候,可以在屏幕上看到录音文件的名字、码率以及录音时间等,然后通过KEY2可以保存该文件,同时停止录音(文件名和时间也都将清零),在完成一段录音后,我们可以通过按KEY_UP按键,来试听刚刚的录音。DS0用于提示程序正在运行,DS1用于提示是否处于暂停录音状态。
本实验用到的资源如下:
1) 指示灯DS0,DS1
2) 三个按键(KEY_UP/KEY0/KEY2)
3) 串口
4) TFTLCD模块
5) SD卡
6) SPI FLASH
7) WM8978
8) I2S2
这些前面都已介绍过。本实验,大家需要准备1个SD卡和一个耳机(或喇叭),分别插入SD卡接口和耳机接口(喇叭接P1接口),然后下载本实验就可以实现一个简单的录音机了。
49.3 软件设计
打开本章实验工程可以看到,相比上一章实验,我们主要在APP分组下面新增了recorder.c和recorder.h两个文件。
因为recorder.c代码比较多,我们这里仅介绍其中几个重要的函数,代码如下:
u8 *i2srecbuf1; //录音buf1
u8 *i2srecbuf2; //录音buf2
FIL* f_rec=0; //录音文件
u32 wavsize; //wav数据大小(字节数,不包括文件头!!)
u8 rec_sta=0; //录音状态
//[7]:0,没有开启录音;1,已经开启录音;
//[6:1]:保留
//[0]:0,正在录音;1,暂停录音;
//录音 I2S_DMA接收中断服务函数.在中断里面写入数据
void rec_i2s_dma_rx_callback(void)
{
u16
bw; u8 res;
if(rec_sta==0X80)//录音模式
{
if(DMA1_Stream3->CR&(1<<19))
{
res=f_write(f_rec,i2srecbuf1,I2S_RX_DMA_BUF_SIZE,(UINT*)&bw);//写文件
if(res)
printf("write error:%d\r\n",res);
}else
{
res=f_write(f_rec,i2srecbuf2,I2S_RX_DMA_BUF_SIZE,(UINT*)&bw);//写文件
if(res)
printf("write error:%d\r\n",res);
}
wavsize+=I2S_RX_DMA_BUF_SIZE;
}
}
const u16 i2splaybuf[2]={0X0000,0X0000};//2个16位数据,用于录音时I2S2主机循环发送.
//进入PCM 录音模式
void recoder_enter_rec_mode(void)
{
WM8978_ADDA_Cfg(0,1); //开启ADC
WM8978_Input_Cfg(1,1,0); //开启输入通道(MIC&LINE
IN)
WM8978_Output_Cfg(0,1); //开启BYPASS输出
WM8978_MIC_Gain(46); //MIC增益设置
WM8978_I2S_Cfg(2,0); //飞利浦标准,16位数据长度
I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,
I2S_DataFormat_16b);//飞利浦标准,主机发送,时钟低电平有效,16位帧长度
I2S2ext_Init(I2S_Standard_Phillips,I2S_Mode_SlaveRx,I2S_CPOL_Low,
I2S_DataFormat_16b);//飞利浦标准,从机接收,时钟低电平有效,16位帧长度
I2S2_SampleRate_Set(16000); //设置采样率
I2S2_TX_DMA_Init((u8*)&i2splaybuf[0],(u8*)&i2splaybuf[1],1);
//配置TX DMA
DMA1_Stream4->CR&=~(1<<4); //关闭传输完成中断(这里不用中断送数据)
I2S2ext_RX_DMA_Init(i2srecbuf1,i2srecbuf2,I2S_RX_DMA_BUF_SIZE/2);//RX
DMA
i2s_rx_callback=rec_i2s_dma_rx_callback;//回调函数指wav_i2s_dma_callback
I2S_Play_Start(); //开始I2S数据发送(主机)
I2S_Rec_Start();
//开始I2S数据接收(从机)
recoder_remindmsg_show(0);
} //进入PCM 放音模式
void recoder_enter_play_mode(void)
{
WM8978_ADDA_Cfg(1,0); //开启DAC
WM8978_Input_Cfg(0,0,0); //关闭输入通道(MIC&LINE
IN)
WM8978_Output_Cfg(1,0); //开启DAC输出
WM8978_MIC_Gain(0); //MIC增益设置为0
I2S_Play_Stop(); //停止时钟发送
I2S_Rec_Stop();
//停止录音
recoder_remindmsg_show(1);
}
//初始化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=2; //双声道
wavhead->fmt.SampleRate=16000; //16Khz采样率 采样速率
wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*4;
//字节速率=采样率*通道数*(ADC位数/8)
wavhead->fmt.BlockAlign=4; //块大小=通道数*(ADC位数/8)
wavhead->fmt.BitsPerSample=16; //16位PCM
wavhead->data.ChunkID=0X61746164;//"data"
wavhead->data.ChunkSize=0; //数据大小,还需要计算
}
//WAV录音
void wav_recorder(void)
{
u8
res; u8 key; u8 rval=0;
__WaveHeader
*wavhead=0;
DIR recdir;
//目录
u8 *pname=0;
u8
timecnt=0; //计时器
u32
recsec=0; //录音时间
while(f_opendir(&recdir,"0:/RECORDER"))//打开录音文件夹
{
Show_Str(30,230,240,16,"RECORDER文件夹错误!",16,0); delay_ms(200);
LCD_Fill(30,230,240,246,WHITE);
delay_ms(200);//清除显示
f_mkdir("0:/RECORDER"); //创建该目录
}
i2srecbuf1=mymalloc(SRAMIN,I2S_RX_DMA_BUF_SIZE);//I2S录音内存1申请
i2srecbuf2=mymalloc(SRAMIN,I2S_RX_DMA_BUF_SIZE);//I2S录音内存2申请
f_rec=(FIL *)mymalloc(SRAMIN,sizeof(FIL)); //开辟FIL字节的内存区域
wavhead=(__WaveHeader*)mymalloc(SRAMIN,sizeof(__WaveHeader));//开辟内存
pname=mymalloc(SRAMIN,30);//申请30字节内存
if(!i2srecbuf1||!i2srecbuf2
||!f_rec ||!wavhead ||!pname)rval=1;
if(rval==0)
{
recoder_enter_rec_mode(); //进入录音模式,此时耳机可以听到咪头采集到的音频
pname[0]=0; //pname没有任何文件名
while(rval==0)
{
key=KEY_Scan(0);
switch(key)
{
case
KEY2_PRES: //STOP&SAVE
if(rec_sta&0X80)//有录音
{
rec_sta=0; //关闭录音
wavhead->riff.ChunkSize=wavsize+36; //整个文件的大小-8;
wavhead->data.ChunkSize=wavsize; //数据大小
f_lseek(f_rec,0); //偏移到文件头.
f_write(f_rec,(const
void*)wavhead,sizeof(__WaveHeader)
,&bw);//写入头数据
f_close(f_rec);
wavsize=0;
}
rec_sta=0;
recsec=0;
LED1=1; //关闭DS1
LCD_Fill(30,190,lcddev.width,lcddev.height,WHITE);//清除显示 break;
case
KEY0_PRES: //REC/PAUSE
if(rec_sta&0X01)
rec_sta&=0XFE;//原来是暂停,继续录音
else
if(rec_sta&0X80) rec_sta|=0X01;//已经在录音了,暂停
else//还没开始录音
{
recsec=0;
recoder_new_pathname(pname); //得到新的名字
Show_Str(30,190,lcddev.width,16,"录制:",16,0);
Show_Str(30+40,190,lcddev.width,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);//写入头数据
recoder_msg_show(0,0);
rec_sta|=0X80; //开始录音
}
}
if(rec_sta&0X01)LED1=0; //提示正在暂停
else
LED1=1;
break;
case
WKUP_PRES: //播放最近一段录音
if(rec_sta!=0X80)//没有在录音
{
if(pname[0])//如果触摸按键被按下,且pname不为空
{
Show_Str(30,190,lcddev.width,16,"播放:",16,0);
Show_Str(30+40,190,lcddev.width,16,pname+11,16,0);//名字
recoder_enter_play_mode(); //进入播放模式
audio_play_song(pname); //播放pname
LCD_Fill(30,190,lcddev.width,lcddev.height,WHITE);//清除
recoder_enter_rec_mode(); //重新进入录音模式
}
}
break;
}
delay_ms(5);
timecnt++;
if((timecnt%20)==0)LED0=!LED0;//DS0闪烁
if(recsec!=(wavsize/wavhead->fmt.ByteRate)) //录音时间显示
{
LED0=!LED0;//DS0闪烁
recsec=wavsize/wavhead->fmt.ByteRate; //录音时间
recoder_msg_show(recsec,wavhead->fmt.SampleRate*wavhead->fmt.
NumOfChannels*wavhead->fmt.BitsPerSample);//显示码率
}
}
}
myfree(SRAMIN,i2srecbuf1); //释放内存
myfree(SRAMIN,i2srecbuf2); //释放内存
myfree(SRAMIN,f_rec); //释放内存
myfree(SRAMIN,wavhead); //释放内存
myfree(SRAMIN,pname); //释放内存
}
这里总共5个函数,其中:rec_i2s_dma_rx_callback函数,用于I2S2ext的DMA接收完成中断回调函数(通过i2s_rx_callback指向该函数实现),在该函数里面,实现音频数据的保存。recoder_enter_rec_mode函数,用于设置WM8978和I2S进入录音模式(开始录音时用到)。recoder_enter_play_mode函数,则用于设置WM8978和I2S进入播放模式(录音回放时用到)。recoder_wav_init函数,该函数初始化wav头的绝大部分数据,这里我们设置了该wav文件为16Khz采样率,16位线性PCM格式,另外由于录音还未真正开始,所以文件大小和数据大小都还是未知的,要等录音结束才能知道。该函数__WaveHeader结构体就是由上一章(48.1.1节)介绍的三个Chunk组成,结构为:
//wav头
typedef __packed struct
{
ChunkRIFF
riff; //riff块
ChunkFMT
fmt; //fmt块
// ChunkFACT
fact; //fact块 线性PCM,没有这个结构体
ChunkDATA
data; //data块
}__WaveHeader;
最后,wav_recorder函数,实现了我们在硬件设计时介绍的功能(开始/暂停录音、保存录音文件、播放最近一次录音等)。该函数使用上一章实现的audio_play_song函数,来播放最近一次录音。recorder.c的其他代码和recorder.h的代码我们这里就不再贴出了,请大家参考光盘本实验的源码。
然后,我们在i2s.c里面也增加了几个函数,如下:
//I2S2ext配置
//参数I2S_Standard: @ref SPI_I2S_Standard I2S标准,
//参数I2S_Mode: @ref SPI_I2S_Mode 模式
//参数I2S_Clock_Polarity @ref SPI_I2S_Clock_Polarity:
//参数I2S_DataFormat:@ref SPI_I2S_Data_Format
Void I2S2ext_Init(u16 I2S_Standard,u16 I2S_Mode,
u16 I2S_Clock_Polarity,u16 I2S_DataFormat)
{
I2S_InitTypeDef
I2S2ext_InitStructure;
I2S2ext_InitStructure.I2S_Mode=I2S_Mode^(1<<8);//IIS模式
I2S2ext_InitStructure.I2S_Standard=I2S_Standard;//IIS标准
I2S2ext_InitStructure.I2S_DataFormat=I2S_DataFormat;//IIS数据长度
I2S2ext_InitStructure.I2S_MCLKOutput=I2S_MCLKOutput_Disable;//主时钟输出禁止
I2S2ext_InitStructure.I2S_AudioFreq=I2S_AudioFreq_Default;//IIS频率设置
I2S2ext_InitStructure.I2S_CPOL=I2S_Clock_Polarity;//空闲状态时钟电平
I2S_FullDuplexConfig(I2S2ext,&I2S2ext_InitStructure);//初始化I2S2ext配置
SPI_I2S_DMACmd(I2S2ext,SPI_I2S_DMAReq_Rx,ENABLE);//I2S2ext DMA请求使能.
I2S_Cmd(I2S2ext,ENABLE); //I2S2ext I2S EN使能.
}
//I2S2ext RX DMA配置
//设置为双缓冲模式,并开启DMA传输完成中断
//buf0:M0AR地址.
//buf1:M1AR地址.
//num:每次传输数据量
void I2S2ext_RX_DMA_Init(u8* buf0,u8 *buf1,u16
num)
{
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
DMA_DeInit(DMA1_Stream3);
while (DMA_GetCmdStatus(DMA1_Stream3)
!= DISABLE){}//等待可配置
DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_FEIF3|DMA_IT_DMEIF3|
DMA_IT_TEIF3|DMA_IT_HTIF3|DMA_IT_TCIF3);
//清空DMA1_Stream3 上所有中断标志
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_3; //通道3 I2S2ext_RX通道
DMA_InitStructure.DMA_PeripheralBaseAddr =
(u32)&I2S2ext->DR;//外设地址
DMA_InitStructure.DMA_Memory0BaseAddr =
(u32)buf0;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR =
DMA_DIR_PeripheralToMemory;//外设到存储器模式
DMA_InitStructure.DMA_BufferSize = num;//数据传输量
DMA_InitStructure.DMA_PeripheralInc =
DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc =
DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize
= DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode =
DMA_Mode_Circular;// 使用循环模式
DMA_InitStructure.DMA_Priority =
DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_FIFOMode =
DMA_FIFOMode_Disable; //不使用FIFO模式
DMA_InitStructure.DMA_FIFOThreshold =
DMA_FIFOThreshold_1QuarterFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst =
DMA_PeripheralBurst_Single;
DMA_Init(DMA1_Stream3, &DMA_InitStructure);//初始化DMA Stream
DMA_DoubleBufferModeConfig(DMA1_Stream3,(u32)buf1,DMA_Memory_0);
//双缓冲模式配置
DMA_DoubleBufferModeCmd(DMA1_Stream3,ENABLE);//双缓冲模式开启
DMA_ITConfig(DMA1_Stream3,DMA_IT_TC,ENABLE);//开启传输完成中断
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
=0x00;//抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority
= 0x01;//响应优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd =
ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}
void (*i2s_rx_callback)(void); //RX回调函数
//DMA1_Stream3中断服务函数
void DMA1_Stream3_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3)==SET)//传输完成标志
{
DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3); //清除传输完成中断
i2s_rx_callback(); //执行回调函数,读取数据等操作在这里面处理
}
}
//I2S开始录音
void I2S_Rec_Start(void)
{
DMA_Cmd(DMA1_Stream3,ENABLE);//开启DMA TX传输,开始录音
}
//关闭I2S录音
void I2S_Rec_Stop(void)
{
DMA_Cmd(DMA1_Stream3,DISABLE);//关闭DMA,结束录音
}
这里也是5个函数,I2S2ext _Init函数完成I2S2ext的初始化,通过4个参数设置I2S2ext的详细配置信息。I2S2ext_RX_DMA_Init函数,用于设置I2S2ext的DMA接收,使用双缓冲循环模式,接收来自WM8978的数据,并开启了传输完成中断。而DMA1_Stream3_IRQHandler函数,则是DMA1数据流3传输完成中断的服务函数,该函数调用i2s_rx_callback函数(函数指针,使用前需指向特定函数)实现DMA数据接收保存。最后,I2S_ Rec_Start和I2S_ Rec_Stop,用于开启和关闭DMA传输。
其他代码,我们就不再介绍了,请大家参考开发板光盘本例程源码。最后,我们看看主函数代码:
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
LED_Init(); //初始化LED
usmart_dev.init(84); //初始化USMART
LCD_Init(); //LCD初始化
KEY_Init(); //按键初始化
W25QXX_Init(); //初始化W25Q128
WM8978_Init(); //初始化WM8978
WM8978_HPvol_Set(40,40); //耳机音量设置
WM8978_SPKvol_Set(50); //喇叭音量设置
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMCCM); //初始化CCM内存池
exfuns_init(); //为fatfs相关变量申请内存
f_mount(fs[0],"0:",1);
//挂载SD卡
POINT_COLOR=RED;
while(font_init()) //检查字库
{
LCD_ShowString(30,40,200,16,16,"Font
Error!");
delay_ms(200);
LCD_Fill(30,40,240,66,WHITE);//清除显示
delay_ms(200);
}
POINT_COLOR=RED;
Show_Str(30,40,200,16,"Explorer
STM32开发板",16,0);
Show_Str(30,60,200,16,"录音机实验",16,0);
Show_Str(30,80,200,16,"正点原子@ALIENTEK",16,0);
Show_Str(30,100,200,16,"2014年6月6日",16,0);
while(1)
{
wav_recorder();
}
}
该函数代码同上一章的main函数代码几乎一样,十分简单,我们就不再多说了。
至此,本实验的软件设计部分结束。
49.4 下载验证
在代码编译成功之后,我们下载代码到ALIENTEK探索者STM32F4开发板上,程序先检测字库,然后检测SD卡的RECORDER文件夹,一切顺利通过之后,进入录音模式,得到,如图49.4.1所示:
图49.4.1 录音机界面
此时,我们按下KEY0就开始录音了,此时看到屏幕显示录音文件的名字、码率以及录音时长,如图49.4.2所示:
图49.4.2 录音进行中
在录音的时候按下KEY0则执行暂停/继续录音的切换,通过DS1指示录音暂停。通过按下KEY2,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通过按KEY_UP按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。
我们将开发板的录音文件放到电脑上面,可以通过属性查看录音文件的属性,如图49.4.3所示:
图49.4.3 录音文件属性
这和我们预期的效果一样,通过电脑端的播放器(winamp/千千静听等)可以直接播放我们所录的音频。经实测,效果还是非常不错的。
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779
|