OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 9641|回复: 4

【正点原子探索者STM32F407开发板例程连载+教学】第49章 录音机实验

[复制链接]

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-12-9 12:31:50 | 显示全部楼层 |阅读模式

第四十九章 录音机实验

 

[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文件格式、WM8978I2SWAV文件格式,我们在上一章已经做了详细介绍了,这里就不作介绍了。

ALIENTEK探索者STM32F4开发板将板载的一个MIC分别接入到了WM89782个差分输入通道(LIP/LINRIP/RIN,原理图见:图48.2.1)。代码上,我们采用立体WAV声录音,不过,左右声道的音源都是一样的,录音出来的WAV文件,听起来就是个单声道效果。

WM8978上一章也做了比较详细的介绍,本章我们主要看一下要进行MIC录音,WM8978的配置步骤:

1,寄存器R000h),该寄存器用于控制WM8978的软复位,写任意值到该寄存器地址,即可实现软复位WM8978

2,寄存器R101h),该寄存器主要要设置MICBEN(bit4)BIASEN(bit3)两个位为1,开启麦克风(MIC)偏置,以及使能模拟部分放大器。

3,寄存器R202h),该寄存器要设置SLEEP(bit6)INPGAENR(bit3)INPGAENL(bit2)ADCENR(bit1)ADCENL(bit0)等五个位。SLEEP设置为0,进入正常工作模式;INPGAENRINPGAENL设置为1,使能IP PGA放大器;ADCENLADCENR设置为1,使能左右通道ADC

4,寄存器R404h),该寄存器要设置WL(bit6:5)FMT(bit4:3)4个位。WL(bit6:5)用于设置字长(即设置音频数据有效位数),00表示16位音频,10表示24位音频;FMT(bit4:3)用于设置I2S音频数据格式(模式),我们一般设置为10,表示I2S格式,即飞利浦模式。

5,寄存器R606h),该寄存器我们直接全部设置为0即可,设置MCLKBCLK都来自外部,即由STM32F4提供。

6,寄存器R140Eh),该寄存器要设置ADCOSR128(bit3)1ADC得到最好的SNR

7,寄存器R442Ch),该寄存器我们要设置LIP2INPPGA(bit0)LIN2INPPGA(bit1)RIP2INPPGA(bit4)RIN2INPPGA(bit5)4个位,将这4个位都设置为1,将左右通道差分输入接入IN PGA

ADCOSR128(bit3)1ADC得到最好的SNR

8,寄存器R452Dh)和R462Eh),这两个寄存器用于设置PGA增益(调节麦克风增益),一个用于设置左通道(R45),另外一个用于设置右通道(R46)。这两个寄存器的最高位(INPPGAUPDATE)用于设置是否更新左右通道的增益,最低6位用于设置左右通道的增益,我们可以先设置好两个寄存器的增益,最后设置其中一个寄存器最高位为1,即可更新增益设置。

9,寄存器R472Fh)和R4830h),这两个寄存器也类似,我们只关心其最高位(bit8),都设置为1,可以让左右通道的MIC各获得20dB的增益。

10,寄存器R4931h),该寄存器我们要设置TSDEN(bit1)这个位,设置为1,开启过热保护。

以上,就是我们用WM8978录音时的设置,按照以上所述,对各个寄存器进行相应的配置,即可使用WM8978正常录音了。不过我们本章还要用到播放录音的功能,WM8978的播放配置在48.1.2节已经介绍过了,请大家参考这个章节。

上一章我们向大家介绍了STM32F4I2S放音,通过上一章的了解,我们知道:STM32F4的全双工需要用到扩展的I2Sx_extx=2/3),和I2Sx组成全双工I2S。在全双工模式下,I2SxI2Sx_ext提供CKWS时钟信号。

本章我们必须向WM8978提供WSCKMCK等时钟,同时又要录音,所以只能使用全双工模式。主I2Sx循环发送数据0X0000,给WM8978,以产生CKWSMCK等信号,从I2Sx_ext,则接收来自WM8978ADC数据(I2Sxext_SD),并保存到SD卡,实现录音。

