板子为STM32F407,通过VM8978和I2S进行录音,保存在SRAM中。
1.VM8978
VM8978是欧胜推出的一款全功能音频处理器,集成了对麦克风的支持以及扬声器功效。VM8978的控制通过I2S(飞利浦标准)接口同MCU进行音频数据传输,通过两线(MODE=0,即 IIC 接口)或三线(MODE=1)接口进行配置 。将VM8978作为从机,接受LRC(数据左/右对齐时钟)和BCLK(位时钟,用于同步)。
I2S标准模式,数据在跟随 LRC 传输的 BCLK 的第二个上升沿时传输 MSB,其他位一直到 LSB 按顺序传输。传输依赖于字长、 BCLK 频率和采样率,在每个采样的 LSB 和下一个采样的 MSB 之间都应该有未用的 BCLK 周期。飞利浦标准模式的 I2S 数据传输协议如图
图中, fs 即音频信号的采样率,比如 44.1Khz,因此可以知道, LRC 的频率就是音频信号的采样率。
对VM8978的配置,一般按照通用配置
WM8978_Write_Reg(1,0X1B); //R1,MICEN设置为1(MIC使能),BIASEN设置为1(模拟器工作),VMIDSEL[1:0]设置为:11(5K)
WM8978_Write_Reg(2,0X1B0); //R2,ROUT1,LOUT1输出使能(耳机可以工作),BOOSTENR,BOOSTENL使能
WM8978_Write_Reg(3,0X6C); //R3,LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX使能
WM8978_Write_Reg(6,0); //R6,MCLK由外部提供
WM8978_Write_Reg(43,1<<4); //R43,INVROUT2反向,驱动喇叭
WM8978_Write_Reg(47,1<<8); //R47设置,PGABOOSTL,左通道MIC获得20倍增益
WM8978_Write_Reg(48,1<<8); //R48设置,PGABOOSTR,右通道MIC获得20倍增益
WM8978_Write_Reg(49,1<<1); //R49,TSDEN,开启过热保护
WM8978_Write_Reg(10,1<<3); //R10,SOFTMUTE关闭,128x采样,最佳SNR
WM8978_Write_Reg(14,1<<3); //R14,ADC 128x采样率
2.IS2
I2S(Inter IC Sound)总线,是飞利浦为数字音频设备之间的音频数据传输而制定的一种总线标准。
STM32F4 的 I2S 是与 SPI部分共用的,通过设置SPI_I2SCFGR寄存器的I2SMOD 位即可开启 I2S功能,I2S接口使用了几乎与SPI 相同的引脚、标志和中断。
STM32F4 的 I2S 支持 4种数据和帧格式组合,分别是:1, 将16位数据封装在16 位帧中;2, 将16位数据封装在32 位帧中; 3, 将24位数据封装在32 位帧中;4, 将32位数据封装在32位帧中。
将 16 位数据封装在32位帧中时,前16 位(MSB)为有效位,16位LSB被强制清零,无需任何软件操作或DMA请求(只需一个读/写操作)。如果应用程序首选DMA,则24位和32位数据帧需要对SPI_DR执行两次CPU 读取或写入操作,或者需要两次DMA操作。 24位的数据帧,硬件会将8位非有效位扩展到带有 0 位的32位。对于所有数据格式和通信标准而言,始终会先发送最高有效位(MSB优先)。
STM32F4 的 I2S 支持: MSB 对齐(左对齐)标准、 LSB 对齐(右对齐)标准、飞利浦标准和PCM 标准等 4 种音频标准,我们用飞利浦标准。
I2S 飞利浦标准, 使用 WS 信号来指示当前正在发送的数据所属的通道。该信号从当前通道数据的第一个位(MSB)之前的一个时钟开始有效。发送方在时钟信号(CK)的下降沿改变数据,接收方在上升沿读取数据。 WS 信号也在 CK 的下降沿变化。
一般我们需要根据音频采样率(fs,即CK的频率)来计算各个分频器的值,常用的音频采样率有:22.05Khz、44.1Khz、48Khz、96Khz、196Khz等。当 MCK输出使能时,fs频率计算公式如下:
fs= (1000*PLLI2SN/PLLI2SR )/[256*(2*I2SDIV+ODD)]
常用fs 对应系数表如下:
//表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD
const u16 I2S_PSC_TBL[][5]=
{
{800 ,256,5,12,1}, //8Khz 采样率
{1102,429,4,19,0}, //11.025Khz 采样率
{1600,213,2,13,0}, //16Khz 采样率
{2205,429,4, 9,1}, //22.05Khz 采样率
{3200,213,2, 6,1}, //32Khz 采样率
{4410,271,2, 6,0}, //44.1Khz 采样率
{4800,258,3, 3,1}, //48Khz 采样率
{8820,316,2, 3,1}, //88.2Khz 采样率
{9600,344,2, 3,1}, //96Khz 采样率
{17640,361,2,2,0}, //176.4Khz 采样率
{19200,393,2,2,0}, //192Khz 采样率
};
IIS的初始化可参对数据手册完成。
3.主程序部分
#include "recorder.h"
#include "malloc.h"
#include "text.h"
#include "wm8978.h"
#include "i2s.h"
#include "led.h"
#include "lcd.h"
#include "delay.h"
#include "key.h"
#include "exfuns.h"
#include "text.h"
#include "string.h"
#include "fft.h"
#include "sram.h"
u8 *i2srecbuf1; //I2S DMA接收BUF1
u8 *i2srecbuf2; //I2S DMA接收BUF2
//REC录音FIFO管理参数.
//由于FATFS文件写入时间的不确定性,如果直接在接收中断里面写文件,可能导致某次写入时间过长
//从而引起数据丢失,故加入FIFO控制,以解决此问题.
vu8 i2srecfifordpos = 0; //FIFO读位置
vu8 i2srecfifowrpos = 0; //FIFO写位置
u8 *i2srecfifobuf[I2S_RX_FIFO_SIZE];//定义10个录音接收FIFO
FIL* f_rec=0; //录音文件
u32 wavsize; //wav数据大小(字节数,不包括文件头!!)
u8 isRecording = 0; //录音状态
//读取录音FIFO
//buf:数据缓存区首地址
//返回值:0,没有数据可读;
// 1,读到了1个数据块
u8 rec_i2s_fifo_read(u8 **buf){
if(i2srecfifordpos==i2srecfifowrpos)return 0;
i2srecfifordpos++; //读位置加1
if(i2srecfifordpos>=I2S_RX_FIFO_SIZE)i2srecfifordpos=0;//归零
*buf=i2srecfifobuf[i2srecfifordpos];
return 1;
}
//写一个录音FIFO
//buf:数据缓存区首地址
//返回值: 0,写入成功;
// 1,写入失败
u8 rec_i2s_fifo_write(u8 *buf){
u16 i;
u8 temp=i2srecfifowrpos;//记录当前写位置
i2srecfifowrpos++; //写位置加1
if(i2srecfifowrpos>=I2S_RX_FIFO_SIZE)i2srecfifowrpos=0;//归零
if(i2srecfifordpos==i2srecfifowrpos){
i2srecfifowrpos=temp;//还原原来的写位置,此次写入失败
return 1;
}
for(i=0;iCR&(1<<19)){
rec_i2s_fifo_write(i2srecbuf1); //i2srecbuf1写入FIFO
}else{
rec_i2s_fifo_write(i2srecbuf2); //i2srecbuf2写入FIFO
}
}
}
//进入PCM 录音模式
void recoder_enter_rec_mode(void){
//2个16位数据,用于录音时I2S Block A主机发送.循环发送0.
static const u16 i2splaybuf[2]={0X0000,0X0000};
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_SPKvol_Set(0); //关闭喇叭.
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);
}
#define WAV_LEN (1024)
void real_fft(short wave[], float res[]){
int i;
static complex signal[WAV_LEN];
for(i=0; i
定义接收FIFO队列长度为10,每个DMA大小为4096.