讓我們看一下I2S規(guī)范,并嘗試用FPGA播放音頻文件。
開(kāi)篇第一步
Inter-IC Sound Interface(簡(jiǎn)稱I2S)是由飛利浦公司開(kāi)發(fā),用于通過(guò)不同IC之間的串行接口(例如從處理器到DAC)傳輸數(shù)字音頻數(shù)據(jù)。該接口使用以下信號(hào)進(jìn)行數(shù)據(jù)傳輸:
SCK (串行時(shí)鐘)——用于數(shù)據(jù)傳輸?shù)臅r(shí)鐘。
SD (串行數(shù)據(jù))- 每個(gè)數(shù)據(jù)字的各個(gè)位通過(guò)該線傳輸。
WS (字選擇)- 定義傳輸數(shù)據(jù)字的長(zhǎng)度。它用于標(biāo)記右或左音頻通道。
僅音頻數(shù)據(jù)通過(guò) I2S 傳輸。附加數(shù)據(jù)(例如各個(gè)總線用戶的配置)通過(guò)其他接口傳輸。數(shù)據(jù)傳輸總是在兩個(gè)總線之間沿一個(gè)方向進(jìn)行,其中一路總線必須充當(dāng)主機(jī)并負(fù)責(zé)生成時(shí)鐘信號(hào)。在由多個(gè)發(fā)送器和接收器組成的復(fù)雜系統(tǒng)中,時(shí)鐘信號(hào)由外部總線主控器生成,并且相應(yīng)的發(fā)送器生成數(shù)據(jù)。
所有數(shù)據(jù)均以二進(jìn)制補(bǔ)碼和 MSB 優(yōu)先的方式傳輸。如果接收方和發(fā)送方的字寬存在正差(即一方的字寬小于另一方的字寬),則剩余位填充0。根據(jù)規(guī)范,數(shù)據(jù)可以同步于正時(shí)鐘沿或負(fù)時(shí)鐘沿,從而數(shù)據(jù)總是在負(fù)時(shí)鐘沿讀入。
WS信號(hào)選擇活動(dòng)通道,并將低或高相位內(nèi)的所有數(shù)據(jù)分配給相應(yīng)的通道:
WS = 0 – 通道 1(左)
WS = 1 – 通道 2(右)
?WS信號(hào)必須在下一個(gè)數(shù)據(jù)字的 MSB 之前的一個(gè)時(shí)鐘周期發(fā)生變化,以便接收器可以將數(shù)據(jù)讀入正確的通道。WS信號(hào)的時(shí)鐘頻率通常對(duì)應(yīng)于音頻信號(hào)的采樣頻率。
在這篇文章中,展示如何設(shè)計(jì)一個(gè)簡(jiǎn)單的 I2S 發(fā)射器,并使用 CS4344 立體聲 D/A 轉(zhuǎn)換器通過(guò)揚(yáng)聲器輸出恒定的聲音。
要輸出的聲音將存儲(chǔ)在 FPGA 的block memory中,并由發(fā)送器讀出,并將數(shù)據(jù)發(fā)送到 D/A 轉(zhuǎn)換器。整個(gè)項(xiàng)目分為三個(gè)部分,將逐步討論:
集成系統(tǒng)時(shí)鐘和I2S模塊的Top設(shè)計(jì)
集成ROM和I2S發(fā)送器的I2S模塊
I2S發(fā)送器
I2S發(fā)送器
設(shè)計(jì)的最底層應(yīng)該是 I2S 發(fā)送器,其任務(wù)是通過(guò) I2S 接口發(fā)送各個(gè)數(shù)據(jù)字。
該框圖產(chǎn)生了以下發(fā)送器:
entityI2S_Transmitteris Generic(WIDTH:INTEGER:=16 ); Port(Clock:inSTD_LOGIC; nReset:inSTD_LOGIC; Ready:outSTD_LOGIC; Tx:inSTD_LOGIC_VECTOR(((2*WIDTH)-1)downto0); LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC ); endI2S_Transmitter;
數(shù)據(jù)字的大小通過(guò)WIDTH參數(shù)定義。
三級(jí)狀態(tài)機(jī)控制發(fā)送器,描述如下:
architectureI2S_Transmitter_ArchofI2S_Transmitteris typeState_tis(State_Reset,State_LoadWord,State_TransmitWord); signalCurrentState:State_t:=State_Reset; signalTx_Int:STD_LOGIC_VECTOR(((2*WIDTH)-1)downto0):=(others=>'0'); signalReady_Int:STD_LOGIC:='0'; signalLRCLK_Int:STD_LOGIC:='1'; signalSD_Int:STD_LOGIC:='0'; signalEnable:STD_LOGIC:='0'; begin process variableBitCounter:INTEGER:=0; begin waituntilfalling_edge(Clock); caseCurrentStateis whenState_Reset=> Ready_Int<=?'0'; ????????????????LRCLK_Int?<=?'1'; ????????????????Enable?<=?'1'; ????????????????SD_Int?<=?'0'; ????????????????Tx_Int?<=?(others?=>'0'); CurrentState<=?State_LoadWord; ????????????when?State_LoadWord?=> BitCounter:=0; Tx_Int<=?Tx; ????????????????LRCLK_Int?<=?'0'; ????????????????CurrentState?<=?State_TransmitWord; ????????????when?State_TransmitWord?=> BitCounter:=BitCounter+1; if(BitCounter>(WIDTH-1))then LRCLK_Int<=?'1'; ????????????????end?if; ????????????????if(BitCounter?((2?*?WIDTH)?-?1))?then ????????????????????Ready_Int?<=?'0'; ????????????????????CurrentState?<=?State_TransmitWord; ????????????????else ????????????????????Ready_Int?<=?'1'; ????????????????????CurrentState?<=?State_LoadWord; ????????????????end?if; ????????????????Tx_Int?<=?Tx_Int(((2?*?WIDTH)?-?2)?downto?0)?&?"0"; ????????????????SD_Int?<=?Tx_Int((2?*?WIDTH)?-?1); ????????end?case; ????????if(nReset?=?'0')?then ????????????CurrentState?<=?State_Reset;???????? ????????end?if; ????end?process; ????Ready?<=?Ready_Int; ????SCLK?<=?Clock?and?Enable; ????LRCLK?<=?LRCLK_Int; ????SD?<=?SD_Int; end?I2S_Transmitter_Arch;
復(fù)位期間,輸出信號(hào)被置位,SCLK時(shí)鐘被停用。復(fù)位后,機(jī)器從State_Reset狀態(tài)變?yōu)镾tate_TransmitWord狀態(tài)。在此狀態(tài)下,機(jī)器Tx_Int通過(guò) I2S 接口傳輸緩沖區(qū)的內(nèi)容。
一旦開(kāi)始傳輸最后一個(gè)數(shù)據(jù)位,Ready就設(shè)置為表示傳輸結(jié)束并準(zhǔn)備好接受新數(shù)據(jù)。然后機(jī)器更改為 stateState_LoadWord狀態(tài),其中發(fā)送緩沖區(qū)填充有新的數(shù)據(jù)字并開(kāi)始新的傳輸。
I2S模塊
I2S 模塊使用 I2S 發(fā)送器將數(shù)據(jù)從 ROM 傳輸?shù)?D/A 轉(zhuǎn)換器。
具有以下代碼:
entityI2Sis Generic(RATIO:INTEGER:=8; WIDTH:INTEGER:=16 ); Port(MCLK:inSTD_LOGIC; nReset:inSTD_LOGIC; LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC ); endI2S;
參數(shù)RATIO 定義了SCLK與MCLK WIDTH的比率以及每個(gè)通道的數(shù)據(jù)字的寬度。
除了 I2S 發(fā)送器之外,該模塊還使用 ROM,該 ROM 可以通過(guò)block memory生成器創(chuàng)建并填充數(shù)據(jù)。兩者都可以使用 Vivado 的 IP 來(lái)完成。
最后,通過(guò)其他選項(xiàng)使用正弦信號(hào) coe 文件(參見(jiàn)附件)對(duì) ROM 進(jìn)行初始化。
I2S 模塊使用狀態(tài)機(jī)從 ROM 讀取數(shù)據(jù)并將其傳輸?shù)?I2S 發(fā)送器。
architectureI2S_ArchofI2Sis typeState_tis(State_Reset,State_WaitForReady,State_IncreaseAddress,State_WaitForStart); signalCurrentState:State_t:=State_Reset; signalTx:STD_LOGIC_VECTOR(((2*WIDTH)-1)downto0):=(others=>'0'); signalROM_Data:STD_LOGIC_VECTOR((WIDTH-1)downto0):=(others=>'0'); signalROM_Address:STD_LOGIC_VECTOR(6downto0):=(others=>'0'); signalReady:STD_LOGIC; signalSCLK_Int:STD_LOGIC:='0'; componentI2S_Transmitteris Generic(WIDTH:INTEGER:=16 ); Port(Clock:inSTD_LOGIC; nReset:inSTD_LOGIC; Ready:outSTD_LOGIC; Tx:inSTD_LOGIC_VECTOR(((2*WIDTH)-1)downto0); LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC ); endcomponent; componentSineROMis Port(Address:inSTD_LOGIC_VECTOR(6downto0); Clock:inSTD_LOGIC; DataOut:outSTD_LOGIC_VECTOR(15downto0) ); endcomponentSineROM; begin Transmitter:I2S_Transmittergenericmap(WIDTH=>WIDTH ) portmap(Clock=>SCLK_Int, nReset=>nReset, Ready=>Ready, Tx=>Tx, LRCLK=>LRCLK, SCLK=>SCLK, SD=>SD ); ROM:SineROMportmap(Clock=>MCLK, Address=>ROM_Address, DataOut=>ROM_Data ); process variableCounter:INTEGER:=0; begin waituntilrising_edge(MCLK); if(Counter((RATIO?/?2)?-?1))?then ????????????Counter?:=?Counter?+?1; ????????else ????????????Counter?:=?0; ????????????SCLK_Int?<=?not?SCLK_Int; ????????end?if; ????????if(nReset?=?'0')?then ????????????Counter?:=?0; ????????????SCLK_Int?<=?'0'; ????????end?if; ????end?process; ????process ????????variable?WordCounter????:?INTEGER?:=?0; ????begin ????????wait?until?rising_edge(MCLK); ????????case?CurrentState?is ????????????when?State_Reset?=> WordCounter:=0; CurrentState<=?State_WaitForReady; ????????????when?State_WaitForReady?=> if(Ready='1')then CurrentState<=?State_WaitForStart; ????????????????else ????????????????????CurrentState?<=?State_WaitForReady; ????????????????end?if; ????????????when?State_WaitForStart?=> ROM_Address<=?STD_LOGIC_VECTOR(to_unsigned(WordCounter,?ROM_Address'length)); ????????????????Tx?<=?x"0000"?&?ROM_Data; ????????????????if(Ready?=?'0')?then ????????????????????CurrentState?<=?State_IncreaseAddress; ????????????????else ????????????????????CurrentState?<=?State_WaitForStart; ????????????????end?if; ????????????when?State_IncreaseAddress?=> if(WordCounter99)?then ????????????????????WordCounter?:=?WordCounter?+?1; ????????????????else ????????????????????WordCounter?:=?0; ????????????????end?if; ????????????????CurrentState?<=?State_WaitForReady; ????????end?case; ????????if(nReset?=?'0')?then ????????????CurrentState?<=?State_Reset; ????????end?if; ????end?process; end?I2S_Arch;
第一個(gè)過(guò)程用于從MCLK生成發(fā)送器所需的時(shí)鐘信號(hào)SCLK 。
process variableCounter:INTEGER:=0; begin waituntilrising_edge(MCLK); if(Counter((RATIO?/?2)?-?1))?then ????????Counter?:=?Counter?+?1; ????else ????????Counter?:=?0; ????????SCLK_Int?<=?not?SCLK_Int; ????end?if; ????if(nReset?=?'0')?then ????????Counter?:=?0; ????????SCLK_Int?<=?'0'; ????end?if; end?process;
第二個(gè)進(jìn)程負(fù)責(zé)狀態(tài)機(jī)的處理。離開(kāi)State_Reset狀態(tài)后,機(jī)器在該State_WaitForReady狀態(tài)下等待,直到發(fā)送器發(fā)出就緒信號(hào)Ready 。??
一旦發(fā)送器準(zhǔn)備就緒,機(jī)器就會(huì)更改State_WaitForStart狀態(tài)。在此狀態(tài)下,從 ROM 讀取當(dāng)前數(shù)據(jù)字并將其傳輸?shù)桨l(fā)送器。
PS:此處顯示的 ROM 僅包含一個(gè)通道的信息。第二個(gè)通道的數(shù)據(jù)需進(jìn)行擴(kuò)展。
一旦發(fā)送器清除就緒信號(hào)并開(kāi)始發(fā)送數(shù)據(jù),狀態(tài)機(jī)就會(huì)更改為State_IncreaseAddress狀態(tài)。該狀態(tài)下ROM地址加1,然后切換回State_WaitForReady狀態(tài)
top模塊
最后一個(gè)組件是頂層設(shè)計(jì),包括 I2S 模塊和時(shí)鐘PLL。
本示例使用以下參數(shù)來(lái)控制 CS4344:
MCLK:12.288MHz
SCLK:1.536 MHz
LRCLK:48kHz
比率:8
寬度:16
時(shí)鐘PLL生成 12.288 MHz 時(shí)鐘,并與之前代碼中完成的 I2S 模塊一起實(shí)例化。
entityTopis Generic(RATIO:INTEGER:=8; WIDTH:INTEGER:=16 ); Port(Clock:inSTD_LOGIC; nReset:inSTD_LOGIC; MCLK:outSTD_LOGIC; LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC; LED:outSTD_LOGIC_VECTOR(3downto0) ); endTop; architectureTop_ArchofTopis signalnSystemReset:STD_LOGIC:='0'; signalMCLK_DCM:STD_LOGIC:='0'; signalLocked:STD_LOGIC:='0'; componentI2Sis Generic(RATIO:INTEGER:=8; WIDTH:INTEGER:=16 ); Port(MCLK:inSTD_LOGIC; nReset:inSTD_LOGIC; LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC ); endcomponent; componentAudioClockis Port(ClockIn:inSTD_LOGIC; Locked:outSTD_LOGIC; MCLK:outSTD_LOGIC; nReset:inSTD_LOGIC ); endcomponent; begin InputClock:AudioClockportmap(ClockIn=>Clock, nReset=>nReset, MCLK=>MCLK_DCM, Locked=>Locked ); I2S_Module:I2Sgenericmap(RATIO=>RATIO, WIDTH=>WIDTH ) portmap(MCLK=>MCLK_DCM, nReset=>nSystemReset, LRCLK=>LRCLK, SCLK=>SCLK, SD=>SD ); nSystemReset<=?nReset?and?Locked; ????LED(0)?<=?nReset; ????LED(1)?<=?Locked; ????LED(2)?<=?nSystemReset; ????MCLK?<=?MCLK_DCM; end?Top_Arch;
最后就可以進(jìn)行測(cè)試。理想情況下,D/A 轉(zhuǎn)換器輸出 480 Hz 正弦信號(hào)。因?yàn)閬?lái)自 ROM 的信號(hào)模式的長(zhǎng)度為 100 個(gè)樣本,采樣頻率為 48 kHz??梢杂?a href="http://wenjunhu.com/v/tag/577/" target="_blank">示波器檢查總線和信號(hào):
此外,還可以檢查音頻信號(hào)(示波器的 FFT 功能是實(shí)現(xiàn)此目的的最佳工具)。
附件-Coe
memory_initialization_radix=16; memory_initialization_vector= 0000, 0809, 100A, 17FB, 1FD4, 278D, 2F1E, 367F, 3DA9, 4495, 4B3B, 5196, 579E, 5D4E, 629F, 678D, 6C12, 7029, 73D0, 7701, 79BB, 7BF9, 7DBA, 7EFC, 7FBE, 7FFF, 7FBE, 7EFC, 7DBA, 7BF9, 79BB, 7701, 73D0, 7029, 6C12, 678D, 629F, 5D4E, 579E, 5196, 4B3B, 4495, 3DA9, 367F, 2F1E, 278D, 1FD4, 17FB, 100A, 0809, 0000, F7F7, EFF6, E805, E02C, D873, D0E2, C981, C257, BB6B, B4C5, AE6A, A862, A2B2, 9D61, 9873, 93EE, 8FD7, 8C30, 88FF, 8645, 8407, 8246, 8104, 8042, 8001, 8042, 8104, 8246, 8407, 8645, 88FF, 8C30, 8FD7, 93EE, 9873, 9D61, A2B2, A862, AE6A, B4C5, BB6B, C257, C981, D0E2, D873, E02C, E805, EFF6, F7F7,
下一篇文章,將向 I2S 發(fā)送器添加 AXI-Stream 接口,并將其與 ZYNQ 的處理系統(tǒng)連接,播放 SD 卡中的音頻文件。
審核編輯:湯梓紅
-
處理器
+關(guān)注
關(guān)注
68文章
19286瀏覽量
229852 -
FPGA
+關(guān)注
關(guān)注
1629文章
21736瀏覽量
603419 -
接口
+關(guān)注
關(guān)注
33文章
8598瀏覽量
151163 -
音頻
+關(guān)注
關(guān)注
29文章
2877瀏覽量
81553
原文標(biāo)題:使用 FPGA 播放音頻(一)
文章出處:【微信號(hào):Open_FPGA,微信公眾號(hào):OpenFPGA】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論