本章我们还是采用I2S2的全双工模式来录音,I2S2的相关寄存器,我们在上一章已经介绍的差不多了。至于I2S2ext的寄存器,则有一套和I2S2一样的寄存器,不过仅仅少数几个对我们有用,他们是:I2S2ext_I2SCFGRI2S2ext_CR2I2S2ext_DR,这三个寄存器对应为的功能和描述,完全同I2S2。寄存器描述,我们这里就不再介绍了,大家可以看前面章节,也可以看《STM32F4xx中文参考手册》第27.5节。

最后,我们看看要通过STM32F4I2S,驱动WM8978实现WAV录音的简要步骤。这一章用到的硬件部分知识点实际上一章节已经讲解,这里我们主要讲解一下步骤:

1)初始化WM8978

这个过程就是前面所讲的WM8978 MIC录音配置步骤,让WM8978ADC以及其模拟部分工作起来。

2)初始化I2S2I2S2ext

      本章要用到I2S2的全双工模式,所以,I2S2I2S2ext都需要配置,其中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)  指示灯DS0DS1

2)  三个按键(KEY_UP/KEY0/KEY2

3)  串口

4)  TFTLCD模块

5)  SD

6)  SPI FLASH

7)  WM8978

8)  I2S2

这些前面都已介绍过。本实验,大家需要准备1SD卡和一个耳机(或喇叭),分别插入SD卡接口和耳机接口(喇叭接P1接口),然后下载本实验就可以实现一个简单的录音机了。

49.3 软件设计

打开本章实验工程可以看到,相比上一章实验,我们主要在APP分组下面新增了recorder.crecorder.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};//216位数据,用于录音时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;              //16PCM

     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函数,用于I2S2extDMA接收完成中断回调函数(通过i2s_rx_callback指向该函数实现),在该函数里面,实现音频数据的保存。recoder_enter_rec_mode函数,用于设置WM8978I2S进入录音模式(开始录音时用到)。recoder_enter_play_mode函数,则用于设置WM8978I2S进入播放模式(录音回放时用到)。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函数,用于设置I2S2extDMA接收,使用双缓冲循环模式,接收来自WM8978的数据,并开启了传输完成中断。而DMA1_Stream3_IRQHandler函数,则是DMA1数据流3传输完成中断的服务函数,该函数调用i2s_rx_callback函数(函数指针,使用前需指向特定函数)实现DMA数据接收保存。最后,I2S_ Rec_StartI2S_ 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,"201466",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/千千静听等)可以直接播放我们所录的音频。经实测,效果还是非常不错的。

实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm 

正点原子探索者STM32F407开发板购买地址http://item.taobao.com/item.htm?id=41855882779

  


实验44 录音机实验.zip

1.21 MB, 下载次数: 1426

第四十九章 录音机实验-STM32F4开发指南-正点原子探索者STM32开发板.pdf

1.11 MB, 下载次数: 550

我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

5

主题

20

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
302
金钱
302
注册时间
2015-9-6
在线时间
31 小时
发表于 2016-2-23 14:53:22 | 显示全部楼层
这里面在保存采集音频数据的时候判断了DMA1_Stream3->CR&(1<<19)是做什么用的?
回复 支持 反对

使用道具 举报

5

主题

20

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
302
金钱
302
注册时间
2015-9-6
在线时间
31 小时
发表于 2016-2-24 10:23:09 | 显示全部楼层
@原子正点
回复 支持 反对

使用道具 举报

5

主题

20

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
302
金钱
302
注册时间
2015-9-6
在线时间
31 小时
发表于 2016-2-24 10:58:05 | 显示全部楼层
令外我建议大家做这个实验的时候在程序里关闭外放喇叭的音量,用耳机听放音,不然回受的噪音实在让人难以忍受
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
16
金钱
16
注册时间
2016-4-15
在线时间
2 小时
发表于 2016-4-20 09:45:09 | 显示全部楼层
同问DMA1_Stream3->CR&(1<<19)的作用。。。是说寄存器满了?
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-26 23:02

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表