尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
FPGA音频回环设计:WM8731的I2S时序详解与踩坑指南
简介一份面向FPGA数字音频设计学习者的工程资料以Altera DE2-EP2C35F672C6开发板为平台借助WM8731立体声编解码器实现音频输入经ADC数字化后送入FPGA处理再通过DAC还原输出的完整信号链。工程包含WM8731从模式配置、I2S串行音频接口时序控制以及简洁的Verilog顶层设计适合作为数字逻辑与嵌入式音频接口的课程设计参考。资源压缩包内含133个文件整体约911KB主要文件类型包括Verilog源码(.v)、Quartus工程与设置文件(.qpf/.qsf)、FPGA配置文件(.sof/.pof)以及大量编译辅助文件与说明文本导入Quartus后即可查看和调试整个工程。相比DE2官方示例作者重写了一套更精简的代码slave_mode_audio目录结构清晰便于对照学习ADC采集、FPGA内部处理、DAC输出的数据流控制并可在DE2实验板上直接验证。已有826人学习浏览适合快速理解FPGA音频编解码器应用也是数字系统综合实验的理想参考。1. FPGA 音频回环的真相WM8731 的数据不是“文件”是流水很多人第一次拿到某 FPGA 开发板上的音频编解码器都会以为回环实验的难度在“音频数据怎么存进 FPGA”。实际完全相反WM8731 这类芯片在 I2S 模式下根本没有“数据包”ADC 把模拟电压变成一串连续的位流DAC 又把位流恢复成模拟电压FPGA 夹在中间只负责在正确的时间点把位流接住、转个方向再送出去。这里没有校验和、没有缓冲、没有重传机制只要位流错了一个 BCLK 周期扬声器里就是爆音或静音。所以整个回环的核心矛盾是你处理的不再是“一段数据”而是“一个一直在流动的波形”。FPGA 必须在纳秒级把输入侧采样到的比特搬运到输出侧的时间槽位里。WM8731 本身承担了几乎所有模拟活儿——前置放大、Σ-Δ 调制、抗混叠滤波留给 FPGA 的只有三根线位时钟 BCLK、左右声道时钟 LRCK、串行数据线。搞懂这层机制你才能解释为什么有些回环工程“能出声但音高不对”为什么“寄存器里音量已经调到最大还是静音”也才能真正理解 FPGA 在音频链路里的角色不是计算器而是时间搬运工。2. 引脚级通路WM8731 音频编解码器挂在 FPGA 的什么位置2.1 三根串行线加一个主时钟就是全部数字接口先从引脚看起。某开发板上 FPGA 与 WM8731 之间真正参与音频数据流动的只有以下信号整理成一张表信号名方向以 FPGA 为参考作用AUD_BCLK输出位时钟每一位串行数据对应一个 BCLK 周期AUD_DACLRC输出帧时钟决定当前位流属于左声道还是右声道AUD_ADCLRC输出帧时钟与 DACLRC 同频同相告诉 FPGA 当前 ADC 送来的位属于哪个声道AUD_DACDAT输出送给 DAC 的串行数据AUD_ADCDAT输入ADC 送来的串行数据AUD_XCK输出编解码器主时钟通常取采样率的 256 倍注意第一件事ADC 和 DAC 的左右时钟是两根独立引脚。很多人直接用导线把 DACLRC 和 ADCLRC 短接这在多数情况下可以工作但严格来说它们的时序相位可以由 FPGA 分别控制。开发板上 WM8731 的 ADC 与 DAC 各有一组模拟输入输出通常 Line In 接 ADC耳机输出接 DAC。WM8731 的 I2S 接口是标准的 4 线制BCLK、LRCK、DAT、主时钟 XCK。其中 DAT 是双向时分的左声道的 32 个 BCLK 周期里送出左声道数据右声道的 32 个 BCLK 周期里送出右声道数据。FPGA 要靠 LRCK 的电平判断当前位属于哪个声道——这个判断一旦差一个 BCLK左右声道就互换或者完全变成噪声。2.2 为什么不用并行 ADCI2S 的代价是“时间对齐”初学者会问WM8731 内部明明有 ADC 和 DAC为什么不能像 SRAM 那样给个地址、读出来原因在于音频是连续时间信号采样率 48kHz 意味着每秒钟要传 48 组立体声样本每组 32 bit总共 1.536Mbit/s。并行接口可以做到但需要至少 16 根数据线加一根采样时钟PCB 布线复杂度急剧上升而且并行数据在高速翻动时会产生严重的同步开关噪声。I2S 用一根数据线换三根控制线用时间换空间。代价是 FPGA 内部必须维持一个与 BCLK 严格对齐的移位逻辑。常见的实现思路是用 BCLK 上升沿采样输入下降沿更新输出保证数据的建立保持时间。这样设计的原因很直接WM8731 手册里写明数据在 BCLK 下降沿变化、上升沿被锁存两边按同一个时钟沿工作才能避免亚稳态。这块板子的 WM8731 模拟输入输出特性也值得关注Line In 进入 ADC 前有一级可编程增益放大器寄存器里可以调 0 到 31 级音量耳机输出则内置了耳机驱动放大器可以直接推 32 欧耳机。这意味回环工程的数字部分失败时现象可能是“有模拟底噪但毫无数字信号”两类问题可以快速区分。3. I2C 配置与 I2S 收发配置是毫秒级时序是纳秒级3.1 I2C 配置状态机WM8731 的上电初始化顺序WM8731 的控制接口不是 I2S而是独立的 I2C7 位地址是 0x1A。FPGA 通过 I2C 写它的 16 个寄存器每个寄存器 7 位地址加 9 位数据。配置最难的不是 I2C 时序本身而是寄存器之间的依赖关系采样率寄存器没设对I2S 频率再准也白搭模拟路径没选 DAC数字位流根本到不了耳机放大器。一个典型的初始化序列如下按这个顺序写寄存器才能避免爆音寄存器地址写入值含义0x0F0x0000复位所有寄存器0x000x0017左声道 Line In 输入启用增益约 0dB0x020x0079左声道耳机输出解除静音音量 0dB0x040x0010模拟路径选择 ADC 到 DAC关闭旁路0x060x0000数字音频路径不做衰减不去加重0x080x0000采样率 48kHz普通模式主时钟 256fs0x0A0x0002数字接口格式 I2S16 位从模式0x0C0x0001激活接口使能数字音频通路FPGA 端 I2C 主控制器一般用状态机实现核心是 SCL 拉低时改变 SDASCL 拉高时采样 SDA。这里给一个简化状态机框架module i2c_config #(parameter CLK_HZ 50_000_000) ( input wire clk, input wire start, output reg done, inout wire i2c_sda, output reg i2c_scl ); localparam TICK CLK_HZ / 100_000; // 100kHz I2C reg [15:0] tick_cnt; reg [6:0] reg_addr; reg [8:0] reg_data; reg [4:0] bit_cnt; reg [3:0] state; // 状态机简写IDLE - START - SEND_ADDR - WAIT_ACK - SEND_DATA - STOP // 每次 tick_cnt 计数到 TICK 产生一个 scl_tick代表一个位周期 always (posedge clk) begin if (tick_cnt TICK) begin tick_cnt 0; // 按位输出 sda先地址 0x1A1 写位 0再寄存器地址高 7 位再数据 9 位 end else begin tick_cnt tick_cnt 1; end end endmodule这段代码的逻辑要点是I2C 的每个位周期由 tick_cnt 分频产生而不是循环延时这样配置多个寄存器时状态机不会因为主机时钟频率变化而失配。实际使用时把寄存器序列放到一个 ROM 或 case 语句里状态机每写完一个寄存器后递增地址全部写完后拉高 done。注意 0x02 和 0x03 是一对立体声寄存器bit8 必须置 1 才会左右同时更新否则写下一条命令时另一个声道会被静音。3.2 I2S 收发用移位寄存器接住连续位流I2S 数据的物理格式是LRCK 为低时传输左声道为高时传输右声道每一帧 32 个 BCLK16 位有效数据前有一个 BCLK 的延迟也就是数据比 LRCK 边沿晚一个位时钟。这个“晚一位”是 I2S 与左对齐格式最大的区别也是 FPGA 采样时最容易出错的地方。FPGA 侧从一个 BCLK 开始捕获 ADC 数据的标准做法如下module i2s_capture ( input wire bclk, // 位时钟典型 1.536MHz input wire lrck, // 左右时钟典型 48kHz input wire adcdat, // ADC 串行数据 output reg [15:0] left_sample, output reg [15:0] right_sample ); reg [3:0] bit_cnt; reg [15:0] shift_reg; wire lrck_edge; reg lrck_d; always (posedge bclk) begin lrck_d lrck; // I2S 的 bit0 是最后一个数据位bit15 是 MSB shift_reg {shift_reg[14:0], adcdat}; if (bit_cnt 4d15) begin // 一个声道收满 16 bit锁存 if (lrck_d 1b0) left_sample shift_reg; else right_sample shift_reg; bit_cnt 0; end else begin bit_cnt bit_cnt 1; end end endmodule机制说明shift_reg 每个 BCLK 上升沿移入一位bit_cnt 从 0 数到 15数满就代表当前声道完成。lrck_d 是打了一拍的 LRCK用来判断刚收满的这个 16 位属于左还是右。这里的关键在于“打一拍”因为 I2S 数据比 LRCK 边沿晚一个 BCLK当你看到 LRCK 跳变时新的声道数据还没开始移位寄存器里仍是上一个声道的残留位直接用 lrck 而不是 lrck_d 判断会把声道切分点错位。发送方向完全对称只是把移位方向反过来并且在 BCLK 下降沿更新 DACDAT。由于 WM8731 采样上升沿FPGA 在下降沿给数据建立时间天然得到保证。3.3 主时钟分频从 50 MHz 到 12.288 MHz 到 1.536 MHzWM8731 要求 XCK 为采样率的整数倍。48kHz 采样率对应 256fs 就是 12.288MHzBCLK 则等于 32 倍采样率也就是 1.536MHz。这三个频率之间存在严格的倍数关系12.288MHz 除以 8 得到 1.536MHz1.536MHz 再除以 32 得到 48kHz。开发板的系统时钟通常是 50MHz不能直接整除 12.288MHz所以必须用 PLL 合成。常见做法是 PLL 分频出 12.288MHz 给 WM8731 的 XCK再用一个 3 位计数器分频产生 BCLK再用 5 位计数器分频产生 LRCK。分频器的宽度和计数边界决定了输出的抖动PLL 输出比直接用逻辑门分频抖动小一个数量级这也是为什么回环工程里不能省掉 PLL。这里有一个容易误导人的点从模式不代表 FPGA 可以随便给频率。从模式只决定数据线方向BCLK 和 LRCK 依然必须由 FPGA 生成并保持整数倍关系。如果把采样率寄存器设成 44.1kHz 而 XCK 仍给 12.288MHz解码器会按错误的系数工作听起来就是音调变慢或变快。4. 音频回环踩坑输入杂音、无声、音调不对的排查现场4.1 耳机里完全静音I2C 读写却正常现象逻辑分析仪能看到 I2C 每个寄存器都有 ACK代码状态机也跑完了但耳机一点声音没有。原因WM8731 上电后耳机输出寄存器默认值是静音状态只配置模拟路径和数字接口并不会自动打开耳机放大器。很多人的初始化序列漏写 0x02 和 0x03 这两个左右声道音量寄存器或者写入了但 bit8 没有置 1导致左声道更新了、右声道还停在静音。解决初始化序列里把 0x02 和 0x03 分别写成 0x0079并确认 bit8 已经置 1。写完后用 I2C 回读验证不要只看写时序有没有 ACK得到的值才是真的进到寄存器里的值。4.2 有“嘶嘶”底噪但没有任何音乐现象DAC 输出端能测到模拟噪声但不是音频信号像是白噪声叠加了微弱方波。原因BCLK 频率和 LRCK 频率根本不是整数倍关系。常见情况是把 PLL 输出直接接成了 BCLK12.288MHz 对编解码器来说太快DAC 根本解不出有效位流。白噪声说明 DAC 在非正常时钟下持续输出随机采样值。解决示波器量 AUD_BCLK先确认是 1.536MHz 而不是 12.288MHz再量 AUD_DACLRC确认是 48kHz。两者必须满足 32:1 的关系。如果 BCLK 正确、LRCK 正确再检查数据线是否有悬空引脚——FPGA 里没驱动的 IO 会浮空DAC 会把浮空电平当数据。4.3 音调不对播放 1kHz 变成 1.1kHz 左右现象输入给一个标准 1kHz 正弦波录出来的却是 1.1kHz听起来像是磁带转速偏快。原因WM8731 的采样率寄存器 0x08 与外部 XCK 不匹配。寄存器设为 48kHz但 XCK 实际输入的是 11.2896MHz也就是 44.1kHz 的 256 倍或者反过来代码里打开了 USB 模式位芯片内部时钟基准变了。解决确定板子上 PLL 实际生成的频率如果是 12.288MHz就只配 0x08 的低四位为 0x0禁止 BOSR 和 CLKODIV2。用频率计或示波器测 XCK 引脚别直接抄网上的工程——不同板子 WM8731 的 XCK 来源可能不同。4.4 左右声道反了或者两个声道完全相同现象单声道测试音从左边进结果从右边出来或者不管输入哪个声道左右输出都一样。原因I2S 数据流本身是分时复用的LRCK 低是左还是右完全由协议约定。如果 FPGA 在捕获端用 lrck_d 判断错了极性或者回环输出端把左声道样本发到了右声道时隙就会出现互换。两个声道相同则更隐蔽DACLRC 和 ADCLRC 用反了FPGA 在同一时刻采了两次同一通道。解决先用单声道信号源测试。把 1kHz 正弦接到 Line In 左声道观察 ADC 捕获后 left_sample 是否为有效波形如果是说明 FPGA 内部通道对齐无误如果 right_sample 才有波形说明 lrck_d 的极性判断反了要么改判断条件要么交换左右样本。两个声道相同的排查重点放在 DACLRC 与 ADCLRC 的相位用示波器双通道对比这两根线正常情况应完全同相。4.5 输入信号很弱且夹杂明显交流声现象回环能听到声音但音量很低把输出音量调满后依然不干净。原因Line In 的寄存器默认处于静音和低增益状态。WM8731 的模拟输入增益范围是从负增益开始的0x00 寄存器的低 5 位只有设到一定数值才有正增益更常见的坑是输入插的是 Line In但代码里没切换模拟输入选择位导致内部还在采样别的输入源。解决0x00/0x01 写入 0x0017确保 LINMUTE 位等于 0、音量位约等于 0dB同时检查 0x04 寄存器的输入选择位确认选到 Line In 而不是麦克风。交流声如果仍然明显重点查模拟地线回环工程的数字地和模拟地如果不共地底噪翻几倍是常态。5. 把回环变成可干预的处理节点全链路设计与验证5.1 更加完整的数据通路捕获、左右交换、可编程音量前面只做了捕获逻辑真正要在 FPGA 里做处理需要把左右声道并行数据取出、运算、再放回 I2S 发送时隙。下面这个回环模块把音量控制做成了简单的移位乘法不引入额外时钟域module audio_loopback ( input wire clk, // 50MHz 系统时钟 input wire bclk, // 1.536MHz 位时钟 input wire lrck, // 48kHz 帧时钟 input wire [15:0] left_in, input wire [15:0] right_in, output reg [15:0] left_out, output reg [15:0] right_out ); reg [4:0] volume; reg [15:0] left_scaled; reg [15:0] right_scaled; always (posedge clk) begin if (lrck ! lrck_d) begin volume volume 1; // 每帧调整一次音量便于观察效果 end end always (posedge clk) begin left_scaled {1b0, left_in[15:1]} {2b0, left_in[15:2]}; right_scaled {1b0, right_in[15:1]} {2b0, right_in[15:2]}; end always (posedge bclk) begin left_out left_scaled; right_out right_scaled; end endmodule这段代码的机制说明left_scaled 通过两次移位相加实现约 0.75 倍增益避免了乘法器。注意赋值符号前一个 always 块用阻塞赋值算组合逻辑最后一个 always 块用非阻塞赋值为输出打拍保证 I2S 发送侧拿到的是稳定的样本值。真正工程里这里会遇到一个隐性问题输入样本是在 BCLK 域捕获的音量调节逻辑跑在 50MHz 系统时钟域两个时钟域之间怎么交换数据我的建议是不要做跨时钟域直接把整个处理逻辑都跑在 BCLK 域。BCLK 1.536MHz 很低组合逻辑余量充足每个样本在 BCLK 上升沿被捕获在下一个 BCLK 上升沿被处理再到下降沿输出给 DAC完全不需要 FIFO。5.2 验证手段示波器看三个点频谱看一个指标回环工程调试时第一件事是确认 BCLK 和 LRCK 的数字波形第二件事是确认 DACDAT 上有数据翻转第三件事才是听声音。示波器探头接到 DACDAT 上如果看到稳定的多位翻转说明数据通路已经打通如果一片平坦说明 FPGA 没在发送数据。更量化的验证是用 1kHz 正弦波输入用示波器FFT功能测 DAC 输出。正常回环应该在 1kHz 处有 -3dB 左右的主峰谐波分量低于 -60dB。如果谐波分量明显偏高多半是 FPGA 侧位宽截断造成的16 位样本被中间运算截成了 14 位量化噪声会以谐波形式出现。排查办法是检查所有中间变量位宽任何一次赋值只要右值比左值窄就发生了截断。还有一个常被忽略的问题是回环延迟。FPGA 从 ADCDAT 采到样本到 DACDAT 输出最小延迟是 2 个 BCLK 周期但实际工程里因为解码、对齐、打拍会变成几十个 BCLK 周期。这个延迟不影响音质但如果以后要做降噪、回声消除这类实时处理就必须把延迟压到 5 个样本以内否则相位失真会让算法直接失效。5.3 为什么这样的回环能工作时基一致性是底线把整个链路从头到尾看一遍模拟信号进 WM8731 的 ADC量化成 16 位 PCM 码串行送进 FPGAFPGA 内的移位寄存器按 BCLK 节拍接住每一位处理完的样本再按同一个 BCLK 节拍送回 DACDAC 解码成模拟信号放大输出。这条链路没有任何地方存过一帧完整的音频数据所有环节都在“同一个时间网格”上滑动。这解释了一个反直觉的现象为什么寄存器配置全错、但 BCLK 和 LRCK 频率精确照样能听到声音因为数字音频解码器不知道你配错了采样率它只是机械地按 XCK 与 BCLK 的比值工作。反过来只要时基错了哪怕所有寄存器全对解码器也出不来正常声音。所以调回环的第一原则永远是先保证频率对再看数据对最后才谈音质。6. 设计取舍为什么用 WM8731 这颗编解码器而不是把 ADC、DAC 分开用6.1 分立方案与集成方案的对比如果自己搭一套 ADC DAC 电路用并行接口接 FPGA能获得更低的延迟和更高的带宽但代价是布线面积。16 位并行数据和模拟电路放在同一块板上同步开关噪声很难压住采样率一高就失效。WM8731 这类编解码器把模拟前端、ADC、DAC、耳机放大器、时钟管理全封装在一起PCB 上走的是 I2S 三根线数字噪声对模拟电路的干扰路径被大幅缩短。另一个取舍在接口协议上。I2S 是专门为连续音频流设计的BCLK、LRCK、DAT 三根线的职责划分非常清晰FPGA 端用移位寄存器就能收发几乎不需要协议状态机而像 SPI 这类通用接口每次传输都要地址、长度信息不适合做低延迟回环。WM8731 还把模拟路径控制做成寄存器而不是跳线意味着回环路径可以在旁路和数字回环之间切换这在调试时非常有用。6.2 时域换取确定性我后来复盘这个回环工程意识到整件事的成败其实归结为一个选择把 FPGA 设成 I2S 主模式还是让 WM8731 做主时钟。我最后选了前者让 FPGA 用 PLL 锁出 12.288MHz、派生 1.536MHz 和 48kHz代价是时钟抖动比编解码器内部晶振略大。但换来的确定性是巨大的——所有模块都跑到同一个时钟树上不会出现两个主设备互相等待的死锁。从那以后我每次做音频回环都会强制自己先测 BCLK 和 LRCK 的频率再初始化寄存器最后才插耳机。寄存器顺序也固定成“模拟路径 → 采样率 → 接口格式 → 激活 → 解除静音”五步再省事也不敢跳过。音频电路这个东西玄学往往藏在时序里把时基钉住了剩下的问题基本都能逻辑排查。希望这份记录能帮你在自己的 FPGA 音频实验里少走一段弯路。本文还有配套的精品资源点击获取
RELATED

