获取录音文件_阿波罗 STM32F767 开发板资料连载第五十三章 录音机实验

本篇博客详细介绍了如何在阿波罗 STM32F767 开发板上实现 WAV 录音功能。内容涵盖了SAI录音简介、硬件设计、软件设计及下载验证。重点讲解了WM8978的配置步骤和STM32F767的SAI接口设置,以及录音文件的保存和播放。
摘要由CSDN通过智能技术生成

1)实验平台:alientek 阿波罗 STM32F767 开发板2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子

37b8010b3d306274982b7e4d144e27e1.png

第五十三章 录音机实验

上一章,我们实现了一个简单的音乐播放器,本章我们将在上一章的基础上,实现一个简

单的录音机,实现 WAV 录音。本章分为如下几个部:

53.1 SAI 录音简介

53.2 硬件设计

53.3 软件设计

53.4 下载验证

53.1 SAI 录音简介

本章涉及的知识点基本上在上一章都有介绍。本章要实现 WAV 录音,还是和上一章一样,

要了解:WAV 文件格式、WM8978 和 SAI 接口。WAV 文件格式,我们在上一章已经做了详细

介绍了,这里就不作介绍了。

ALIENTEK 阿波罗 STM32F767 开发板将板载的一个 MIC 分别接入到了 WM8978 的 2 个

差分输入通道(LIP/LIN 和 RIP/RIN,原理图见:图 52.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 都来

自外部,即由 STM32F767 提供。

6,寄存器 R14(0Eh),该寄存器要设置 ADCOSR128(bit3)为 1,ADC 得到最好的 SNR。

7,寄存器 R44(2Ch),该寄存器我们要设置 LIP2INPPGA(bit0)、LIN2INPPGA(bit1)、

RIP2INPPGA(bit4)和 RIN2INPPGA(bit5)等 4 个位,将这 4 个位都设置为 1,将左右通道差

分输入接入 IN PGA。

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 的播放配置

在 50.1.2 节已经介绍过了,请大家参考这个章节。

上一章我们向大家介绍了 STM32F767 的 SAI 放音,通过上一章的了解,我们知道:

STM32F767 SAI 的全双工通信,需要用到 SAI 的两个子模块(SAI_A 和 SAI_B)),一个工作

在主模式,产生 FS、SCK 和 MCLK,一个工作在从模式,通过 SD 引脚接收数据。

本章我们必须向 WM8978 提供 WS(FS),CK(SCK)和 MCK(MCLK)等时钟,同时又要录音,

所以只能使用全双工模式。工作在主模式的 SAI 子模块循环发送数据 0X0000,给 WM8978,

以产生 CK、WS 和 MCK 等信号,工作在从模式的 SAI 子模块,则接收来自 WM8978 的 ADC

数据(ADCDAT),并保存到 SD 卡,实现录音。

本章我们将同时使用 SAI 的两个子模块,以实现录音功能,SAI 的相关寄存器,我们在上

一章已经介绍的差不多了,这里就不再进行寄存器介绍,大家可以参考《STM32F7 中文参考手

册.pdf》第 33.5 小节。

要实现录音功能,我们根据上一章,图 50.2.1 的连接关系可知,SAI_A 子模块必须工作在

主模式,循环发送 0X0000,以提供 FS、SCK 和 MCLK 等时钟信号,SAI_B 子模块则工作在从

模式,读取 ADCDAT 输出的数据流(SAI_SD_B),从而实现录音功能。

最后,我们看看要通过 STM32F767 的 SAI,驱动 WM8978 实现 WAV 录音的简要步骤,

如下:

1)初始化 WM8978

这个过程就是前面所讲的 WM8978 MIC 录音配置步骤,让 WM8978 的 ADC 以及其模拟部

分工作起来。

2)初始化 SAI_A 和 SAI_B

本章要用到 SAI 的全双工模式,所以,SAI_A 和 SAI_B 都需要配置,其中 SAI_A 配置为

主模式,SAI 设置为从模式,且与 SAI_A 同步。他们的其他配置(协议、时钟电平特性、slot

相关参数)基本一样,只是一个是发送一个是接收,且都要使能 DMA。同时,还需要设置音

