1. 程式人生 > >STM32:利用VM8978和I2S實現錄音的頻率分析

STM32:利用VM8978和I2S實現錄音的頻率分析

板子為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)為有效位,16LSB被強制清零,無需任何軟體操作或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.05Khz44.1Khz48Khz96Khz196Khz等。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;i<I2S_RX_DMA_BUF_SIZE;i++)
		i2srecfifobuf[i2srecfifowrpos][i] = buf[i];//拷貝資料
	return 0;
} 

//錄音 I2S_DMA接收中斷服務函式.在中斷裡面寫入資料
void rec_i2s_dma_rx_callback(void) {      
	if(isRecording){
		// 典型的乒乓FIFO操作
		if(DMA1_Stream3->CR&(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<WAV_LEN; i++){
        signal[i].real = wave[i] / 32767.0;
        signal[i].imag = 0.0f;
    }
    fft(WAV_LEN, signal);
    c_abs(signal, res, WAV_LEN);
}



//WAV錄音 
void wav_recorder(){ 
	u8 i;
	u8 *pdatabuf;
	float *fft_data;
	fft_data   = mymalloc(SRAMIN,I2S_RX_DMA_BUF_SIZE*sizeof(float));//I2S錄音FIFO記憶體申請
	i2srecbuf1 = mymalloc(SRAMIN,I2S_RX_DMA_BUF_SIZE);//I2S錄音FIFO記憶體申請
	i2srecbuf2 = mymalloc(SRAMIN,I2S_RX_DMA_BUF_SIZE);//I2S錄音FIFO記憶體申請

	for(i=0;i<I2S_RX_FIFO_SIZE;i++){
		i2srecfifobuf[i] = mymalloc(SRAMIN,I2S_RX_DMA_BUF_SIZE);//I2S錄音FIFO記憶體申請
		if(i2srecfifobuf[i]==NULL)
			break;			//申請失敗
	}

	if(i==I2S_RX_FIFO_SIZE){
		recoder_enter_rec_mode();	//進入錄音模式,此時耳機可以聽到咪頭採集到的音訊   
 	   	for(;;){
			if( KEY_Scan(0) == KEY0_PRES){
				if(isRecording) {
					isRecording = 0;	        //停止錄音
					i2srecfifordpos = 0;	    //FIFO讀寫位置重新歸零
					i2srecfifowrpos = 0;
					LED1 = 1;
				}else{
					isRecording = 1;	        //開始錄音
					LED1 = 0;
				}
			}
			if(rec_i2s_fifo_read(&pdatabuf)){
				int i, j;
				static unsigned char buffer[40];
				real_fft((short*)pdatabuf, fft_data);//傅立葉變換
				
				for(i=0; i<8; i++){
					float sum = 0.0f;
					for(j=0; j<32; j++){
						sum += fft_data[i*32+j];
					}
					sprintf((char*)buffer, "%5d~%5dHz: %6.2f", i*2756, (i+1)*2756, sum);
					Show_Str(10,120+30*i,240,16,buffer,16,0);
				}
			}
		}		 
	}
	for(i=0;i<I2S_RX_FIFO_SIZE;i++)
		myfree(SRAMIN,i2srecfifobuf[i]);  //I2S錄音FIFO記憶體釋放
}
定義接收FIFO佇列長度為10,每個DMA大小為4096.