相关推荐

OpenProject 开源项目管理快速上手:免费自托管,甘特图、看板、时间跟踪全都有

OpenProject 开源项目管理快速上手:免费自托管,甘特图、看板、时间跟踪全都有

OpenProject 开源项目管理快速上手:免费自托管,甘特图、看板、时间跟踪全都有 【免费下载链接】openproject OpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alt…

📅 2026/10/20 10:31:12
超越Bridge:全面掌握Docker网络隔离与通信的底层逻辑!

超越Bridge:全面掌握Docker网络隔离与通信的底层逻辑!

文章目录本篇摘要一.Docker 网络原理虚拟网卡之tun/tap虚拟网卡之tun/tap基础指令模拟实战了解虚拟网卡之tun/tap虚拟网卡之VethVeth实战之验证不同namespace之间正常通信veth和网络命名空间常见指令虚拟交换机(linux bridge)虚拟网卡之多veth基于网桥的…

📅 2026/10/20 10:31:12
AI魔改与AIGC微短剧安全创作:红线边界与生产流程解析

AI魔改与AIGC微短剧安全创作:红线边界与生产流程解析

你有没有刷到过那种“AI魔改”视频?经典影视角色被换脸换声,甚至整段剧情都被改写,数据动不动就几十万赞。这类内容是前两年的流量密码,很多账号靠着它一夜起号。但如果你最近还在这么玩,我建议你停一下——因为从2026…