频采样率,不过这个只需要设置 SAI_A 的即可,还是通过上一章介绍的查表法设置。

3)设置发送和接收 DMA

放音和录音都是采用 DMA 传输数据的,本章放音其实就是个幌子,不过也得设置 DMA

(使用 DMA2 数据流 3 的通道 0),配置同上一章一模一样,不过不需要开启 DMA 传输完成

中断。对于录音,则使用的是 DMA2 数据流 5 的通道 0 实现的 DMA 数据接收,我们需要配置

DMA2 的数据流 5,本章将 DMA2 数据流 5 设置为:双缓冲循环模式,外设和存储器都是 16

位宽,并开启传输完成中断(方便接收数据)。

4)编写接收通道 DMA 传输完成中断服务函数

为了方便接收音频数据,我们使用 DMA 传输完成中断,每当一个缓冲接数据满了,硬件

自动切换为下一个缓冲,同时进入中断服务函数,将已满缓冲的数据写入 SD 卡的 wav 文件。

过程如图 53.1.1 所示:

1187ad589ac13c12aad8da522c7b55ff.png

图 53.1.1 DMA 双缓冲接收音频数据流框图

5)创建 WAV 文件,并保存 wav 头

前面 4 步完成,其实就可以开始读取音频数据了,不过在录音之前,我们需要先在创建一

个新的文件,并写入 wav 头,然后才能开始写入我们读取到的的 PCM 音频数据。

6)开启 DMA 传输,接收数据

然后,我们就只需要开启 DMA 传输,然后及时将 SAI_SD_B 读到的数据写入到 SD 卡之

前新建的 wav 文件里面,就可以实现录音了。

7)计算整个文件大小,重新保存 wav 头并关闭文件

在结束录音的时候,我们必须知道本次录音的大小(数据大小和整个文件大小),然后更新

wav 头,重新写入文件,最后因为 FATFS,在文件创建之后,必须调用 f_close,文件才会真正

体现在文件系统里面,否则是不会写入的!所以最后还需要调用 f_close,以保存文件。

53.2 硬件设计

本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,

再检测 SD 卡根目录是否存在 RECORDER 文件夹,如果不存在则创建,如果创建失败,则报

错。在找到 SD 卡的 RECORDER 文件夹后,即进入录音模式(包括配置 WM8978 和 SAI 等),

此时可以在耳机(或喇叭)听到采集到的音频。KEY0 用于开始/暂停录音,KEY2 用于保存并

停止录音,KEY_UP 用于播放最近一次的录音。

当我们按下 KEY0 的时候,可以在屏幕上看到录音文件的名字、码率以及录音时间等,然

后通过 KEY2 可以保存该文件,同时停止录音(文件名和时间也都将清零),在完成一段录音

后,我们可以通过按 KEY_UP 按键,来试听刚刚的录音。DS0 用于提示程序正在运行,DS1

用于提示是否处于暂停录音状态。

本实验用到的资源如下:

1) 指示灯 DS0,DS1

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

3) 串口

4) LCD 模块

5) SD 卡

6) SPI FLASH

7) WM8978

8) SAI

这些前面都已介绍过。本实验,大家需要准备 1 个 SD 卡和一个耳机,分别插入 SD 卡接

口和耳机接口(PHONE),然后下载本实验就可以实现一个简单的录音机了。

53.3 软件设计

打开本章实验工程可以看到我们在 APP 分组下新增了 recorder.c 文件,用来存放录音相关

源码。因为 recorder.c 代码比较多,我们这里仅介绍其中几个重要的函数,代码如下:

