ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA音频开发实战:WM8731时钟、I2C与I2S配置全解析

FPGA音频开发实战:WM8731时钟、I2C与I2S配置全解析 简介面向FPGA音频开发与硬件设计学习者这份资源围绕WM8731立体声编解码器提供了完整的现场可编程门阵列工程实现。内容包括I2C控制器、音频配置、数模转换输出等核心模块的硬件描述语言源码以及Quartus工程配置文件可用于DE1-SoCDE115等平台进行音频采集、播放与实时信号处理验证尤其适合学习硬件描述语言与音频编解码器接口时序的读者。压缩包共一百六十六个文件大小五点七六兆字节文件类型涵盖硬件描述语言源码、工程设置、比特流文件、手册文档与备份文件等既包含可直接加载到芯片的配置数据也保留了便于二次开发的源代码与测试文件目录结构完整便于按模块查阅。该资源已有四百一十六人浏览学习借助完整工程和测试代码使用者可以快速理解WM8731与现场可编程门阵列之间的I2C配置流程、寄存器控制方法并以此为模板扩展自定义音频处理算法。1. 一个 wm8731.rar 老工程背后FPGA 音频开发先过哪三关网上流传的 wm8731.rar、feed8ep 这类分包命名大多是早年 FPGA 音频工程的压缩包残留真正有价值的是里面那颗 WM8731。WM8731 是 Cirrus Logic 的低功耗立体声编解码器在 FPGA 音频开发板上的地位接近“教科书级外设”因为它同时牵出三条线给编解码器的主时钟 MCLK、通过 I2C 写入的寄存器配置、以及收发音频数据的 I2S 串行链路。新手跑一个 wm8731 fpga 工程通常不是模块难写而是这三条线缺一条、或者相位不对就整片静音还查不出毛病。这篇按“时钟 → 控制 → 数据 → 排错”的顺序把能直接复现的方案和参数列出来适合正在做 FPGA 入门音频开发、或者准备把 WM8731 接到自研板卡上的工程师。2. WM8731 时钟与接口选型12.288MHz、256fs 与 I2S 信号怎么对齐2.1 先确认 WM8731 工作模式再决定要不要外接 MCLKWM8731 内部有数字插值器和抽取滤波器工作时必须有一个稳定的主时钟做时序基准。只有一种例外把 WM8731 的 USB 引脚拉高让它工作在 USB 模式这时可以用 12MHz 时钟配合内部 PLL 产生所有音频时钟外部不再需要 MCLK。FPGA 音频工程里更常见的是普通模式也就是外部晶振进入 FPGA经过 PLL 生成 12.288MHz 或 11.2896MHz直接接到 WM8731 的 XTI 引脚同时用同一路时钟分频出 BCLK 和 LRCLK。拿到一块带 WM8731 的开发板先看原理图确认三件事WM8731 的 XTI 是从 FPGA 引脚引入还是接了独立晶振USB 引脚是拉高还是拉低CSB 引脚是拉高还是拉低。CSB 不影响数据通路只影响 I2C 从机地址CSB0 时写地址是 0x1A读地址是 0x1BCSB1 时分别是 0x1C 和 0x1D。很多工程死在“写不进去”最后发现是 CSB 电平与代码里的从机地址不一致。音频采样率选 48kHz 还是 44.1kHz直接决定了 MCLK 频率所以这一步不要在原理图确认之前就开始写 Verilog。2.2 12.288MHz 与 11.2896MHz采样率、MCLK 与分频关系表WM8731 支持 256fs、384fs 和 512fs 的 MCLK 倍率其中 256fs 对时钟抖动最不敏感FPGA 工程默认用它。BCLK 则固定取 64fs也就是一个 LRCLK 周期内包含 64 个位时钟左右声道各 32 位。下面这张表按 48kHz 和 44.1kHz 两条主力链路列出常用频率照这个配就不会出现“写 0x08 寄存器时不知道 SR 字段该填什么”的尴尬。采样率 fsMCLK 256fsMCLK 384fsBCLK 64fs常见用途8 kHz2.048 MHz3.072 MHz512 kHz语音、对讲32 kHz8.192 MHz12.288 MHz2.048 MHzFM 广播质量44.1 kHz11.2896 MHz16.9344 MHz2.8224 MHzCD 音源48 kHz12.288 MHz18.432 MHz3.072 MHzPC 音频、HDMI 常用如果板载晶振是 50MHz就用 FPGA PLL 生成 12.288MHz采 48kHz 这一族工程里最容易买到也最好对齐。若一定要播 44.1kHz 的 WAV 文件PLL 输出 11.2896MHzBCLK 同步改成 2.8224MHzLRCLK 仍保持 44.1kHz。需要注意的是12.288MHz 本身也能让 44.1kHz 的 WM8731 发声但音调会偏不是采样率寄存器设成 44.1k 就能解决的MCLK 必须跟着 fs 走。2.3 用系统 PLL 生成 WM8731 所需 MCLK 的最小时钟模块常见做法是直接实例化 Vivado 或 Quartus 的 Clocking Wizard输入 50MHz输出选 12.288MHz然后把输出时钟做成 FPGA 引脚上的 MCLK。下面是一段最小实例代码重点不是 Wizard 参数而是 MCLK 应该经过 ODDR 输出到引脚避免普通逻辑布线给音频时钟带来毛刺。// 50MHz - 12.288MHz作为 WM8731 的 MCLK clk_wiz_audio u_audio_clk ( .clk_in1 (clk_50m), .clk_out1 (clk_12m288), .reset (~rst_n), .locked (pll_locked) ); // 用 ODDR 把 12.288MHz 复制到引脚D10、D21 输出一路同频方波 ODDR #( .DDR_CLK_EDGE(SAME_EDGE), .INIT(1b0) ) u_oddr_mclk ( .Q (wm8731_mclk), .C (clk_12m288), .CE (1b1), .D1 (1b0), .D2 (1b1), .R (1b0), .S (1b0) );代码里clk_12m288是 PLL 输出的全局时钟ODDR 把它复制到输出引脚D10、D21 的含义是每个时钟上升沿先输出 0、后输出 1等效于把 12.288MHz 方波送到 WM8731 XTI。pll_locked要参与后续 I2C 配置的复位控制PLL 没锁定就写寄存器会出现偶发失败。这里不要直接把clk_12m288接到普通assign输出引脚上引脚上出现毛刺时 WM8731 的数字滤波器会出现周期性噪声听感像低沉的电流声。3. FPGA 托管 WM8731 的 I2C 配置链寄存器地址、上电顺序和状态机3.1 WM8731 的 I2C 控制总线格式与从机地址判定WM8731 控制接口支持 I2C 和 SPIFPGA 开发板上基本都用 I2C。I2C 写一帧数据由四段组成设备地址7 位、寄存器地址8 位、配置高字节、配置低字节设备地址的最低位是读写标志。WM8731 的寄存器值是 9 位有效但 I2C 帧里习惯按 16 位字传输低 9 位有效高位补 0所以代码里配置值写成16h0004、16h0017都没问题。判定从机地址时看 CSBCSB 接低电平写地址 0x1A、读地址 0x1BCSB 接高电平写地址 0x1C、读地址 0x1D。I2C 总线上必须要有上拉电阻通常 2.2kΩ 到 4.7kΩ。FPGA 的inout引脚在输出 0 时拉低总线、输出 1 时设置成高阻态如果直接输出 1 而不是释放总线会拉不住开漏结构读回时序直接错。我一般会把 SDA 输出和三态控制单独拆成一个sda_oe信号检查 ACK 时把sda_oe拉低让引脚变输入方向。3.2 一套能跑通的最小寄存器配置序列上电后不要直接写寄存器先给 WM8731 发软复位让所有寄存器回到默认值再按“数字接口格式 → 采样率 → 模拟路径 → 电源 → 激活”的顺序配置。以下是实测链路中最常用的一组值48kHz、16bit、I2S 格式MCLK 为 12.288MHz。寄存器地址写值作用0x0F0x0000软复位0x070x0004数字接口设为 I2S16 bit0x080x0000采样率 48kHzMCLK 256fs0x000x0017左声道输入不静音默认音量0x010x0017右声道输入不静音0x020x0079左耳机输出约 0 dB0x030x0079右耳机输出0x040x0008DACSEL1DAC 信号接入耳机通路0x050x0000解除 DAC 数字静音DACMU00x060x0000所有模拟和数字模块上电0x090x0001Active1数字接口开始工作注意 0x04 寄存器写 0x0008 只置了 DACSEL网上很多例程写 0x0012 也能出声多出来的位是边音和输入路径选择不影响播放但会影响录音监听。如果只做播放写 0x0008 更干净。0x05 是很容易漏的一处它的 bit3 DACMU 默认是 1意思是 DAC 输出被静音不写 0 解除静音前面所有音量设置都白搭。3.3 I2C 配置状态机的可复用写法配置 WM8731 的 I2C 状态机不需要做通用主机只要顺序发送寄存器值即可。下面这段是状态机核心SCL 半周期计数由CLK_DIV决定50MHz 主频、SCL 100kHz 时取 250。每写完一个字节检查 ACKACK 异常时把err拉高方便后续在 ILA 里定位。localparam IDLE 4d0, START 4d1, ADDR 4d2, ACK1 4d3, REG 4d4, ACK2 4d5, DATAH 4d6, ACK3 4d7, DATAL 4d8, ACK4 4d9, STOP 4d10; always (posedge clk_50m) begin if (!rst_n) begin state IDLE; bit_cnt 3d0; end else case (state) IDLE: begin if (start_cfg) begin scl_en 1b1; sda_oe 1b1; sda_din 1b0; // START 条件 state START; end end START: begin if (scl_cnt CLK_DIV[8:0]) begin sda_din {7h1A, 1b0}; // 从机写地址CSB0 tx_byte {7h1A, 1b0}; bit_cnt 3d0; state ADDR; end end ADDR: begin // 每 8 个 bit 发送完进入 ACK1 if (bit_cnt 3d7 scl_low) begin sda_oe 1b0; // 释放 SDA等待从机 ACK state ACK1; end else if (bit_cnt 3d7 scl_low) begin tx_byte {tx_byte[6:0], 1b0}; bit_cnt bit_cnt 1b1; end sda_din tx_byte[7]; end ACK1: begin if (scl_high) begin if (sda_in) begin err 1b1; // 从机没拉低 SDA说明地址错误 state STOP; end else begin sda_oe 1b1; tx_byte 8h07; // 这里放入寄存器地址 state REG; end end end // REG、DATAH、DATAL 与 ADDR 结构相同ACK2/ACK3/ACK4 与 ACK1 相同 // 全部 ACK 通过后进入 STOPscl_en0 并拉高 sda 完成停止条件 endcase end上面的状态机把地址字节、寄存器地址、数据高字节、数据低字节拆成四段发送每段都独立检查 ACK。scl_low和scl_high是分频计数器给出的半周期窗口所有数据变化都发生在 SCL 低电平期间保证 WM8731 在 SCL 上升沿采样到稳定数据。如果 I2C 配置失败优先查err是在哪个 ACK 位置拉高的ACK1 失败说明从机地址和 CSB 不匹配ACK2 以后失败说明寄存器地址写错或 SCL 频率超过 400kHz。4. I2S 数据通路FPGA 发音频给 WM8731、从 WM8731 收音频的时序控制4.1 I2S 时序里容易搞混的 BCLK 采样边沿与左右声道极性WM8731 的数字音频接口是 I2S 从机BCLK 和 LRCLK 都由 FPGA 产生。标准 I2S 里 LRCLK 低电平传左声道、高电平传右声道数据在 BCLK 下降沿更新接收端在 BCLK 上升沿采样MSB 比 LRCLK 边沿延迟一个 BCLK。FPGA 做发送时最稳妥的方式是在 BCLK 上升沿装载和移位同时保证数据只在下降沿前后变化做接收时在 BCLK 上升沿采样 ADCDAT 即可。下面这张表是容易错的三处对应关系。信号FPGA 发送侧FPGA 接收侧BCLK输出 3.072MHz提供或接收LRCLK低左高右每 64 个 BCLK 翻转用边沿判断左右声道数据下降沿更新上升沿被 WM8731 采样上升沿采样 ADCDATMSB 时序比 LRCLK 边沿晚一个 BCLK采样时同样晚一个 BCLK常见错误是把 LRCLK 极性接反结果左声道数据和右声道数据对调听着像声场反相还有人在 16bit 数据下用 32bit 移位寄存器低 16 位补的是 0结果音量变小而且有直流偏移。先固定格式I2S、16bit、右对齐在 WM8731 配置里是 0x0004这三者必须和 FPGA 移位宽度完全一致。4.2 生成 BCLK 和 LRCLK 的计数器写法12.288MHz 分频得到 3.072MHz BCLK再数 64 个 BCLK 翻转一次 LRCLK得到 48kHz。这里用 bclk 脉冲做计数使能而不是直接再分频一级时钟能让 LRCLK 与 BCLK 的相位关系稳定。reg [1:0] bclk_div; reg [5:0] frame_cnt; reg lrclk; // 4 分频用高两位拼出 3.072MHz 方波 always (posedge clk_12m288) begin bclk_div bclk_div 1b1; end assign bclk bclk_div[1]; // 每个 BCLK 周期出现一次的脉冲用它做 LRCLK 计数 wire bclk_pulse (bclk_div 2b11); always (posedge clk_12m288) begin if (bclk_pulse) begin if (frame_cnt 6d31) begin lrclk ~lrclk; // 每 32 个 BCLK 翻转一次周期 64fs frame_cnt 6d0; end else begin frame_cnt frame_cnt 1b1; end end endbclk bclk_div[1]是 BCLK 的 50% 占空比方波频率正好 3.072MHz。frame_cnt从 0 数到 31每 32 个 BCLK 周期翻转 LRCLK一个完整的 LRCLK 周期包含 64 个 BCLK对应 48kHz 采样率。这种写法把 LRCLK 的翻转放在了 BCLK 边沿附近WM8731 对 LRCLK 与 BCLK 的相对相位要求不是特别苛刻只要不是把 LRCLK 做成独立异步计数器就行。4.3 DAC 输出与 ADC 采集的模块代码DAC 发送模块的核心是 16 位并行数据转串行。每检测到 LRCLK 边沿装载当前声道的并行数据其余 BCLK 周期做移位输出。module wm8731_dac #( parameter W 16 )( input wire bclk, input wire lrclk, input wire [W-1:0] dac_l, input wire [W-1:0] dac_r, output reg dac_dat ); reg [W-1:0] sr; reg lrclk_d; always (posedge bclk) begin lrclk_d lrclk; if (lrclk ! lrclk_d) begin // LRCLK 边沿装载当前声道下一个 BCLK 上升沿开始输出 MSB sr lrclk ? dac_r : dac_l; end else begin dac_dat sr[W-1]; sr {sr[W-2:0], 1b0}; end end endmodule这段代码在 LRCLK 边沿装载数据但dac_dat在装载那个时钟周期没有更新到下一个 BCLK 上升沿才把 MSB 推出去正好等效于标准 I2S 里“MSB 比 LRCLK 边沿晚一个 BCLK”的时序。如果示波器抓到 MSB 提前或延后一个位就把装载分支里的lrclk ? dac_r : dac_l对调或者把装载和移位拆成两个 always 块微调一拍。ADC 接收模块反过来在 BCLK 上升沿采样 ADCDAT 并移入移位寄存器在 LRCLK 边沿锁存完整声道数据。module wm8731_adc #( parameter W 16 )( input wire bclk, input wire lrclk, input wire adc_dat, output reg [W-1:0] adc_l, output reg [W-1:0] adc_r ); reg [W-1:0] shift; reg lrclk_d; always (posedge bclk) begin lrclk_d lrclk; shift {shift[W-2:0], adc_dat}; if (lrclk !lrclk_d) // 上升沿时左声道 16bit 已移完 adc_l shift; else if (!lrclk lrclk_d) // 下降沿时右声道 16bit 已移完 adc_r shift; end endmodule这里锁存的是shift不是shift的下一个值原因是 ADCDAT 的 MSB 比 LRCLK 边沿晚一个 BCLKLRCLK 边沿到来时移位寄存器里正好是前一个声道的最后 16 位。实抓波形时你会发现锁存值可能偏一拍这很正常I2S 接收端普遍存在一个位周期的对齐问题只要左右声道不串、数据不花屏就说明边沿选对了。5. WM8731 音频工程的排错顺序没声音、爆音和 I2C 不回 ACK 怎么定位5.1 无声音先查电源、MCLK 和寄存器回读WM8731 无声音时不要急着改 Verilog先按顺序排除物理层。第一步测 AVDD 和 DVDD 是否都是 3.3V很多开发板把模拟电源和数字电源用磁珠分开磁珠虚焊会导致 AVDD 为 0WM8731 完全静默。第二步用示波器或频率计测 XTI 引脚是否有 12.288MHz没有 MCLK 时 WM8731 既不做 DAC 也不做 ADC。第三步检查 0x06 上电寄存器是不是 0x0000以及 0x09 Active 寄存器是不是 0x0001这两个值直接影响模拟输出级是否工作。寄存器回读是最快的定位手段。WM8731 支持 I2C 读操作读时从机地址改成 0x1BCSB0先发送寄存器地址再重新发送起始条件加读地址之后读回两个字节。FPGA 工程里可以不用完整支持所有寄存器只回读 0x07、0x08、0x09 三个关键值写进去是 0x0004、0x0000、0x0001基本就能确认控制链路正常。如果读回全 0xFF 或者 ACK 阶段 SDA 一直是高先查 I2C 上拉电阻和 CSB 电平再查 SCL 频率是否超过 400kHz。5.2 BCLK/LRCLK 与 WM8731 期望的格式不一致导致的爆音爆音和“嘶嘶声”通常不是电源问题而是数字格式对齐错误。最典型的一种是 BCLK 用了 32fs 而不是 64fsWM8731 会漏掉半个声道的采样数据听感像快速断续的噼啪声。另一种是 FPGA 里移位宽度配置成 32bit但 WM8731 寄存器还是 16bit I2S于是每个声道多出来 16 个时钟周期的无效位DAC 把低位噪声当有效信号放大。还有一种常见情况是往 0x08 写 0x000048k但实际 MCLK 给的是 11.2896MHz此时 WM8731 内部滤波器工作点偏移输出频率跟着偏听起来像磁带变调。现象可能原因检查点完全无声MCLK 缺失、Active0、DACMU1测 MCLK回读 0x05/0x09爆音BCLK 不是 64fs、位宽不匹配用 ILA 看 BCLK 频率左右颠倒LRCLK 极性反对调dac_l/dac_r装载音调偏高MCLK 与采样率寄存器不匹配48k 必须配 12.288MHz声音小且有直流移位寄存器低位补 0 造成偏移确认 16bit 还是 24bit排查时把 FPGA 内部音频数据源换成固定值比如左声道输出 16h2000、右声道输出 16hE000这时耳机里应该是轻微的直流偏移声但不会有爆音。如果连固定值都爆问题必在 BCLK/LRCLK 生成或 I2S 格式配置不在上游数据。5.3 ILA/SignalTap 抓 I2S 与 I2C 信号的建议用 ILA 抓 I2S 时采样时钟别用 BCLK 本身最好用 50MHz 或 100MHz 的监测时钟这样才能看到 BCLK 与 LRCLK 的相对相位。抓 I2C 配置过程要在start_cfg拉高后触发观察scl、sda_in、state、err四个信号重点确认 ACK 位置有没有从机拉低。如果sda_in在 ACK 窗口是低但下一拍又变高说明释放 SDA 的方向切换晚了一个计数周期把sda_oe拉低的动作提前到 SCL 下降沿即可。I2S 数据引脚在综合时容易被优化给adc_dat这类输入信号加 I/O 约束比改代码更有效。下面是一组常见的 XDC 约束写法把 MCLK 定义为时钟并约束 ADCDAT 相对于 MCLK 的输入延迟ILA 里波形会稳定很多。create_clock -period 12.288 -name mclk [get_ports {wm8731_mclk}] set_input_delay -clock [get_clocks mclk] -max 2.0 [get_ports {adc_dat}] set_input_delay -clock [get_clocks mclk] -min 0.5 [get_ports {adc_dat}]约束里的 2.0ns 和 0.5ns 是经验值具体要根据 PCB 走线长度调整。FPGA 音频项目里 I2S 速率不高3.072MHz 的 BCLK 对时序余量非常宽裕但如果连set_input_delay都不写工具会按默认 0ns 分析实际板卡走线较长时可能偶发采错位。6. 用查找表在 FPGA 内生成 1kHz 正弦波闭环验证 WM8731 播放链路验证 WM8731 播放链路最直接的方法是在 FPGA 内部生成一个 1kHz 正弦波送到 WM8731 后接耳机听有没有连续单音。这样做可以把“数据源问题”从链路里隔离掉。生成正弦波用查找表加定点相位累加不需要除法器整个模块只有 ROM 和一个累加器。先用 Python 生成 1024 点 16 位补码正弦表导出成 Vivado 能识别的 COE 文件。import math N 1024 with open(sine.coe, w) as f: f.write(memory_initialization_radix16;\n) f.write(memory_initialization_vector\n) for i in range(N): v int(32767 * math.sin(2 * math.pi * i / N)) if v 0: v v 65536 # 转成 16 位补码 f.write(f{v:04x} (, if i N - 1 else ;) \n)生成的 COE 文件里0x0000 到 0x7fff 是正半周0x8000 到 0xffff 是负半周正好匹配 WM8731 的二进制补码输入。在 FPGA 里用 Block RAM 初始化这个 ROM每次 LRCLK 上升沿按相位累加值取一个点送给 DAC。localparam PHASE_INC (1000 * 65536) / 48000; // 1kHz, 48kHz 采样, Q16.16 reg [31:0] phase; reg [15:0] dac_l; reg [15:0] dac_r; always (posedge lrclk) begin phase phase PHASE_INC; dac_l sine_rom[phase[31:16]]; dac_r dac_l; // 右声道延迟一拍听感上仍是同相单音 endPHASE_INC是定点相位步长65536 相当于 2 的 16 次方的整数 1.0 相位。1kHz 在 48kHz 采样率下每个采样点应该走 1/48 个周期换算成 16 位定点就是 1365.33取整为 1365 后频率误差约 0.03%人耳完全听不出。如果从 48kHz 切到 44.1kHzPHASE_INC换成(1000 * 65536) / 44100约等于 1486。ROM 输出直接接到第 4 章的dac_l和dac_r输入ILA 里抓lrclk、dac_dat、dac_l三路信号能看到每个 LRCLK 低电平期间串行移出 16 位左声道数据再接耳机确认 1kHz 单音干净整条 WM8731 播放链路就算闭环了。本文还有配套的精品资源点击获取
返回列表