资讯动态

FPGA音频回环实现:基于DE2板与WM8731的I2S/I2C全流程设计

发布时间:2026/10/20 6:36:55 来源:尧图企业网站定制
简介这是一份基于ALTERA DE2开发板EP2C35F672C6与WM8731音频编解码器的FPGA音频输入输出示例工程面向数字逻辑与嵌入式音频学习者演示了模拟音频经ADC数字化、FPGA内部处理后由DAC还原输出的完整链路。工程内包含重新编写的简化从机模式代码便于理解I2S接口时序与音频数据流的控制方式。压缩包共133个文件以Verilog源码v、Quartus工程与配置文件qpf/qsf、编译下载文件sof/pof、编译报告rpt及大量Quartus辅助数据库为主整体约911KB属于轻量级可运行工程。目前已有826人浏览学习。通过该工程可掌握FPGA与WM8731在从机模式下的连接方法了解采样率、位深与时钟生成的配合关系并对照精简代码梳理ADC/DAC的完整数据通路适合作为FPGA音频方向入门及二次开发的参考。1. 为什么这块 DE2 学习板要做一条音频回环通路拿到 DE2 开发板板上那颗 FPGA 芯片是 EP2C35F672C6旁边焊接的 WM8731 音频 Codec 是最容易被低估的器件。大多数入门例程只能演示「放一首歌」但真要把「音频输入 - WM8731 ADC - FPGA - WM8731 DAC - 音频输出」这条链路做成自己的工程很多人第一步就卡住了WM8731 上电后并不工作它需要先用 I2C 写完寄存器音频数据还得按 I2S 时序进出三路时钟的频率关系也要完全匹配。这篇文章把整套方案从头拆到尾适合正在做数字逻辑课设、音频相关毕设或者想把板载音频接口真正用于数字信号处理的人。读完你能直接拿出一份覆盖引脚设计、Verilog 模块、寄存器配置到验收标准的完整方案。2. 硬件链路与时钟规划WM8731 六根信号线的引脚级设计2.1 板级信号通路DE2 与 WM8731 之间只有十根线WM8731 是一颗低功耗立体声音频编解码器内部既有 ADC 也有 DAC模拟侧支持 Line In、麦克风输入和耳机输出数字侧则分成两个接口音频数据传输用的串行接口以及寄存器配置用的控制接口。对 FPGA 设计者来说真正需要关心的信号其实很少引脚级连接如下。信号名方向作用AUD_XCKFPGA - WM8731主时钟 MCLK决定采样率基准AUD_BCLKFPGA - WM8731位时钟每个时钟沿对应一个数据位AUD_ADCLRCKFPGA - WM8731ADC 左右声道帧时钟AUD_DACLRCKFPGA - WM8731DAC 左右声道帧时钟AUD_ADCDATWM8731 - FPGAADC 串行 PCM 数据输出AUD_DACDATFPGA - WM8731DAC 串行 PCM 数据输入AUD_I2C_SCLK / SDATFPGA - WM8731I2C 控制接口负责写寄存器DE2 板在设计时已经把这组信号从 WM8731 引到了 FPGA 的通用 IO 上所以不需要自己画 PCB也不用关心模拟前端怎么搭。你要做的只是在顶层模块里把同名端口声明出来然后在引脚约束文件里做映射。DE2 板的用户手册会给出完整的引脚对应表不同批次可能略有差异实际做的时候一定要以自己手上这块板子的手册为准不要直接抄网上的某个工程文件。2.2 MCLK、BCLK、LRCK 三种时钟的关系音频设计中三路时钟是最容易搞乱的地方。本方案统一采用 48kHz 采样率这时候三路时钟的频率是锁定的时钟频率来源说明MCLK (AUD_XCK)18.432 MHzFPGA PLL 输出WM8731 内部调制器主时钟BCLK3.072 MHzFPGA 逻辑分频64 × 48kHz每比特一个时钟LRCK48 kHzFPGA 逻辑分频左右声道帧时钟这里的关键是MCLK 与采样率的绑定关系由 WM8731 的 0x08 寄存器决定。18.432MHz 对应的采样率绑定值是 384 倍过采样也就是 384 × 48kHz 18.432MHz。BCLK 则固定为 64 倍采样率因为一帧包含左右两个声道、各 16bit正好 64 个时钟周期。听到有声音但音调不对八成就是这三路时钟的关系没对上。2.3 引脚约束与顶层端口设计顶层模块的端口设计可以直接按 DE2 板卡手册的命名来写。引脚约束的格式参考如下具体引脚号由你手里的板卡版本决定set_location_assignment PIN_xx -to AUD_XCK set_location_assignment PIN_xx -to AUD_BCLK set_location_assignment PIN_xx -to AUD_ADCLRCK set_location_assignment PIN_xx -to AUD_DACLRCK set_location_assignment PIN_xx -to AUD_ADCDAT set_location_assignment PIN_xx -to AUD_DACDAT set_location_assignment PIN_xx -to AUD_I2C_SCLK set_location_assignment PIN_xx -to AUD_I2C_SDAT顶层 Verilog 模块里端口声明和约束文件里的名字必须完全一致大小写都不能差。我一般会把音频相关的信号集中放在一个区域方便综合后对照 Floorplan 检查。这里值得注意的一点是WM8731 工作在从模式即 MCLK、BCLK、LRCK 全部由 FPGA 产生WM8731 只负责被动接收和发送数据。让 FPGA 统一产生三路时钟时序关系完全可控也能避免异步时钟域带来的数据采样不确定问题。3. FPGA 内部设计I2S 收发模块与直通数据通路3.1 顶层模块划分时钟、I2S、I2C 三块FPGA 侧的逻辑分成三个可独立验证的模块。第一个是时钟生成模块用 PLL 把板载 50MHz 晶振转换成 18.432MHz再通过计数器分频出 BCLK 和 LRCK。第二个是 I2C 配置模块上电后自动把七个寄存器依次写入 WM8731。第三个是 I2S 数据通路包含接收和发送两个方向。这个划分方式的核心思想是让每个模块都能单独仿真和测试时钟模块先验证频率I2C 模块先验证 ACK 应答I2S 数据通路最后接上。在实际项目里先分模块验证再集成比直接写一个大顶层然后上电调要快得多排查问题也不用从头到尾翻代码。3.2 I2S 接收模块ADCDAT 的采样与左右声道拆分I2S 是一种串行 PCM 传输协议LRCK 低电平表示左声道高电平表示右声道数据在 BCLK 的边沿传递。WM8731 的 ADC 在 FPGA 提供的 BCLK 上升沿输出数据所以接收端在 BCLK 上升沿采样 ADCDAT 即可。module i2s_rx #( parameter DATA_W 16 )( input wire bclk, input wire lrclk, input wire adcdat, output reg [DATA_W-1:0] left_data, output reg [DATA_W-1:0] right_data, output reg data_valid ); reg [4:0] bit_cnt; // 一帧 32 个 BCLK左右声道各 16bit always (posedge bclk) begin if (bit_cnt 5d31) bit_cnt 5d0; else bit_cnt bit_cnt 1b1; end always (posedge bclk) begin // I2S 标准有 1 位延迟从第 2 个 BCLK 开始收有效数据 if (bit_cnt 5d0) begin data_valid 1b0; end else if (bit_cnt 5d1 bit_cnt DATA_W) begin if (!lrclk) left_data[DATA_W - bit_cnt] adcdat; else right_data[DATA_W - bit_cnt] adcdat; end else if (bit_cnt DATA_W 1) begin data_valid 1b1; // 后级模块可以在这个节拍锁存数据 end end endmodule这个模块用 bit_cnt 对一帧内的 32 个位时钟计数。注意 I2S 协议规定 LRCK 跳变之后先空一个 BCLK真正的 MSB 从第 2 个 BCLK 开始出现所以代码里 bit_cnt 为 1 时采样的是最高位bit_cnt 为 16 时采样的是最低位。data_valid 信号的作用是给后级一个明确的锁存节拍避免在数据还没收完时就去读 left_data 和 right_data。3.3 I2S 发送模块把并行数据重新串行化发送方向要做的恰好是接收的反过程把 16 位并行数据按照同样的帧结构一位一位地送到 DACDAT。WM8731 的 DAC 在 BCLK 上升沿采样输入数据所以发送端应该在 BCLK 下降沿更新 DACDAT保证数据在上升沿到来时已经稳定。module i2s_tx #( parameter DATA_W 16 )( input wire bclk, input wire lrclk, input wire [DATA_W-1:0] left_data, input wire [DATA_W-1:0] right_data, output reg dacdat ); reg [4:0] bit_cnt; always (posedge bclk) begin if (bit_cnt 5d31) bit_cnt 5d0; else bit_cnt bit_cnt 1b1; end always (negedge bclk) begin if (bit_cnt 5d1 bit_cnt DATA_W) begin if (!lrclk) dacdat left_data[DATA_W - bit_cnt]; else dacdat right_data[DATA_W - bit_cnt]; end else begin dacdat 1b0; end end endmodule发送模块和接收模块的 bit_cnt 计数逻辑完全一致这保证了数据从接收端到发送端的对齐关系。这里有一个容易踩的坑如果发送模块用的是 posedge bclk 输出数据DAC 在上升沿采样时可能采到正在变化的数据听感上会出现类似金属声的杂音。用下降沿输出、上升沿采样是 I2S 传输的标准做法也是答辩时值得讲清楚的细节。3.4 直通通路不加任何处理先打通链路最基础的验证方式是直通把 i2s_rx 输出的 left_data 直接接到 i2s_tx 的 left_dataright_data 同样直连不做任何算法处理。这样做的好处是把 FPGA 的逻辑功能先排除掉——如果直通模式下耳机里能听到清晰的输入声音说明 WM8731 的 ADC、DAC、寄存器配置以及 FPGA 的 I2S 时序全部正确。如果想让方案更有内容可以在直通的基础上加一个简单的音量控制用拨码开关输入 0~3 的档位把 16 位数据右移对应位数实现 0dB、-6dB、-12dB、-18dB 的衰减。右移实现比乘法器简单也不会引入截位噪声适合作为课设的扩展功能。4. WM8731 的 I2C 初始化七个寄存器搞定音频通路4.1 关键寄存器配置表WM8731 的控制接口是 I2C每个控制字由 7 位寄存器地址和 9 位数据组成。本方案采用 18.432MHz 的 MCLK按照 48kHz 采样率来配置完整的初始化序列如下寄存器地址写入值作用0x000x0097左声道 Line In 音量 0dB关闭输入静音0x020x0079左声道耳机输出约 0dB解除 DAC 软静音0x040x0012模拟路径DAC 输出接通耳机放大器0x060x0000数字路径默认关闭高通滤波与去重0x080x0068采样率 48kHzMCLK 18.432MHz384fs0x0A0x0002数字音频接口 I2S、16 位0x0C0x0001激活接口Codec 开始正常工作这里最需要注意的是 0x08 寄存器和 MCLK 频率的联动关系。0x0068 这个值只有在 MCLK 为 18.432MHz 时才对应 48kHz 采样率如果你的 PLL 输出是 12.288MHz那 0x08 必须改成 0x0000。网上不少例程直接抄寄存器配置没有检查 PLL 输出频率结果声音明显变调这个问题在后面的排查章节还会详细展开。4.2 I2C 写状态机实现I2C 传输的起始条件是 SCL 为高电平时 SDA 产生下降沿终止条件是 SCL 为高电平时 SDA 产生上升沿。WM8731 的 7 位设备地址是 0x1A加上写方向位后第一字节为 0x34。每个寄存器控制字拆成两个字节发送第一字节是高 7 位寄存器地址和 9 位数据中的最高位第二字节是数据低 8 位。module i2c_cfg ( input wire clk, input wire rst_n, output reg scl, output reg sda, output reg cfg_done ); localparam SCL_DIV 16d250; // 50MHz 分频得到 100kHz localparam REG_NUM 7d7; reg [6:0] reg_idx; reg [8:0] data_buf; reg [3:0] bit_cnt; reg [1:0] state; reg [8:0] scl_cnt; localparam S_IDLE 2d0, S_START 2d1, S_DATA 2d2, S_ACK 2d3; // 控制字列表{寄存器地址[6:0], 数据[8:0]} reg [15:0] cfg_table [0:REG_NUM-1]; initial begin cfg_table[0] 16h0097; cfg_table[1] 16h0079; cfg_table[2] 16h0012; cfg_table[3] 16h0000; cfg_table[4] 16h0068; cfg_table[5] 16h0002; cfg_table[6] 16h0001; end // 状态机主体每个状态内控制 scl 和 sda 的电平翻转 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state S_IDLE; scl 1b1; sda 1b1; reg_idx 7d0; cfg_done 1b0; end else begin case (state) S_IDLE: begin if (reg_idx REG_NUM) begin // 取出寄存器地址和数据准备发送 data_buf {cfg_table[reg_idx][15:9], cfg_table[reg_idx][8:0]}; bit_cnt 4d0; state S_START; end else begin cfg_done 1b1; end end S_START: begin // 起始条件SCL 高电平期间 SDA 拉低 scl 1b1; if (scl_cnt 9d0) sda 1b1; else if (scl_cnt 9d100) begin sda 1b0; state S_DATA; scl_cnt 9d0; end end S_DATA: begin // 先发设备地址 0x34再发控制字两字节 if (bit_cnt 8) begin // 每个 bit 在 SCL 低电平时更新 SDA if (scl_cnt 9d0) begin sda data_buf[8]; data_buf {data_buf[7:0], 1b0}; end ... end end endcase end end endmodule上面的代码为了展示核心结构做了简化实际完整实现时还需要在每个字节后插入 ACK 检测WM8731 在第 9 个时钟周期会把 SDA 拉低表示应答。建议用一个四段式状态机把发送 bit、等待 ACK、发送下一字节、结束条件分开处理。调通 I2C 的关键点有两个一是 SCL 频率不能超过 400kHz二是要记得设备地址第一字节是 0x34 而不是 0x1A后者是去掉写位之后的 7 位地址直接写 0x1A 到总线上是拿不到 ACK 的。4.3 初始化顺序先时钟后配置WM8731 上电之后内部处于待机状态必须完成寄存器配置并写入 0x0C 的激活位才会开始工作。推荐的初始化顺序是先让 FPGA 的 PLL 锁定并输出 18.432MHz 的 MCLK再运行 I2C 配置状态机最后把 0x01 写入 0x0C 寄存器激活 Codec。如果把配置放在 MCLK 稳定之前WM8731 内部状态机可能无法正确锁存控制字。实际工程里我会在 I2C 配置完成后再延时 20ms确保 Codec 内部模拟部分稳定再开始往 DAC 送音频数据。5. 常见问题排查静音、变调、左右声道颠倒5.1 现象 - 原因 - 解决四条高频踩坑点第一条坑上电后耳机里完全没声音或者只有明显的底噪。原因是 WM8731 没有完成初始化最常见的表现是 0x0C 寄存器没有写入 0x01Codec 一直处于待机状态。解决方法是先确认 cfg_done 信号是否拉高然后把 I2C 的三字节发送过程用逻辑分析仪抓一遍逐帧核对设备地址和寄存器数据。第二条坑有声音但明显变调男声听着像卡通人物。原因是 0x08 寄存器的配置值与实际 MCLK 频率不匹配。MCLK 是 18.432MHz 却配了 12.288MHz 对应的 0x0000导致采样率被错误解释。解决方法是不要凭感觉猜 MCLK直接用示波器量 FPGA 输出到 AUD_XCK 的实际频率再对照 WM8731 数据手册里的采样率表选择正确的寄存器值。第三条坑左右声道颠倒或者只有一个声道有声。左右颠倒通常是 LRCK 电平极性理解反了标准 I2S 是低电平左声道有些例程里写成高电平左声道把 i2s_rx 模块里的判断条件取反就能解决。只有单声道出声多半是只配置了 0x02左声道耳机输出漏掉了右声道对应的 0x03 寄存器补上配置即可。第四条坑I2C 发了数据但设备完全不应答SDA 一直为高。原因是设备地址字节写错把 7 位地址 0x1A 直接当作第一个字节发送实际上 I2C 需要的是 0x34。另一个常见原因是 SCL 频率太高WM8731 的 I2C 接口无法跟上把分频系数调大、让 SCL 降到 100kHz 再试。5.2 排查工具与手段先时钟再配置最后看数据音频通路调试有一个固定的排查顺序按这个顺序走能快速定位问题。第一步用示波器验证 AUD_XCK、AUD_BCLK、AUD_LRCK 三路时钟频率分别应该是 18.432MHz、3.072MHz、48kHz频率不对直接改时钟模块不用往下查。第二步验证 I2C抓 SCL 和 SDA 的波形确认每个字节后都有 ACK 低电平。第三步验证数据通路把 ADCDAT 抓出来看是否有稳定的电平翻转如果 ADC 输出恒定不变问题基本在寄存器配置或模拟输入。最后一步才接耳机试听避免用听觉判断错误问题。6. 验收与答辩用 1kHz 正弦波把方案讲清楚6.1 三个验收测试音频系统最怕主观评价所以验收要围绕可测量的信号来做。准备一个能输出 1kHz 正弦波的信号源或者直接用手机 App 播放测试音频按下面的表格逐项验证。测试项输入观察点通过标准频率正确性1kHz 正弦波 LINE IN耳机输出接示波器输出频率在 990Hz~1010Hz 之间波形保真度1kHz 正弦波幅度中等示波器观察输出波形无明显削顶、无可见毛刺左右声道一致性左右声道分别输入不同频率逐声道试听或双通道示波器左右声道均正常且互不串扰频率测量是最有力的验收证据。如果示波器能读出 1kHz说明时钟树和采样率配置全部正确如果读数是 667Hz 或 750Hz 之类的值说明 0x08 寄存器与 MCLK 不匹配。波形无削顶则说明模拟路径增益设置合理没有过载。6.2 答辩时的讲解重心答辩时不要从 Verilog 代码逐行讲评委想听的是设计决策过程。建议按三条主线组织讲解第一讲时钟树说清楚 18.432MHz、3.072MHz、48kHz 三路时钟从哪来、为什么是这种倍数关系第二讲 I2C 初始化展示寄存器配置表说明 0x08 与 MCLK 的绑定关系第三讲 I2S 数据对齐重点解释发送端为什么在 BCLK 下降沿输出数据。准备一页幻灯片放上示波器实测的三时钟频率截图再放一组 1kHz 输入输出的对比波形整体说服力会强很多。我每次做音频项目都会在调 I2C 之前先花十分钟把三路时钟量一遍这个习惯帮我避免过多次“以为 Codec 坏了最后发现是时钟分频写错”的尴尬。先时钟、再配置、后数据按这个顺序走完方案基本不会翻车希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