u8 *sairecbuf1; //SAI1 DMA 接收 BUF1u8 *sairecbuf2; //SAI1 DMA 接收 BUF2 //REC 录音 FIFO 管理参数.//由于 FATFS 文件写入时间的不确定性,如果直接在接收中断里面写文件,可能导致某次写//入时间过长从而引起数据丢失,故加入 FIFO 控制,以解决此问题.vu8 sairecfifordpos=0; //FIFO 读位置vu8 sairecfifowrpos=0; //FIFO 写位置u8 *sairecfifobuf[SAI_RX_FIFO_SIZE]; //定义 10 个录音接收 FIFOFIL* f_rec=0; //录音文件u32 wavsize; //wav 数据大小(字节数,不包括文件头!!)u8 rec_sta=0; //录音状态//[7]:0,没有开启录音;1,已经开启录音;//[6:1]:保留//[0]:0,正在录音;1,暂停录音; //读取录音 FIFO//buf:数据缓存区首地址//返回值:0,没有数据可读;// 1,读到了 1 个数据块u8 rec_sai_fifo_read(u8 **buf){if(sairecfifordpos==sairecfifowrpos)return 0;sairecfifordpos++; //读位置加 1if(sairecfifordpos>=SAI_RX_FIFO_SIZE)sairecfifordpos=0;//归零*buf=sairecfifobuf[sairecfifordpos];return 1;}//写一个录音 FIFO//buf:数据缓存区首地址//返回值:0,写入成功;// 1,写入失败u8 rec_sai_fifo_write(u8 *buf){u16 i;u8 temp=sairecfifowrpos;//记录当前写位置sairecfifowrpos++; //写位置加 1if(sairecfifowrpos>=SAI_RX_FIFO_SIZE)sairecfifowrpos=0;//归零 if(sairecfifordpos==sairecfifowrpos){sairecfifowrpos=temp;//还原原来的写位置,此次写入失败  return 1;}for(i=0;iCR&(1<<19))rec_sai_fifo_write(sairecbuf1);//sairecbuf1 写 FIFOelse rec_sai_fifo_write(sairecbuf2);//sairecbuf2 写入 FIFO } } const u16 saiplaybuf[2]={0X0000,0X0000};//2 个数据,用于录音时 SAI_A 主机循环发送 0.//进入 PCM 录音模式 void recoder_enter_rec_mode(void){WM8978_ADDA_Cfg(0,1); //开启 ADCWM8978_Input_Cfg(1,1,0); //开启输入通道(MIC&LINE IN)WM8978_Output_Cfg(0,1); //开启 BYPASS 输出WM8978_MIC_Gain(46); //MIC 增益设置WM8978_SPKvol_Set(0); //关闭喇叭.WM8978_I2S_Cfg(2,0); //飞利浦标准,16 位数据长度SAIA_Init(SAI_MODEMASTER_TX,SAI_CLOCKSTROBING_RISINGEDGE,SAI_DATASIZE_16); //SAI1 Block A,主发送,16 位数据SAIB_Init(SAI_MODESLAVE_RX,SAI_CLOCKSTROBING_RISINGEDGE,SAI_DATASIZE_16);//SAI1 Block B 从模式接收,16 位SAIA_SampleRate_Set(REC_SAMPLERATE);//设置采样率SAIA_TX_DMA_Init((u8*)&saiplaybuf[0],(u8*)&saiplaybuf[1],1,1);//TX DMA,16 位 __HAL_DMA_DISABLE_IT(&SAI1_TXDMA_Handler,DMA_IT_TC);//关闭传输完成中断(这里不用中断送数据) SAIA_RX_DMA_Init(sairecbuf1,sairecbuf2,SAI_RX_DMA_BUF_SIZE/2,1);//配置 RX DMA sai_rx_callback=rec_sai_dma_rx_callback;//初始化回调函数指 sai_rx_callbackSAI_Play_Start(); //开始 SAI 数据发送(主机)SAI_Rec_Start(); //开始 SAI 数据接收(从机)recoder_remindmsg_show(0); } //初始化 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 ADPCMwavhead->fmt.NumOfChannels=2; //双声道wavhead->fmt.SampleRate=REC_SAMPLERATE;//设置采样速率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,i; u8 key; u8 rval=0;__WaveHeader *wavhead=0; DIR recdir; //目录 u8 *pname=0; u8 *pdatabuf;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"); //尝试创建该目录 } sairecbuf1=mymalloc(SRAMIN,SAI_RX_DMA_BUF_SIZE); //SAI 录音内存 1 申请sairecbuf2=mymalloc(SRAMIN,SAI_RX_DMA_BUF_SIZE); //SAI 录音内存 2 申请 for(i=0;iriff.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;sairecfifordpos=0; //FIFO 读写位置重新归零sairecfifowrpos=0;}rec_sta=0; recsec=0;LED1(1); //关闭 DS1LCD_Fill(30,190,lcddev.width-1,lcddev.height-1,WHITE);//清除显示break;case KEY0_PRES: //REC/PAUSEif(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==0X80)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);//显示LCD_Fill(30,210,lcddev.width-1,230,WHITE); recoder_enter_play_mode(); //进入播放模式audio_play_song(pname); //播放 pnameLCD_Fill(30,190,lcddev.width-1,lcddev.height-1,WHITE); recoder_enter_rec_mode(); //重新进入录音模式} }break;}if(rec_sai_fifo_read(&pdatabuf))//读取一次数据,读到数据了,写入文件{res=f_write(f_rec,pdatabuf,SAI_RX_DMA_BUF_SIZE,(UINT*)&bw);//写if(res)printf("write error:%d",res);wavsize+=SAI_RX_DMA_BUF_SIZE;}else delay_ms(5);timecnt++;if((timecnt%20)==0)LED0_Toggle; //DS0 闪烁 if(recsec!=(wavsize/wavhead->fmt.ByteRate)) //录音时间显示{ LED0_Toggle;//DS0 闪烁recsec=wavsize/wavhead->fmt.ByteRate; //录音时间 recoder_msg_show(recsec,wavhead->fmt.SampleRate*wavhead->fmt.NumOfChannels*wavhead->fmt.BitsPerSample);//显示码率} }} myfree(SRAMIN,sairecbuf1); //释放内存myfree(SRAMIN,sairecbuf2); //释放内存 for(i=0;i

