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

2019-07-20 22:44发布

第四十九章 录音机实验

  [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 ",res);                             }else               {                      res=f_write(f_rec,i2srecbuf2,I2S_RX_DMA_BUF_SIZE,(UINT*)&bw);//写文件                      if(res) printf("write error:%d ",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);                                    }        &
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。