📅 2026/10/20 10:26:11
MORE NEWS

更多资讯

📰

【Agent】【workflow】19.工作流烹饪书 - LlamaIndex工作流示例

案例目标本案例展示了LlamaIndex工作流系统的多种使用模式,通过一系列示例演示如何构建、组合和扩展工作流。主要目标包括:展示LlamaIndex工作流的基本构建块和设计模式演示不同类型的工作流(顺序、条件、循环等)提供可重用的工作…

📰

genshin-wish-export 快速上手指南:三步导出并分析原神抽卡记录

genshin-wish-export 快速上手指南:三步导出并分析原神抽卡记录 【免费下载链接】genshin-wish-export Easily export the Genshin Impact wish record. 项目地址: https://gitcode.com/GitHub_Trending/ge/genshin-wish-export genshin-wish-export 是一款免…

📰

RAG Chunking 实战|主流切分策略选型与进阶优化手段

Chunk切分是RAG的地基,语义丢失是绕不开的顽疾。本文结合文档格式坑点,从重叠调参讲起,分析固定/递归/语义/结构/Parent‑Child五大切分方式,再讲解多检索入口、层级元数据、Late‑Chunking、Contextual‑Chunking四种进阶方案&am…

📰

CVPR 2026 Findings|FSSG:前视声呐数据集

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

RAG 检索架构:向量粗排 + Reranker 精排,到底怎么配合?

在RAG检索增强生成、知识库问答、文本检索系统开发中,向量检索与Reranker重排模型是两大核心组件。很多开发者落地RAG时会遇到经典痛点:向量检索返回文本语义看着很像,但和用户实际问题无关,直接导致大模型幻觉、回答错误。本文拆…

📰

智能汽车竞赛视觉组:数据集与AprilTag支架设计实战指南

看到第21届智能汽车竞赛人工智能视觉组数据集和AprilTag支架一起公布的时候,我第一反应是:这届终于把“跑起来”之前的隐性门槛摆到台面上了。做过视觉组的都知道,真正劝退新队伍的往往不是车模调参,而是视觉感知怎么起步——模型…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