这里总共 6 个函数,接下来,我们分别介绍。

1,rec_sai_fifo_read 和 rec_sai_fifo_write 函数

这两个函数用于我们构建的 FIFO 里面的数据读取和写入,SAI 采集到的数据,通过 FATFS

写入 SD 卡的时候,因为 FATFS 写入时间不确定(有时候短,有时候长),可能导致数据写入

不及时,出现数据丢失,从而录音会有间隔(丢失一部分)。所以,我们构建了一个 FIFO,SAI

采集的数据,通过 rec_sai_fifo_write 函数写入 FIFO 里面,在主循环里面,我们通过

rec_sai_fifo_read 函数不停的读取 FIFO 里面的数据,并将数据通过 FATFS 写入 SD 卡里面,只

要 rec_sai_fifo_read 的速度,不小于 rec_sai_fifo_write 的速度,就可以保证数据不丢失,这个

FIFO 起到了一个缓冲的作用,从而保证录音文件的流畅性。

2,rec_sai_dma_rx_callback 函数

该函数用于 SAI_B 的 DMA 接收完成中断回调函数(通过 sai_rx_callback 指向该函数实现),

在该函数里面调用 rec_sai_fifo_write 函数,将采集到的音频数据,写入 FIFO。

3,recoder_enter_rec_mode 函数

该函数用于设置 WM8978 进入录音模式,并设置 SAI_A 和 SAI_B 的工作模式和位数等信

息,然后配置 DMA 和回调函数的指向,最后开启录音。调用该函数后,就可以开始录音了。

4,recoder_wav_init 函数

该函数初始化 wav 头的绝大部分数据,采样率通过 REC_SAMPLERATE 宏定义修改,默

认是 44.1Khz,位数为 16 位,线性 PCM 格式,另外由于录音还未真正开始,所以文件大小和

数据大小都还是未知的,要等录音结束才能知道。该函数__WaveHeader 结构体就是由上一章

(50.1.1 节)介绍的三个 Chunk 组成,结构为:

//wav 头

typedef __packed struct

{

ChunkRIFF riff;

//riff 块

ChunkFMT fmt; //fmt 块

//

ChunkFACT fact; //fact 块 线性 PCM,没有这个结构体

ChunkDATA data; //data 块

}__WaveHeader;

5,wav_recorder 函数

