開篇第一步
Inter-IC Sound Interface(簡稱I2S)是由飛利浦公司開發,用于通過不同IC之間的串行接口(例如從處理器到DAC)傳輸數字音頻數據。該接口使用以下信號進行數據傳輸:
SCK (串行時鐘)——用于數據傳輸的時鐘。
SD (串行數據)- 每個數據字的各個位通過該線傳輸。
WS (字選擇)- 定義傳輸數據字的長度。它用于標記右或左音頻通道。
僅音頻數據通過 I2S 傳輸。附加數據(例如各個總線用戶的配置)通過其他接口傳輸。數據傳輸總是在兩個總線之間沿一個方向進行,其中一路總線必須充當主機并負責生成時鐘信號。在由多個發送器和接收器組成的復雜系統中,時鐘信號由外部總線主控器生成,并且相應的發送器生成數據。
所有數據均以二進制補碼和 MSB 優先的方式傳輸。如果接收方和發送方的字寬存在正差(即一方的字寬小于另一方的字寬),則剩余位填充0。根據規范,數據可以同步于正時鐘沿或負時鐘沿,從而數據總是在負時鐘沿讀入。
WS信號選擇活動通道,并將低或高相位內的所有數據分配給相應的通道:
WS = 0 – 通道 1(左)
WS = 1 – 通道 2(右)
?WS信號必須在下一個數據字的 MSB 之前的一個時鐘周期發生變化,以便接收器可以將數據讀入正確的通道。WS信號的時鐘頻率通常對應于音頻信號的采樣頻率。
在這篇文章中,展示如何設計一個簡單的 I2S 發射器,并使用 CS4344 立體聲 D/A 轉換器通過揚聲器輸出恒定的聲音。
要輸出的聲音將存儲在 FPGA 的block memory中,并由發送器讀出,并將數據發送到 D/A 轉換器。整個項目分為三個部分,將逐步討論:
集成系統時鐘和I2S模塊的Top設計
集成ROM和I2S發送器的I2S模塊
I2S發送器
I2S發送器
設計的最底層應該是 I2S 發送器,其任務是通過 I2S 接口發送各個數據字。
該框圖產生了以下發送器:
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;
數據字的大小通過WIDTH參數定義。
三級狀態機控制發送器,描述如下:
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;
復位期間,輸出信號被置位,SCLK時鐘被停用。復位后,機器從State_Reset狀態變為State_TransmitWord狀態。在此狀態下,機器Tx_Int通過 I2S 接口傳輸緩沖區的內容。
一旦開始傳輸最后一個數據位,Ready就設置為表示傳輸結束并準備好接受新數據。然后機器更改為 stateState_LoadWord狀態,其中發送緩沖區填充有新的數據字并開始新的傳輸。
I2S模塊
I2S 模塊使用 I2S 發送器將數據從 ROM 傳輸到 D/A 轉換器。
具有以下代碼:
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;
參數RATIO 定義了SCLK與MCLK WIDTH的比率以及每個通道的數據字的寬度。
除了 I2S 發送器之外,該模塊還使用 ROM,該 ROM 可以通過block memory生成器創建并填充數據。兩者都可以使用 Vivado 的 IP 來完成。
最后,通過其他選項使用正弦信號 coe 文件(參見附件)對 ROM 進行初始化。
I2S 模塊使用狀態機從 ROM 讀取數據并將其傳輸到 I2S 發送器。
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;
第一個過程用于從MCLK生成發送器所需的時鐘信號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;
第二個進程負責狀態機的處理。離開State_Reset狀態后,機器在該State_WaitForReady狀態下等待,直到發送器發出就緒信號Ready 。??
一旦發送器準備就緒,機器就會更改State_WaitForStart狀態。在此狀態下,從 ROM 讀取當前數據字并將其傳輸到發送器。
PS:此處顯示的 ROM 僅包含一個通道的信息。第二個通道的數據需進行擴展。
一旦發送器清除就緒信號并開始發送數據,狀態機就會更改為State_IncreaseAddress狀態。該狀態下ROM地址加1,然后切換回State_WaitForReady狀態
top模塊
最后一個組件是頂層設計,包括 I2S 模塊和時鐘PLL。
本示例使用以下參數來控制 CS4344:
MCLK:12.288MHz
SCLK:1.536 MHz
LRCLK:48kHz
比率:8
寬度:16
時鐘PLL生成 12.288 MHz 時鐘,并與之前代碼中完成的 I2S 模塊一起實例化。
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;
最后就可以進行測試。理想情況下,D/A 轉換器輸出 480 Hz 正弦信號。因為來自 ROM 的信號模式的長度為 100 個樣本,采樣頻率為 48 kHz。可以用示波器檢查總線和信號:
此外,還可以檢查音頻信號(示波器的 FFT 功能是實現此目的的最佳工具)。
附件-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 發送器添加 AXI-Stream 接口,并將其與 ZYNQ 的處理系統連接,播放 SD 卡中的音頻文件。
審核編輯:湯梓紅
-
處理器
+關注
關注
68文章
19286瀏覽量
229843 -
FPGA
+關注
關注
1629文章
21736瀏覽量
603389 -
接口
+關注
關注
33文章
8598瀏覽量
151157 -
音頻
+關注
關注
29文章
2877瀏覽量
81548
原文標題:使用 FPGA 播放音頻(一)
文章出處:【微信號:Open_FPGA,微信公眾號:OpenFPGA】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論