一、I2S簡介
I2S(也叫IIS,即:Inter IC Sound)總線, 又稱集成電路內置音頻總線,是飛利浦公司為數字音頻設備之間的音頻資料傳輸而制定的一種總線標準,該總線專責于音頻設備之間的資料傳輸,廣泛應用于各種多媒體系統,它采用了沿獨立的導線傳輸時鐘與資料信號的設計,通過將資料和時鐘信號分離,避免了因時差誘發的失真,為用戶節省了購買抵抗音頻抖動的專業設備的費用,
特點
●支持全雙工/半雙工通信
●支持主/從模式設定
●8位可編程線性預分頻器,可實作精確的音頻采樣頻率(8~192Khz)
●支持16位/24位/32位資料格式
●資料包幀固定為16位(僅16位資料幀)或32位(可容納16/24/32位資料幀)
●可編程時鐘極性
●支持MSB對齊(左對齊)、LSB對齊(右對齊)、飛利浦標準和PCM標準等I2S標準
●支持DMA資料傳輸(16位寬)
●資料方向固定位MSB在前
●支持主時鐘輸出(固定為256*fs,fs即音頻采樣率)
二、I2S框圖以及信號
I2S框圖
STM32F4的I2S是與SPI部分共用的,通過設定SPI_I2SCFGR暫存器的I2SMOD位即可開啟I2S功能,I2S介面使用了幾乎與SPI相同的引腳、標志和中斷,

信號
1,SD:串行資料(映射到 MOSI 引腳),用于發送或接收兩個時分復用的資料通道上的資料(僅半雙工模式),
2,WS:字選擇(映射到NSS引腳),即左右時鐘,用于切換左右聲道的資料,WS頻率等于音頻信號采樣率(fs),
3,CK:串行時鐘(映射到SCK引腳),即位時鐘,是主模式下的串行時鐘輸出以及從模式下的串行時鐘輸入,CK頻率=WS頻率(fs)216(16位寬),如果是32位寬,則是:CK頻率=WS頻率(fs)232(32位寬),
4,I2S2ext_SD和I2S3ext_SD:用于控制I2S全雙工模式的附加串行資料引腳(映射到MISO引腳),這兩個引腳僅用于全雙工模式,
5,MCK:即主時鐘輸出,當I2S配置為主模式(且SPI_I2SPR暫存器的MCKOE位置1)時,使用此時鐘,該時鐘頻率為 256×fs,fs:音頻信號采樣頻率,
STM32F4為支持I2S全雙工模式,除了I2S2和I2S3,還可以使用兩個額外的I2S,它們稱為擴展I2S(I2S2_ext、I2S3_ext),其框圖為:

擴展I2S (I2Sx_ext)只能用于全雙工模式,I2Sx_ext始終在從模式下作業,I2Sx和I2Sx_ext 均可用于發送和接收,
三、幀格式
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位資料幀,
4種幀標準:
1,飛利浦標準;
2,MSB 對齊(左對齊)標準;
3,LSB 對齊(右對齊)標準;
4,PCM標準;
I2S飛利浦標準幀24位資料,32位幀格式

I2S飛利浦標準,使用WS信號來指示當前正在發送的資料所屬的通道,該信號從當前通道資料的第一個位(MSB)之前的一個時鐘開始有效,發送方在時鐘信號(CK)的下降沿改變資料,接收方在上升沿讀取資料,WS信號也在CK的下降沿變化,在24位模式下資料傳輸,需要對SPI_DR執行兩次讀取或寫入操作,比如要發送0X8EAA33這個資料,就要分兩次寫入SPI_DR,第一次寫入:0X8EAA,第二次寫入0X33xx(xx可以為任意數值),這樣就把0X8EAA33發送出去了,
注意:從SD卡讀取到的24位WAV資料流,是低位元組在前,高位元組在后的,比如,我們讀到一個聲道的資料(24bit),存盤在buf[3]里面,那么要通過SPI_DR發送這個24位資料,程序如下:
SPI_DR=((u16)buf[2]<<8)+buf[1];
SPI_DR=(u16)buf[0]<<8;
這樣,第一次發送高16位資料,第二次發送低8位資料,完成一次24bit資料的發送,
四、I2S時鐘