该函数实现了我们在硬件设计时介绍的功能(开始/暂停录音、保存录音文件、播放最近一

次录音等),实现方法请大家参考源码理解。另外,该函数使用上一章实现的 audio_play_song

函数,来播放最近一次录音。

recorder.c 的其他代码和 recorder.h 的代码我们这里就不再贴出了,请大家参考光盘本实验

的源码。然后,我们在 sai.c 里面也增加了几个函数,如下:

//SAI Block B 初始化,I2S,飞利浦标准//mode:工作模式,可以设置:SAI_MODEMASTER_TX///SAI_MODEMASTER_RX/SAI_MODESLAVE_TX/SAI_MODESLAVE_RX//cpol:数据在时钟的上升/下降沿选通,可以设置://SAI_CLOCKSTROBING_FALLINGEDGE/SAI_CLOCKSTROBING_RISINGEDGE//datalen:数据大小,可以设置:SAI_DATASIZE_8/10/16/20/24/32void SAIB_Init(u32 mode,u32 cpol,u32 datalen){ HAL_SAI_DeInit(&SAI1B_Handler); //清除以前的配置 SAI1B_Handler.Instance=SAI1_Block_B; //SAI1 Bock B SAI1B_Handler.Init.AudioMode=mode; //设置 SAI1 工作模式 SAI1B_Handler.Init.Synchro=SAI_SYNCHRONOUS; //音频模块同步 SAI1B_Handler.Init.OutputDrive=SAI_OUTPUTDRIVE_ENABLE; //立即驱动输出 SAI1B_Handler.Init.NoDivider=SAI_MASTERDIVIDER_ENABLE; //使能主时钟分频器 SAI1B_Handler.Init.FIFOThreshold=SAI_FIFOTHRESHOLD_1QF //设置 FIFO 阈值 SAI1B_Handler.Init.ClockSource=SAI_CLKSOURCE_PLLI2S; //SIA 时钟源为 PLL2S SAI1B_Handler.Init.MonoStereoMode=SAI_STEREOMODE; //立体声模式 SAI1B_Handler.Init.Protocol=SAI_FREE_PROTOCOL; //设置 SAI1 协议为自由协议 SAI1B_Handler.Init.DataSize=datalen; //设置数据大小 SAI1B_Handler.Init.FirstBit=SAI_FIRSTBIT_MSB; //数据 MSB 位优先 SAI1B_Handler.Init.ClockStrobing=cpol; //数据在时钟的上升/下降沿选通  //帧设置 SAI1B_Handler.FrameInit.FrameLength=64; //设置帧长度为 64,左/右通道各 32 个 SCK, SAI1B_Handler.FrameInit.ActiveFrameLength=32; //设置帧同步有效电平长度 SAI1B_Handler.FrameInit.FSDefinition=SAI_FS_CHANNEL_IDENTIFICATION;//FS 信号为 SOF 信号+通道识别信号 SAI1B_Handler.FrameInit.FSPolarity=SAI_FS_ACTIVE_LOW; //FS 低电平有效(下降沿) SAI1B_Handler.FrameInit.FSOffset=SAI_FS_BEFOREFIRSTBIT; //在 slot0 的第一位的前一位使能 FS,以匹配飞利浦标准 //SLOT 设置 SAI1B_Handler.SlotInit.FirstBitOffset=0; //slot 偏移(FBOFF)为 0 SAI1B_Handler.SlotInit.SlotSize=SAI_SLOTSIZE_32B; //slot 大小为 32 位 SAI1B_Handler.SlotInit.SlotNumber=2; //slot 数为 2 个  SAI1B_Handler.SlotInit.SlotActive=SAI_SLOTACTIVE_0|SAI_SLOTACTIVE_1;//使能 slot0 和 slot1  HAL_SAI_Init(&SAI1B_Handler); SAIB_DMA_Enable(); //使能 SAI 的 DMA 功能 __HAL_SAI_ENABLE(&SAI1B_Handler); //使能 SAI }//SAIA TX DMA 配置//设置为双缓冲模式,并开启 DMA 传输完成中断//buf0:M0AR 地址.//buf1:M1AR 地址.//num:每次传输数据量//width:位宽(存储器和外设,同时设置),0,8 位;1,16 位;2,32 位;void SAIA_RX_DMA_Init(u8* buf0,u8 *buf1,u16 num,u8 width){  u32 memwidth=0,perwidth=0; //外设和存储器位宽 switch(width) { case 0: //8 位 memwidth=DMA_MDATAALIGN_BYTE; perwidth=DMA_PDATAALIGN_BYTE; break; case 1: //16 位 memwidth=DMA_MDATAALIGN_HALFWORD; perwidth=DMA_PDATAALIGN_HALFWORD; break; case 2: //32 位 memwidth=DMA_MDATAALIGN_WORD; perwidth=DMA_PDATAALIGN_WORD; break;  } __HAL_RCC_DMA2_CLK_ENABLE(); //使能 DMA2 时钟 __HAL_LINKDMA(&SAI1B_Handler,hdmarx,SAI1_RXDMA_Handler); //将 DMA 与 SAI 联系起来 SAI1_RXDMA_Handler.Instance=DMA2_Stream5; //DMA2 数据流 5  SAI1_RXDMA_Handler.Init.Channel=DMA_CHANNEL_0; //通道 0 SAI1_RXDMA_Handler.Init.Direction=DMA_PERIPH_TO_MEMORY; //外设到存储器 SAI1_RXDMA_Handler.Init.PeriphInc=DMA_PINC_DISABLE; //外设非增量模式 SAI1_RXDMA_Handler.Init.MemInc=DMA_MINC_ENABLE; //存储器增量模式 SAI1_RXDMA_Handler.Init.PeriphDataAlignment=perwidth; //外设数据长度:16/32 位 SAI1_RXDMA_Handler.Init.MemDataAlignment=memwidth; //存储器数据长度:16/32 位 SAI1_RXDMA_Handler.Init.Mode=DMA_CIRCULAR; //使用循环模式 SAI1_RXDMA_Handler.Init.Priority=DMA_PRIORITY_MEDIUM; //中等优先级 SAI1_RXDMA_Handler.Init.FIFOMode=DMA_FIFOMODE_DISABLE; //不使用 FIFO SAI1_RXDMA_Handler.Init.MemBurst=DMA_MBURST_SINGLE; //存储器单次突发 SAI1_RXDMA_Handler.Init.PeriphBurst=DMA_PBURST_SINGLE; //外设单次突发 HAL_DMA_DeInit(&SAI1_RXDMA_Handler); //先清除以前的设置 HAL_DMA_Init(&SAI1_RXDMA_Handler); //初始化 DMA  HAL_DMAEx_MultiBufferStart(&SAI1_RXDMA_Handler,(u32)&SAI1_Block_B->DR,(u32)buf0,(u32)buf1,num);//开启双缓冲 __HAL_DMA_DISABLE(&SAI1_RXDMA_Handler); //先关闭接收 DMA delay_us(10); //10us 延时,防止-O2 优化出问题 __HAL_DMA_CLEAR_FLAG(&SAI1_RXDMA_Handler,DMA_FLAG_TCIF1_5); //清除 DMA 传输完成中断标志位 __HAL_DMA_ENABLE_IT(&SAI1_RXDMA_Handler,DMA_IT_TC);//开启传输完成中断  HAL_NVIC_SetPriority(DMA2_Stream5_IRQn,0,1); //DMA 中断优先级 HAL_NVIC_EnableIRQ(DMA2_Stream5_IRQn);}void (*sai_rx_callback)(void); //RX 回调函数//DMA2_Stream5 中断服务函数void DMA2_Stream5_IRQHandler(void){  if(__HAL_DMA_GET_FLAG(&SAI1_RXDMA_Handler,DMA_FLAG_TCIF1_5)!=RESET) //DMA 传输完成 { __HAL_DMA_CLEAR_FLAG(&SAI1_RXDMA_Handler,DMA_FLAG_TCIF1_5);  //清除 DMA 传输完成中断标志位 if(sai_rx_callback!=NULL)sai_rx_callback();//执行回调函数,读取数据等操作在这里面处理  } } //SAI 开始录音void SAI_Rec_Start(void){  __HAL_DMA_ENABLE(&SAI1_RXDMA_Handler);//开启 DMA RX 传输 }//关闭 SAI 录音void SAI_Rec_Stop(void){  __HAL_DMA_DISABLE(&SAI1_RXDMA_Handler);//结束录音 }

