第四十九章 录音机实验
[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
",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};//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);
}
&
一周热门 更多>