:WM8731與DE2板I2C/I2S全鏈路打通)
簡(jiǎn)介一個(gè)基于Altera DE2開發(fā)板EP2C35F672C6 FPGA與WM8731音頻編解碼器的完整音頻處理工程覆蓋從模擬音頻輸入、ADC數(shù)字化采樣、FPGA內(nèi)部處理、DAC重建到最終模擬輸出的全鏈路適合正在學(xué)習(xí)FPGA數(shù)字邏輯設(shè)計(jì)、音頻編解碼協(xié)議或嵌入式音頻應(yīng)用的開發(fā)者。音頻信號(hào)先由WM8731內(nèi)置ADC完成采樣量化通過(guò)I2S串行總線和分頻后的SCLK/LRCK時(shí)鐘送入FPGAFPGA內(nèi)部可執(zhí)行濾波、增益或混音等處理隨后將結(jié)果送回同芯片的DAC輸出。開發(fā)者參照DE2官方范例重寫了代碼精簡(jiǎn)了時(shí)序控制與數(shù)據(jù)搬運(yùn)邏輯使整體結(jié)構(gòu)更清晰便于理解Master/Slave同步機(jī)制與采樣字長(zhǎng)匹配等關(guān)鍵點(diǎn)。壓縮包共133個(gè)文件體積約911KB以Verilog源碼為主要學(xué)習(xí)對(duì)象同時(shí)包含Quartus工程配置、編譯報(bào)告、綜合數(shù)據(jù)庫(kù)與FPGA下載文件能夠從RTL級(jí)到布局布線逐層查看工程生成過(guò)程方便復(fù)盤與二次開發(fā)。已有826人學(xué)習(xí)下載。其中不僅有可直接運(yùn)行的音頻通路代碼還展示了如何用I2S接口讓FPGA與WM8731可靠協(xié)同工作對(duì)掌握ADC/DAC數(shù)據(jù)交互、時(shí)鐘樹設(shè)計(jì)和基礎(chǔ)音頻處理算法都有實(shí)際幫助適合課設(shè)或畢設(shè)階段參考。1. 從「音頻回環(huán)」入門 FPGA 數(shù)字信號(hào)鏈路WM8731 與 DE2 板的一次完整打通手頭有一塊 DE2-EP2C35F672C6 開發(fā)板想驗(yàn)證 FPGA 在真實(shí)信號(hào)鏈路里到底能干什么最簡(jiǎn)單的切入點(diǎn)不是寫流水燈而是把板載的 WM8731 音頻編解碼芯片用起來(lái)LINE IN 進(jìn)模擬信號(hào)芯片內(nèi)部 ADC 轉(zhuǎn)成數(shù)字流FPGA 拿到數(shù)據(jù)后原樣丟給 DAC再?gòu)亩鷻C(jī)口聽到自己的聲音。這個(gè)項(xiàng)目看起來(lái)只是「輸入到輸出」的直通實(shí)際拆開會(huì)發(fā)現(xiàn)里面藏著一整套數(shù)字音頻系統(tǒng)的骨架——I2C 寄存器配置、I2S 時(shí)序?qū)R、主從時(shí)鐘同步、左右聲道采樣拼接任何一個(gè)環(huán)節(jié)沒做對(duì)出來(lái)的就是噪聲或者干脆靜音。適合手里有同款或類似開發(fā)板、想從點(diǎn)燈進(jìn)階到真實(shí)信號(hào)處理的開發(fā)者也適合正在啃音頻協(xié)議、想找個(gè)能跑通的參考實(shí)現(xiàn)的人。項(xiàng)目最終交付的不是一句「Hello World」而是一條能通、能改、能繼續(xù)往上加效果的完整音頻通路。2. 硬件鏈路與工程結(jié)構(gòu)從 WM8731 芯片手冊(cè)到 FPGA 引腳約束2.1 信號(hào)鏈路全景模擬入、數(shù)字處理、模擬出整條鏈路的核心是板載的 WM8731 編解碼芯片這是一顆低功耗立體聲音頻 codec內(nèi)部集成了 ADC、DAC、耳機(jī)驅(qū)動(dòng)和完整的時(shí)鐘管理單元。信號(hào)走向很直觀外部音頻從 LINE IN 接口進(jìn)入經(jīng)過(guò) WM8731 內(nèi)部 ADC 按設(shè)定的采樣率轉(zhuǎn)成數(shù)字信號(hào)通過(guò) I2S 總線送給 FPGAFPGA 在內(nèi)部做處理——本項(xiàng)目里是直通或軟件可控的回環(huán)處理完的數(shù)據(jù)再經(jīng) I2S 總線送回 WM8731 的 DAC轉(zhuǎn)為模擬信號(hào)從耳機(jī)接口輸出。FPGA 夾在中間既是數(shù)據(jù)搬運(yùn)工也是未來(lái)接入 DSP 算法的主戰(zhàn)場(chǎng)。WM8731 與 FPGA 之間的接口分兩路一路是 I2C負(fù)責(zé)配置芯片內(nèi)部寄存器包括采樣率、輸入輸出增益、電源管理等另一路是 I2S承載實(shí)時(shí)音頻數(shù)據(jù)流。I2C 配置通路決定了芯片的工作模式I2S 數(shù)據(jù)通路決定了音頻數(shù)據(jù)能否無(wú)損傳輸兩條路缺一不可。所以項(xiàng)目里最核心的工作就是兩件事寫一個(gè)能用的 I2C 控制器完成初始化再寫一個(gè)時(shí)序正確的 I2S 收發(fā)器把數(shù)據(jù)跑起來(lái)。2.2 引腳分配與工程目錄EP2C35F672C6 的板級(jí)約束EP2C35F672C6 是 Cyclone II 系列 FPGA672 個(gè)引腳的 FBGA 封裝DE2 板把 WM8731 接到了 FPGA 的特定引腳上。在寫邏輯之前必須先確認(rèn)引腳約束。常見的分配方式如下set_location_assignment PIN_AC21 -to AUD_ADC_DAT set_location_assignment PIN_AF24 -to AUD_BCLK set_location_assignment PIN_AB23 -to AUD_DAC_DAT set_location_assignment PIN_AC22 -to AUD_DACLRCK set_location_assignment PIN_AB22 -to AUD_ADCLRCK set_location_assignment PIN_AE23 -to AUD_MCLK set_location_assignment PIN_AE25 -to AUD_I2C_SCLK set_location_assignment PIN_AD25 -to AUD_I2C_SDAT這段約束里AUD_ADC_DAT是 WM8731 的 ADC 串行數(shù)據(jù)輸出數(shù)據(jù)從芯片流向 FPGAAUD_DAC_DAT是 DAC 數(shù)據(jù)輸入數(shù)據(jù)從 FPGA 流向芯片AUD_BCLK是位時(shí)鐘AUD_DACLRCK和AUD_ADCLRCK分別是 DAC 和 ADC 的左右聲道幀同步信號(hào)AUD_MCLK是主時(shí)鐘。這些引腳名在 DE2 板附帶的使用手冊(cè)里有明確標(biāo)注約束文件的本質(zhì)就是把 FPGA 的物理引腳和邏輯信號(hào)對(duì)應(yīng)起來(lái)。如果這里寫錯(cuò)一個(gè)引腳編號(hào)整個(gè)工程燒進(jìn)去之后音頻通路就是斷的而且很難排查。工程目錄我習(xí)慣按功能拆分rtl目錄放 RTL 代碼包含頂層模塊、I2C 控制器、I2S 收發(fā)器db和incremental_db是綜合布局布線產(chǎn)生的中間目錄quartus目錄放工程文件sim放仿真用的 testbench。頂層模塊負(fù)責(zé)例化子模塊和連接信號(hào)I2C 控制器負(fù)責(zé)初始化 codecI2S 收發(fā)器負(fù)責(zé)音頻數(shù)據(jù)搬運(yùn)。這樣的結(jié)構(gòu)保證了音頻鏈路每一段都是獨(dú)立可替換的后續(xù)加效果器或?yàn)V波器時(shí)不影響基礎(chǔ)通路。3. 寄存器配置編寫手寫 I2C 控制器驅(qū)動(dòng) WM8731 初始化3.1 WM8731 寄存器映射與默認(rèn)值推導(dǎo)WM8731 內(nèi)部寄存器是 7 位地址加 9 位數(shù)據(jù)通過(guò) I2C 總線訪問(wèn)芯片的 I2C 地址是 0x1A7 位地址寫操作時(shí)發(fā)送 0x34。項(xiàng)目需要配置的寄存器不多核心是以下幾個(gè)寄存器 0 是左聲道輸入音量寄存器 2 是右聲道輸入音量寄存器 4 是模擬音頻通路控制寄存器 6 是數(shù)字音頻通路控制寄存器 8 是采樣率控制寄存器 12 是激活使能寄存器 15 是重置。localparam CFG_START 8d0; localparam CFG_RESET {7h0F, 9h000}; // 重置信道復(fù)位所有寄存器 localparam CFG_LINEIN {7h00, 9h017}; // 左聲道輸入音量 0dB開啟輸入 localparam CFG_LINEIN_R {7h02, 9h017}; // 右聲道輸入音量 0dB開啟輸入 localparam CFG_ANAPATH {7h04, 9h012}; // 模擬通路DAC 直通不經(jīng)過(guò)混音 localparam CFG_DEEMP {7h06, 9h000}; // 數(shù)字通路關(guān)掉去加重關(guān)掉軟靜音 localparam CFG_SRATE {7h08, 9h01E}; // 采樣率 48kHzMCLK18.432MHz localparam CFG_ACTIVE {7h0C, 9h001}; // 激活接口開始傳輸音頻CFG_RESET這條配置要在上電后第一時(shí)間發(fā)送把芯片恢復(fù)到已知狀態(tài)。0x0F 寄存器寫入 0x000 后所有寄存器回到默認(rèn)值后續(xù)配置從干凈狀態(tài)開始。CFG_LINEIN和CFG_LINEIN_R把左右聲道輸入音量設(shè)在 0dB確保信號(hào)不被衰減。CFG_ANAPATH選擇了 DAC 到耳機(jī)輸出的直通路徑繞開模擬混音器——這個(gè)項(xiàng)目里我們用數(shù)字回環(huán)模擬混音器只會(huì)增加噪聲。CFG_SRATE的 0x01E 對(duì)應(yīng) 48kHz 采樣率這里寫的是芯片手冊(cè)里的推薦配置值MCLK 由 FPGA 側(cè)提供 18.432MHz 時(shí)鐘。3.2 I2C 控制器狀態(tài)機(jī)實(shí)現(xiàn)與字節(jié)級(jí)時(shí)序I2C 控制器是本項(xiàng)目第一個(gè)硬骨頭。WM8731 的 I2C 時(shí)序要求起始條件 SCL 高電平時(shí) SDA 下降沿停止條件 SCL 高電平時(shí) SDA 上升沿?cái)?shù)據(jù)在 SCL 高電平期間必須保持穩(wěn)定。FPGA 作為主機(jī)需要主動(dòng)產(chǎn)生這些時(shí)序信號(hào)。我一般用一個(gè)三段式狀態(tài)機(jī)來(lái)寫把發(fā)送過(guò)程拆成 IDLE、START、SHIFT、ACK、STOP 等幾個(gè)狀態(tài)。module i2c_master ( input clk, // 50MHz 板載時(shí)鐘 input start, // 啟動(dòng)一次傳輸 input [15:0] data, // 要發(fā)送的數(shù)據(jù)高 8 位是寄存器地址低 8 位是數(shù)據(jù) output reg done, // 傳輸完成標(biāo)志 output reg scl, // I2C 時(shí)鐘線 inout sda // I2C 數(shù)據(jù)線 ); parameter CLK_DIV 50; // 50MHz 分頻到約 100kHz SCL符合 I2C 標(biāo)準(zhǔn) reg [6:0] clk_cnt; reg [4:0] state; reg [4:0] bit_cnt; reg [15:0] tx_buf; reg sda_out; localparam IDLE 5d0, START 5d1, SEND 5d2, ACK 5d3, STOP 5d4; assign sda sda_out ? 1bz : 1b0; always (posedge clk) begin if (clk_cnt CLK_DIV) clk_cnt 0; else clk_cnt clk_cnt 1; end always (posedge clk) begin if (clk_cnt 0) begin case (state) IDLE: begin if (start) begin state START; tx_buf data; bit_cnt 16; scl 1b1; sda_out 1b1; end end START: begin sda_out 1b0; // SCL 高電平期間 SDA 拉低產(chǎn)生起始條件 scl 1b0; state SEND; end SEND: begin scl 1b1; sda_out tx_buf[15]; // 先發(fā)最高位 tx_buf {tx_buf[14:0], 1b0}; bit_cnt bit_cnt - 1; if (bit_cnt 1) state ACK; // 數(shù)據(jù)段發(fā)送完畢進(jìn)入應(yīng)答 else scl 1b0; end ACK: begin sda_out 1b1; // 釋放 SDA讀應(yīng)答位 scl 1b0; state STOP; end STOP: begin scl 1b1; sda_out 1b1; // SCL 高電平期間 SDA 拉高產(chǎn)生停止條件 state IDLE; done 1b1; end endcase end end endmodule這個(gè)控制器的每個(gè)狀態(tài)都在 SCL 為低電平時(shí)切換 SDA在 SCL 為高電平時(shí)數(shù)據(jù)保持穩(wěn)定嚴(yán)格滿足 I2C 協(xié)議。CLK_DIV是個(gè)關(guān)鍵參數(shù)50MHz 主頻分頻到 100kHz SCL這個(gè)是標(biāo)準(zhǔn)模式的上限WM8731 完全兼容。如果后續(xù)想加快初始化速度可以把分頻系數(shù)改小但要注意芯片的 SCL 最高頻率限制。data端口接收 16 位數(shù)據(jù)WM8731 實(shí)際使用的是 7 位寄存器地址加 9 位數(shù)據(jù)多余的位數(shù)在發(fā)送時(shí)會(huì)是無(wú)效位但芯片會(huì)忽略。3.3 配置時(shí)序與上電初始化流程I2C 控制器寫好后還不能直接往 WM8731 灌配置。芯片上電后需要等待電源穩(wěn)定然后依次發(fā)送配置項(xiàng)。我用一個(gè)簡(jiǎn)單的計(jì)數(shù)器來(lái)觸發(fā)逐條發(fā)送上電后延時(shí) 100ms拉高start信號(hào)第一條配置是 RESET等待done標(biāo)志回落后發(fā)下一條。當(dāng)十二條配置全部發(fā)送完成置位cfg_done這時(shí) FPGA 才能開始往 I2S 數(shù)據(jù)線上送數(shù)據(jù)。實(shí)際調(diào)試中容易翻車的地方在 ACK 狀態(tài)。我習(xí)慣把 I2C 的sda管腳配置成inout在 ACK 狀態(tài)下把sda_out拉高釋放總線然后等 WM8731 把 SDA 拉低表示應(yīng)答。如果芯片沒有正確上電或者地址寫錯(cuò)SDA 會(huì)一直保持高電平這時(shí)done標(biāo)志照樣會(huì)拉起但 WM8731 根本沒收到配置。排查這種問(wèn)題的方法很簡(jiǎn)單在仿真里看 ACK 位是不是低電平或者用一個(gè) IO 拉高連接到邏輯分析儀上看波形。4. 數(shù)據(jù)通路實(shí)現(xiàn)I2S 收發(fā)器與頂層的持續(xù)回環(huán)4.1 I2S 時(shí)序協(xié)議位時(shí)鐘、幀同步與數(shù)據(jù)對(duì)齊WM8731 配置完成后音頻數(shù)據(jù)通過(guò) I2S 總線傳輸。I2S 協(xié)議有三個(gè)關(guān)鍵信號(hào)BCLK 是位時(shí)鐘每個(gè)時(shí)鐘周期傳輸一位數(shù)據(jù)LRCK 是幀同步信號(hào)高電平表示右聲道低電平表示左聲道DAT 是串行數(shù)據(jù)線。WM8731 工作在從機(jī)模式時(shí)BCLK 和 LRCK 都可以由 FPGA 產(chǎn)生。但要注意本項(xiàng)目中 WM8731 是主機(jī)它自己會(huì)產(chǎn)生 BCLK 和 LRCKFPGA 只需要提供主時(shí)鐘 MCLK 并讀取數(shù)據(jù)。我用的是主機(jī)模式FPGA 自己產(chǎn)生 BCLK 和 LRCKWM8731 作為從機(jī)接收。這樣做的優(yōu)點(diǎn)是不用擔(dān)心 MCLK 分頻系數(shù)的計(jì)算錯(cuò)誤只要保證 BCLK 是 48kHz 的整數(shù)倍即可。BCLK 頻率通常是 采樣率乘以位寬乘以通道數(shù)48kHz 乘 16 位乘 2 通道等于 1.536MHz。LRCK 的頻率就是采樣率本身每隔 32 個(gè) BCLK 周期翻轉(zhuǎn)一次。4.2 I2S 收發(fā)模塊的 Verilog 實(shí)現(xiàn)module i2s_transceiver ( input clk, // 板載 50MHz 時(shí)鐘 input rst_n, // 異步復(fù)位 input mclk, // WM8731 主時(shí)鐘輸出18.432MHz input i2s_bclk, // 位時(shí)鐘由 FPGA 內(nèi)部產(chǎn)生 input i2s_lrck, // 左右聲道幀同步 output reg dac_dat, // FPGA 到 WM8731 DAC 的數(shù)據(jù) input adc_dat, // WM8731 ADC 到 FPGA 的數(shù)據(jù) output reg [15:0] left_out, // 左聲道數(shù)據(jù)輸出 output reg [15:0] right_out, // 右聲道數(shù)據(jù)輸出 input [15:0] left_in, // 左聲道數(shù)據(jù)輸入 input [15:0] right_in // 右聲道數(shù)據(jù)輸入 ); reg [4:0] bit_count; reg [15:0] shift_out; reg [15:0] shift_in; always (posedge i2s_bclk or negedge rst_n) begin if (!rst_n) begin bit_count 0; shift_out 0; shift_in 0; end else begin if (i2s_lrck 1b0) begin // 左聲道采樣周期 if (bit_count 0) begin left_out {shift_in[14:0], adc_dat}; // 串行轉(zhuǎn)并行累積 shift_out left_in; shift_in 0; end bit_count bit_count 1; shift_out {shift_out[14:0], 1b0}; dac_dat shift_out[15]; shift_in {shift_in[14:0], adc_dat}; end else begin // 右聲道采樣周期 // 邏輯與左聲道一致將 right_in / right_out 對(duì)應(yīng)替換 bit_count bit_count 1; end end end endmodule這段代碼的核心思路是在 LRCK 為低電平時(shí)每個(gè) BCLK 上升沿從adc_dat采一位寫入shift_in16 個(gè)位時(shí)鐘后得到一個(gè)完整的左聲道樣本發(fā)送側(cè)則把left_in載入shift_out每個(gè) BCLK 上升沿從最高位逐位移出到dac_dat。LRCK 翻轉(zhuǎn)后進(jìn)入右聲道周期邏輯相同。數(shù)據(jù)位寬是 16 位這也是 WM8731 的默認(rèn)字長(zhǎng)。bit_count計(jì)數(shù)到 16 時(shí)自動(dòng)歸零等待下一個(gè)聲道周期開始。需要留意的是I2S 數(shù)據(jù)是 MSB 先行且數(shù)據(jù)比 LRCK 翻轉(zhuǎn)晚一個(gè)位時(shí)鐘。這意味著發(fā)送側(cè)在 LRCK 變低后的第一個(gè) BCLK 上升沿不能立即發(fā)送數(shù)據(jù)的最高位而是要延時(shí)一拍。我在模塊里用shift_out預(yù)先載入數(shù)據(jù)在 LRCK 有效后的第二個(gè) BCLK 沿開始輸出就是為了滿足這個(gè)時(shí)序要求。如果這里不處理左右聲道的數(shù)據(jù)會(huì)整體錯(cuò)位一個(gè)位時(shí)鐘聽感上出現(xiàn)明顯的卡頓。4.3 頂層模塊把 I2C 與 I2S 拼接成完整通路頂層模塊的任務(wù)是實(shí)例化前面兩個(gè)子模塊把它們連接成一條可用的通路。I2C 控制器用 50MHz 時(shí)鐘驅(qū)動(dòng)I2S 收發(fā)器用 BCLK 和 LRCK 驅(qū)動(dòng)。這里有個(gè)容易踩坑的地方——不同時(shí)鐘域的信號(hào)不能直接混用。I2C 的done標(biāo)志是 50MHz 時(shí)鐘域的信號(hào)I2S 的left_out數(shù)據(jù)是 BCLK 時(shí)鐘域的信號(hào)如果直接互相賦值綜合工具會(huì)報(bào)跨時(shí)鐘域警告而且要擔(dān)心亞穩(wěn)態(tài)問(wèn)題。module audio_top ( input clk_50m, // 板載 50MHz 時(shí)鐘 input rst_n, // 復(fù)位按鈕 inout AUD_I2C_SDAT, // WM8731 I2C 數(shù)據(jù)線 output AUD_I2C_SCLK, // WM8731 I2C 時(shí)鐘線 output AUD_MCLK, // WM8731 主時(shí)鐘 input AUD_ADC_DAT, // ADC 數(shù)據(jù)輸入 output AUD_DAC_DAT, // DAC 數(shù)據(jù)輸出 output AUD_BCLK, // 位時(shí)鐘 output AUD_LRCK // 幀同步 ); wire cfg_done; wire [15:0] adc_left, adc_right; wire [15:0] dac_left, dac_right; // 相位累加器產(chǎn)生 MCLK18.432MHz用 PLL 或計(jì)數(shù)器分頻產(chǎn)生 assign AUD_LRCK bclk_div ? ... // 從 BCLK 計(jì)數(shù)器產(chǎn)生 48kHz 幀同步 i2c_master u_i2c ( .clk(clk_50m), .start(start_config), .data(config_data), .done(config_done), .scl(AUD_I2C_SCLK), .sda(AUD_I2C_SDAT) ); i2s_transceiver u_i2s ( .clk(clk_50m), .rst_n(rst_n), .i2s_bclk(AUD_BCLK), .i2s_lrck(AUD_LRCK), .adc_dat(AUD_ADC_DAT), .dac_dat(AUD_DAC_DAT), .left_out(adc_left), .right_out(adc_right), .left_in(dac_left), .right_in(dac_right) ); assign dac_left adc_left; // 直通回環(huán) assign dac_right adc_right; endmodule頂層模塊里最容易被忽視的是 MCLK 的產(chǎn)生。WM8731 需要一個(gè)主時(shí)鐘輸入頻率必須是采樣率的整數(shù)倍常見的有 12.288MHz 和 18.432MHz。DE2 板上有 50MHz 晶振直接分頻得不到這兩個(gè)頻率所以項(xiàng)目里用 FPGA 內(nèi)置 PLL 來(lái)生成。如果你用的開發(fā)板沒有音頻專用晶振這一步是跑不通的。一個(gè)替代方案是把 WM8731 工作在自動(dòng)時(shí)鐘檢測(cè)模式通過(guò)芯片內(nèi)部 PLL 從 BCLK 恢復(fù)主時(shí)鐘但這樣配置寄存器和 MCLK 引腳連接都要改不推薦第一次做就選這條路。5. 常見問(wèn)題排查音頻回環(huán)不出聲的五個(gè)典型原因5.1 現(xiàn)象是靜音原因是 I2C 配置根本沒送進(jìn)去上板之后最典型的失敗現(xiàn)象是無(wú)論怎么調(diào)整音量旋鈕耳機(jī)里一點(diǎn)聲音都沒有。先不急著懷疑代碼先看 I2C 波形。把邏輯分析儀接到AUD_I2C_SCLK和AUD_I2C_SDAT上抓上電瞬間的信號(hào)。如果 SCL 沒有時(shí)鐘或者 SDA 一直為高說(shuō)明 I2C 控制器沒有工作。這時(shí)檢查復(fù)位信號(hào)是否釋放、狀態(tài)機(jī)是否卡在 IDLE、start脈沖是否正確產(chǎn)生。有時(shí)候 I2C 波形正常但 WM8731 還是不工作這時(shí)候就要檢查 ACK 位。芯片正確應(yīng)答時(shí)會(huì)在第 9 個(gè)時(shí)鐘周期把 SDA 拉低如果 SDA 一直高說(shuō)明芯片根本沒收到完整數(shù)據(jù)。常見原因是 I2C 地址錯(cuò)誤——WM8731 的 CSB 引腳接地時(shí)地址是 0x1A接高電平是 0x1B如果板子上的地址引腳被拉高而你用了 0x1A芯片自然不會(huì)回應(yīng)。這個(gè)坑我見過(guò)多次。解決用萬(wàn)用表量一下 CSB 引腳電平或者干脆在代碼里把兩個(gè)地址都試一遍。5.2 現(xiàn)象是刺耳噪聲原因是左右聲道數(shù)據(jù)錯(cuò)位另一種常見故障是音頻能出聲但聲音像機(jī)器人說(shuō)話一樣機(jī)械且斷續(xù)或者左右聲道明顯錯(cuò)位。這種問(wèn)題基本出在 I2S 時(shí)序?qū)R上。I2S 協(xié)議規(guī)定數(shù)據(jù)比 LRCK 翻轉(zhuǎn)晚一個(gè) BCLK如果發(fā)送模塊在 LRCK 翻轉(zhuǎn)的同一個(gè)沿就送出第一位數(shù)據(jù)接收端會(huì)把前一個(gè)聲道的最后一位當(dāng)成新聲道的起始位整個(gè)數(shù)據(jù)流錯(cuò)位一位。解決在發(fā)送模塊里增加一拍延時(shí)確保第一個(gè) bit 出現(xiàn)在 LRCK 翻轉(zhuǎn)后的第二個(gè) BCLK 沿。還有一種錯(cuò)位是左右聲道數(shù)據(jù)互換聽起來(lái)像聲場(chǎng)翻轉(zhuǎn)但聲音本身是連續(xù)的。這是 LRCK 極性接反了。WM8731 的 LRCK 高電平對(duì)應(yīng)右聲道如果你在代碼里把高電平當(dāng)成了左聲道左右就換過(guò)來(lái)了。檢查一下接收模塊里處理 LRCK 電平的分支邏輯交換兩個(gè)分支即可。5.3 現(xiàn)象是電流底噪原因是模擬地與數(shù)字地處理不當(dāng)如果聲音能出來(lái)但伴隨持續(xù)的白噪聲或 50Hz 交流聲且音量隨輸入增益增大而增大那問(wèn)題大概率在模擬前端。WM8731 是混合信號(hào)芯片模擬地和數(shù)字地雖然是同一個(gè)封裝但 PCB 上通常分開鋪設(shè)。DE2 板上已經(jīng)做了隔離我們能做的有限但有一點(diǎn)必須注意耳機(jī)插頭地線和 LINE IN 地線如果接觸不良噪聲會(huì)異常明顯。這種情況在開發(fā)板上沒有太好的軟件優(yōu)化手段可以試著降低輸入增益減少模擬前端的噪聲放大。如果是 FPGA 側(cè)帶來(lái)的噪聲檢查 MCLK 的質(zhì)量。MCLK 抖動(dòng)過(guò)大會(huì)直接惡化 ADC 和 DAC 的時(shí)鐘同步精度導(dǎo)致輸出波形毛刺增多。用示波器看 MCLK 引腳如果上升沿不陡峭或存在明顯抖動(dòng)檢查 PLL 配置是否正確。我在項(xiàng)目里把 PLL 的輸出時(shí)鐘定為 18.432MHz并且加了一級(jí) BUFG 全局時(shí)鐘緩沖確保到 WM8731 的時(shí)鐘路徑足夠干凈。5.4 現(xiàn)象是上電后必須按復(fù)位才發(fā)聲原因是初始化時(shí)序沒做完有些開發(fā)者會(huì)遇到一種奇怪現(xiàn)象每次上電后需要手動(dòng)按一下復(fù)位按鈕音頻通路才正常。這說(shuō)明 WM8731 的初始化流程在時(shí)序上落后于上電I2C 配置是在復(fù)位釋放之前就開始發(fā)送或者復(fù)位釋放后 FPGA 立刻去讀 ADC 數(shù)據(jù)而芯片還沒來(lái)得及完成內(nèi)部初始化。解決在復(fù)位釋放后加一個(gè)計(jì)數(shù)器延時(shí)至少等待 10ms 讓 WM8731 的模擬電路穩(wěn)定再啟動(dòng) I2C 配置流程。這個(gè)延時(shí)用 50MHz 時(shí)鐘計(jì)數(shù)很容易實(shí)現(xiàn)500000 個(gè)時(shí)鐘周期就是 10ms。5.5 現(xiàn)象是采樣率聽起來(lái)不對(duì)原因是幀同步頻率偏了最后一種現(xiàn)象是音頻能播放但音調(diào)明顯偏高或偏低。這說(shuō)明AUD_LRCK的頻率不是 48kHz。LRCK 是 BCLK 分頻產(chǎn)生的BCLK 如果由 PLL 產(chǎn)生要檢查 PLL 分頻參數(shù)是否算錯(cuò)。比如 18.432MHz 的 MCLK 如果被誤當(dāng)成 50MHz 來(lái)分頻得到的 LRCK 頻率會(huì)差近三倍。這種情況下音樂速率變得離譜基本可以斷定是分頻參數(shù)的問(wèn)題。用頻率計(jì)或示波器測(cè)一下 LRCK 的實(shí)際頻率把它調(diào)回 48kHz 附近即可。6. 二次開發(fā)給音頻通路加一個(gè)實(shí)時(shí)音量調(diào)節(jié)和數(shù)字增益6.1 在數(shù)據(jù)通路上插入乘法器實(shí)現(xiàn)音量控制基礎(chǔ)回環(huán)跑通之后最能體現(xiàn) FPGA 優(yōu)勢(shì)的改造是加數(shù)字增益控制。既然 ADC 輸出的數(shù)據(jù)已經(jīng)以 16 位并行形式到達(dá) FPGA完全可以在直通鏈路上插入一個(gè)乘法器用撥碼開關(guān)控制增益系數(shù)實(shí)現(xiàn)數(shù)字音量調(diào)節(jié)。設(shè)計(jì)思路如下把左右聲道的left_in和right_in分別乘以一個(gè)可配置的增益系數(shù)乘法結(jié)果截位后送入 DAC。增益系數(shù)用一個(gè) 16 位寄存器保存每撥動(dòng)一次撥碼開關(guān)寄存器加或減一個(gè)固定步進(jìn)。這個(gè)改造有個(gè)典型陷阱16 位乘以 16 位得到 32 位結(jié)果如果直接把低 16 位送給 DAC聲音會(huì)嚴(yán)重失真甚至爆音。正確做法是保留高 16 位或者做帶符號(hào)飽和處理。我一般這樣處理乘法結(jié)果右移 8 位再判斷是否溢出若溢出則鉗位到最大正值或最小負(fù)值。這樣音量調(diào)節(jié)的步進(jìn)是 1/256足夠細(xì)膩同時(shí)避免了截位帶來(lái)的削波噪聲。6.2 順著通路加一個(gè)簡(jiǎn)單的低通濾波驗(yàn)證可擴(kuò)展性音量控制之外另一條值得動(dòng)手的路徑是插一個(gè)數(shù)字濾波器。在i2s_transceiver的數(shù)據(jù)輸出端串聯(lián)一個(gè)一階 IIR 低通濾波器系數(shù)固定截止頻率設(shè)在 8kHz 左右。這個(gè)改動(dòng)只涉及組合邏輯和一個(gè)狀態(tài)寄存器不需要改 I2C 配置和 I2S 時(shí)序。濾波器結(jié)構(gòu)是標(biāo)準(zhǔn)的差分方程y[n] y[n-1] alpha * (x[n] - y[n-1])alpha 取 0.2 左右。乘法和加法都放在 BCLK 域內(nèi)注意控制時(shí)序避免產(chǎn)生組合邏輯環(huán)路。這樣做的好處是驗(yàn)證 FPGA 處理信號(hào)的能力已經(jīng)從「搬運(yùn)數(shù)據(jù)」進(jìn)化到「變換數(shù)據(jù)」而這正是后續(xù)做均衡器、回聲、降噪算法的第一步。把 alpha 系數(shù)也做成可配置就能在運(yùn)行時(shí)切換濾波效果。再往后如果對(duì)時(shí)延不敏感還可以把數(shù)據(jù)緩存到片內(nèi) RAM 做延遲效果配合撥碼開關(guān)做簡(jiǎn)單的回聲。6.3 從直通到算法的改造驗(yàn)證方法每次改動(dòng)后最直接的驗(yàn)證方法是播放一段自己熟悉的音樂——最好是對(duì)人聲和低頻都有清晰辨識(shí)度的曲目然后切換增益系數(shù)聽音量變化是否線性切換濾波器參數(shù)聽高頻是否明顯衰減。用示波器量 DAC 輸出腳的波形也能看出濾波器的痕跡原始方波信號(hào)經(jīng)過(guò)低通濾波后邊緣會(huì)變得圓滑。我從這個(gè)項(xiàng)目里學(xué)到的最重要一課是FPGA 工程調(diào)試永遠(yuǎn)先從鏈路末端往前查。音頻聲卡無(wú)聲先摸 I2CI2C 通了查幀同步幀同步對(duì)了我才去動(dòng)信號(hào)處理邏輯。順序一亂就會(huì)陷入同時(shí)懷疑所有模塊的狀態(tài)誰(shuí)也查不出來(lái)。從那以后我每加一個(gè)模塊都會(huì)在它入口打一個(gè)debug標(biāo)志先確認(rèn)真數(shù)據(jù)進(jìn)來(lái)了再看它出去的是什么。這個(gè)習(xí)慣讓我少走很多彎路希望也能幫你在調(diào) FPGA 音頻的時(shí)候少踩幾個(gè)坑。本文還有配套的精品資源點(diǎn)擊獲取