这里新增了5个函数,SAIB_Init函数完成SAI_B子模块的初始化,通过3个参数设置SAI_B

的详细配置信息。SAIB_RX_DMA_Init 函数,用于设置 SAI_B 的 DMA 接收,使用双缓冲循环

模式,接收来自 WM8978 的数据,并开启了传输完成中断。而 DMA2_Stream5_IRQHandler 函

数,则是 DMA2 数据流 5 传输完成中断的服务函数,该函数调用 sai_rx_callback 函数(函数指

针,使用前需指向特定函数)实现 DMA 数据接收保存。最后,SAI_ Rec_Start 和 SAI_ Rec_Stop,

用于开启和关闭 SAI_B 的 DMA 传输。

其他代码,我们就不再介绍了,请大家参考开发板光盘本例程源码。最后我们看看 main

函数源码:

int main(void){  Cache_Enable(); //打开 L1-Cache HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz  delay_init(216); //延时初始化uart_init(115200); //串口初始化 LED_Init(); //初始化KEY_Init(); //初始化按键 SDRAM_Init(); //初始化 SDRAM LCD_Init(); //初始化 LCDW25QXX_Init(); //初始化 W25Q256 WM8978_Init(); //初始化 WM8978WM8978_HPvol_Set(40,40); //耳机音量设置WM8978_SPKvol_Set(40); //喇叭音量设置 my_mem_init(SRAMIN); //初始化内部内存池 my_mem_init(SRAMEX); //初始化外部 SDRAM 内存池 my_mem_init(SRAMDTCM); //初始化内部 DTCM 内存池 exfuns_init(); //为 fatfs 相关变量申请内存  f_mount(fs[0],"0:",1); //挂载 SD 卡f_mount(fs[1],"1:",1); //挂载 SPI FLASH.  f_mount(fs[2],"2:",1); //挂载 NAND FLASH. POINT_COLOR=RED; while(font_init()) //检查字库{ LCD_ShowString(30,50,200,16,16,"Font Error!");delay_ms(200); LCD_Fill(30,50,240,66,WHITE);//清除显示 delay_ms(200); } POINT_COLOR=RED; Show_Str(30,40,200,16,"阿波罗 STM32F4/F7 开发板",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,"2016 年 1 月 29 日",16,0); while(1){ wav_recorder(); } }

该函数代码同上一章的 main 函数代码几乎一样,十分简单,我们就不再多说了。

至此,本实验的软件设计部分结束。

53.4 下载验证

在代码编译成功之后,我们下载代码到 ALIENTEK 阿波罗 STM32 开发板上,程序先检测

字库,然后检测 SD 卡的 RECORDER 文件夹,一切顺利通过之后,进入录音模式,得到,如

图 53.4.1 所示:

0c998a72aae6a4839adfe5f9d724e2e7.png

图 53.4.1 录音机界面

此时,我们按下 KEY0 就开始录音了,此时看到屏幕显示录音文件的名字、码率以及录音

时长,如图 53.4.2 所示:

06128a7cca478670122580b8ecdec96c.png

图 53.4.2 录音进行中

在录音的时候按下 KEY0 则执行暂停/继续录音的切换,通过 DS1 指示录音暂停。通过按

下 KEY2,可以停止当前录音,并保存录音文件。在完成一次录音文件保存之后,我们可以通

过按 KEY_UP 按键,来实现播放这个录音文件(即播放最近一次的录音文件),实现试听。

我们将开发板的录音文件放到电脑上面,可以通过属性查看录音文件的属性,如图 53.4.3

所示:

749123083cf09d094480985c91940ed8.png

图 53.4.3 录音文件属性

这和我们预期的效果一样,通过电脑端的播放器(winamp/千千静听等)可以直接播放我们

所录的音频。经实测,效果还是非常不错的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值