上圖中的I2SxCLK,可以來自PLLI2S輸出(通過R系數分頻)或者來自外部時鐘(I2S_CKIN引腳),一般使用前者作為I2SxCLK輸入時鐘,
需要根據音頻采樣率(fs)來計算各個分頻器的值,常用的音頻采樣率有:22.05Khz、44.1Khz、48Khz、96Khz、196Khz等,
當MCK輸出使能時,fs頻率計算公式如下:
fs=I2SxCLK/[256 * (2 * I2SDIV+ODD)]
其中:I2SxCLK=(HSE/pllm)* PLLI2SN/PLLI2SR, HSE是8Mhz,而pllm在系統時鐘初始化就確定了,是8,這樣結合以上式,可得計算公式如下:
fs= (1000 * PLLI2SN/PLLI2SR )/[256 * (2 * I2SDIV+ODD)]
fs單位是:Khz,其中:PLLI2SN取值范圍:192~ 432;PLLI2SR取值范圍:2~ 7;I2SDIV取值范圍:2~255;ODD取值范圍:0/1,
根據以上約束條件,便可根據fs來設定各個系數的值了,不過很多時候,并不能取得和fs一模一樣的頻率,只能近似等于fs,比如44.1Khz采樣率,設定PLLI2SN=271,PLLI2SR=2,I2SDIV=6,ODD=0,得到fs=44.108073Khz,誤差為:0.0183%,晶振頻率決定了有時無法通過分頻得到我們所要的fs,所以,某些fs如果要實作0誤差,必須得選用外部時鐘才可以,
為了方便可以將常用的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采樣率
};
五、I2S暫存器
1、SPI_I2S配置暫存器(SPI_I2SCFGR)

I2SMOD位,設定為1,選擇I2S模式,注意,必須在I2S/SPI禁止的時候,設定該位,
I2SE位,設定為1,使能I2S外設,該位必須在I2SMOD位設定之后再設定,
I2SCFG[1:0]位, 這兩個位用于配置I2S模式,設定為10,選擇主模式(發送),
I2SSTD[1:0]位,這兩個位用于選擇I2S標準,設定為00,選擇飛利浦標準,
CKPOL位,用于設定空閑時時鐘電平,設定為0,空閑時時鐘低電平,
DATLEN[1:0]位,用于設定資料長度,00,表示16位資料;01表示24位資料,
CHLEN位,用于設定通道長度,即幀長度,0,表示16位;1,表示32位,
2、SPI_I2S預分頻器暫存器(SPI_I2SSPR)

設定MCKOE為1,開啟MCK輸出,ODD和I2SDIV則根據不同的fs,查表進行設定,
3、PLLI2S配置暫存器(RCC_PLLI2SCFGR)

該暫存器用于配置PLLI2SR和PLLI2SN兩個系數,PLLI2SR的取值范圍是:2~ 7,PLLI2SN的取值范圍是:192~432,同樣,這兩個也是根據fs的值來設定的,
六、I2S初始化步驟
- 1)初始化WM8978
這個程序就是配置上一講講介紹的WM8978那十幾個暫存器,包括軟復位、DAC設定、輸出設定和音量設定等, - 2)初始化I2S
此程序主要設定SPI_I2SCFGR暫存器,設定I2S模式、I2S標準、時鐘空閑電平和資料幀長等,最后開啟I2S TX DMA,使能I2S外設, - 3)決議WAV檔案,獲取音頻信號采樣率和位數并設定I2S時鐘分頻器
決議WAV檔案,取得音頻信號的采樣率(fs)和位數(16位或24位),根據這兩個引數,來設定I2S的時鐘分頻,用前面介紹的查表法來設定, - 4)設定DMA
I2S播放音頻,一般采用DMA來傳輸資料,這里我們用I2S2,其TX用DMA1資料流4的通道0來傳輸資料,并且,STM32F4的DMA具有雙緩沖機制,這樣可以提高效率,這里,我們將DMA1資料流4設定為:雙緩沖回圈模式,外設和存盤器都是16位寬,并開啟DMA傳輸完成中斷(方便填充資料), - 5)撰寫DMA傳輸完成中斷服務函式
為了方便填充音頻資料,我們使用DMA傳輸完成中斷,每當一個緩沖資料發送完后,硬體自動切換為下一個緩沖,同時進入中斷服務函式,填充資料到發送完的這個緩沖,如下圖所示:

- 6)開啟DMA傳輸,填充資料
最后,只需要開啟DMA傳輸,然后及時填充WAV資料到DMA的兩個快取區即可,此時,就可以在WM8978的耳機和喇叭通道聽到所播放音樂了,
七、播放WAV檔案代碼
//播放某個WAV檔案
//fname:wav檔案路徑.
//回傳值:
//KEY0_PRES:下一曲
//KEY1_PRES:上一曲
//其他:錯誤
u8 wav_play_song(u8* fname)
{
u8 key;
u8 t=0;
u8 res;
u32 fillnum;
audiodev.file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));
audiodev.i2sbuf1=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
audiodev.i2sbuf2=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
audiodev.tbuf=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);
if(audiodev.file&&audiodev.i2sbuf1&&audiodev.i2sbuf2&&audiodev.tbuf)
{
res=wav_decode_init(fname,&wavctrl);//得到檔案的資訊
if(res==0)//決議檔案成功
{
if(wavctrl.bps==16)
{
WM8978_I2S_Cfg(2,0); //飛利浦標準,16位資料長度
I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,I2S_DataFormat_16bextended); //飛利浦標準,主機發送,時鐘低電平有效,16位擴展幀長度
}else if(wavctrl.bps==24)
{
WM8978_I2S_Cfg(2,2); //飛利浦標準,24位資料長度
I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,I2S_DataFormat_24b); //飛利浦標準,主機發送,時鐘低電平有效,24位擴展幀長度
}
I2S2_SampleRate_Set(wavctrl.samplerate);//設定采樣率
I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE/2); //配置TX DMA
i2s_tx_callback=wav_i2s_dma_tx_callback; //回呼函式指wav_i2s_dma_callback
audio_stop();
res=f_open(audiodev.file,(TCHAR*)fname,FA_READ); //打開檔案
if(res==0)
{
f_lseek(audiodev.file, wavctrl.datastart); //跳過檔案頭
fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);
fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);
audio_start();
while(res==0)
{
while(wavtransferend==0);//等待wav傳輸完成;
wavtransferend=0;
if(fillnum!=WAV_I2S_TX_DMA_BUFSIZE)//播放結束?
{
res=KEY0_PRES;
break;
}
if(wavwitchbuf)fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf2
else fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf1
while(1)
{
key=KEY_Scan(0);
if(key==WKUP_PRES)//暫停
{
if(audiodev.status&0X01)audiodev.status&=~(1<<0);
else audiodev.status|=0X01;
}
if(key==KEY2_PRES||key==KEY0_PRES)//下一曲/上一曲
{
res=key;
break;
}
wav_get_curtime(audiodev.file,&wavctrl);//得到總時間和當前播放的時間
audio_msg_show(wavctrl.totsec,wavctrl.cursec,wavctrl.bitrate);
t++;
if(t==20)
{
t=0;
LED0=!LED0;
}
if((audiodev.status&0X01)==0)delay_ms(10);
else break;
}
}
audio_stop();
}else res=0XFF;
}else res=0XFF;
}else res=0XFF;
myfree(SRAMIN,audiodev.tbuf); //釋放記憶體
myfree(SRAMIN,audiodev.i2sbuf1);//釋放記憶體
myfree(SRAMIN,audiodev.i2sbuf2);//釋放記憶體
myfree(SRAMIN,audiodev.file); //釋放記憶體
return res;
}
音樂播放就講到這里啦!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/310653.html
標籤:其他
