FPGA实现音频采集与回放
本文基于DE2-EP2C35F672C6平台,详解FPGA与WM8731的音频采集与回放系统设计,涵盖I²C配置、I²S时序控制、多时钟域处理等关键技术,探讨系统调试要点及后续扩展方向,适用于嵌入式音频教学与原型开发。
FPGA实现WM8731音频采集与回放系统:基于DE2-EP2C35F672C6平台的技术解析
在嵌入式音频系统开发中,如何构建一个稳定、低延迟且可扩展的数字音频链路,始终是硬件工程师面临的核心挑战之一。尤其是在教学实验和原型验证场景下,既要保证信号完整性,又要兼顾设计灵活性与可调试性。正是在这样的背景下, FPGA + 专用音频CODEC 的组合脱颖而出——它既避免了MCU软件调度带来的时序不确定性,又提供了远超传统DSP芯片的定制化空间。
以 Terasic DE2开发板(搭载Altera Cyclone II EP2C35F672C6) 搭配 Wolfson WM8731立体声编解码器 构建的音频采集与回放系统,便是一个极具代表性的实践范例。这套方案不仅实现了从麦克风输入到耳机输出的完整模拟-数字-模拟闭环,更为后续引入滤波、混响、自动增益控制等高级处理功能预留了充分的硬件资源和逻辑自由度。
为什么选择FPGA来处理音频?
很多人会问:为什么不直接用单片机或ARM处理器做音频采集?毕竟它们也能支持I²S接口。答案在于“确定性”和“并行性”。
音频流本质上是一种高频率、持续不断的串行数据流。以48kHz采样率、16位立体声为例,每秒需要处理超过1.5MB的数据,且每个样本必须在精确的时间窗口内完成接收或发送。一旦出现哪怕几个周期的偏差,就会导致BCLK抖动,进而引发爆音、失真甚至通信中断。
而FPGA的优势正在于此——通过硬件状态机和同步逻辑,可以严格锁定每一个BIT_CLK的边沿行为,确保LRCK切换与声道对齐完全符合I²S协议规范。更重要的是,FPGA允许你在不影响主数据通路的前提下,并行运行多个处理模块:比如一边进行FFT频谱分析,一边执行FIR滤波,同时还能将原始数据写入片上缓冲区用于录制。
这正是为什么像DE2这类中端FPGA平台,尽管其主频不高(仅50MHz晶振),依然能胜任高质量音频任务的关键所在。
WM8731:小巧但全能的音频前端
WM8731虽然是一款发布于2000年代中期的老牌CODEC,但它至今仍被广泛用于各类教学板和工业设计中,原因很简单: 接口标准、配置清晰、性能可靠 。
这块芯片集成了双通道ADC和DAC,支持8~96kHz采样率,数据宽度可达24位,信噪比高达95dB(DAC侧)。它的数字接口兼容I²S、Left-Justified等多种模式,模拟输入支持麦克风和线路两级放大,还内置了静音控制、软斜坡启动等功能,极大简化了外围电路设计。
不过,WM8731本身并不“智能”。它不会自动协商采样率,也不会主动发起数据传输。所有工作模式都依赖外部主机通过 I²C总线 对其内部寄存器进行初始化配置。这就意味着,如果你不先告诉它“我要用48kHz、I²S格式、启用ADC”,那么即使物理连接正确,也听不到任何声音。
I²C配置的艺术
WM8731共有10个可编程寄存器,每个8位,覆盖了增益设置、电源管理、接口格式、采样率分频等多个方面。例如:
- 寄存器
0x06控制ADC/DAC是否开启; 0x09设定数据格式(I²S vs DSP mode)、字长(16/20/24/32bit);0x0E配置PLL和采样率(如44.1kHz、48kHz等);
这些寄存器必须按特定顺序写入,否则可能导致芯片锁死或输出异常。更关键的是,I²C通信速率通常限制在100kHz以下,因此整个配置过程可能持续数毫秒,期间需保持复位信号有效,防止CODEC误动作。
下面是一段典型的Verilog状态机实现片段,用于驱动I²C总线向WM8731写入初始参数:
// 初始化序列(简化版)
reg [7:0] init_seq[0:11];
initial begin
init_seq[0] = 8'h1A; // 左输入音量 + 更新标志
init_seq[1] = 8'h00;
init_seq[2] = 8'h1B;
init_seq[3] = 8'h00;
init_seq[4] = 8'h04; // 模拟路径:麦克风输入启用
init_seq[5] = 8'h00;
init_seq[6] = 8'h05; // 数字路径:DAC静音开启
init_seq[7] = 8'h02;
init_seq[8] = 8'h06; // 功耗控制:ADC/DAC上电
init_seq[9] = 8'h09; // I²S, 16bit, 从模式
init_seq[10] = 8'h0E; // 48kHz采样率
init_seq[11] = 8'h00; // 激活所有配置
end
配合一个三段式状态机(IDLE → START → ADDR_WR → DATA_WR → STOP),即可完成逐字节发送。需要注意的是,每次操作之间应加入足够的延时(例如数百个系统时钟周期),以满足I²C的建立时间和器件响应时间要求。
📌 实践提示:
- WM8731默认I²C地址为0x34(SA引脚接地)或0x35(接VCC),务必确认开发板上的跳线设置;
- 若配置后无反应,建议先用示波器抓取SCL/SDA波形,验证是否有ACK返回;
- 可通过LED指示灯标记配置进度,便于调试。
FPGA如何掌控I²S时序?
如果说I²C是用来“唤醒”WM8731的钥匙,那么I²S就是承载音频数据的生命线。I²S协议虽简单,但对时钟精度和相位关系极为敏感。任何微小的偏移都会造成数据错位,表现为左右声道颠倒、噼啪声或完全无声。
在DE2平台上,WM8731通常配置为 从模式(Slave Mode) ,即由FPGA提供BIT_CLK(BCLK)和LRCK(帧时钟),而MCLK(主时钟)则由板载晶振或FPGA内部PLL生成。
典型时钟配置(fs = 48kHz)
| 信号 | 频率 | 计算方式 |
|---|---|---|
| MCLK | 12.288 MHz | 256 × fs |
| BCLK | 3.072 MHz | 64 × fs (16bit × 2ch) |
| LRCK | 48 kHz | 等于采样率 |
其中,BCLK决定了每个数据位的传输节奏,LRCK每周期表示一个新的音频帧开始(高电平右声道,低电平左声道)。由于FPGA系统时钟为50MHz,我们需要通过分频或PLL合成来获得这些精确频率。
以下是一个简化的I²S控制器核心逻辑:
// 分频生成BCLK
localparam BCLK_DIV = 50_000_000 / (2 * 32 * 48000); // ~5.2倍分频
reg [9:0] div_cnt;
always @(posedge clk_50m or negedge reset_n) begin
if (!reset_n)
div_cnt <= 0;
else if (div_cnt == BCLK_DIV - 1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1;
end
wire pos_edge_bclk = (div_cnt == (BCLK_DIV >> 1) - 1);
always @(posedge clk_50m) begin
if (pos_edge_bclk)
bclk <= ~bclk;
end
随后利用BCLK计数生成LRCK:
reg [4:0] bit_count;
always @(posedge bclk or negedge reset_n) begin
if (!reset_n) begin
bit_count <= 0;
lrck <= 0;
end else begin
if (bit_count == 31) begin
bit_count <= 0;
lrck <= ~lrck;
end else
bit_count <= bit_count + 1;
end
end
注意:这里假设使用16位I²S格式,每声道32个BCLK周期(含16个数据位+16个空闲位),因此LRCK在第32个BCLK结束时翻转。
数据收发与直通实现
有了稳定的时钟源,接下来就是最关键的一步: 在正确的时钟边沿采样DIN,并按时序驱动DOUT 。
I²S标准规定数据在BCLK上升沿或下降沿有效,具体取决于设备要求。WM8731一般采用 MSB先行、BCLK上升沿锁存 的方式。因此我们可以在BCLK下降沿移位数据(提前准备),在上升沿采样。
// 接收路径(ADC → FPGA)
reg [15:0] adc_left, adc_right;
reg [4:0] rx_bit_cnt;
always @(negedge bclk or negedge reset_n) begin
if (!reset_n) begin
rx_bit_cnt <= 0;
{adc_left, adc_right} <= 0;
end else begin
if (rx_bit_cnt < 16) begin
adc_reg[15-rx_bit_cnt] <= din;
rx_bit_cnt <= rx_bit_cnt + 1;
end else if (full_cycle_bclk) begin
rx_bit_cnt <= 0;
if (lrck)
adc_right <= adc_reg;
else
adc_left <= adc_reg;
end
end
end
发送路径类似,只需在BCLK上升沿前将数据准备好:
// 发送路径(FPGA → DAC)
reg [15:0] dac_reg;
always @(negedge bclk) begin
if (tx_bit_cnt < 16)
dout <= dac_reg[15-tx_bit_cnt];
else
dout <= 1'bZ; // 高阻态或保持最后一位
tx_bit_cnt <= (tx_bit_cnt == 15) ? 0 : tx_bit_cnt + 1;
end
当前设计为“直通模式”,即ADC采集的数据不做处理,直接赋值给DAC输出:
assign dac_reg = lrck ? adc_right : adc_left;
这种最简形态看似平凡,实则是验证系统可用性的第一步。只有当你能听到清晰、无噪声的原声回放时,才能放心地在此基础上叠加滤波、混响或其他算法模块。
实际工程中的常见问题与对策
即便理论设计完美,实际部署中仍可能遇到各种“玄学”问题。以下是几个典型故障及其排查思路:
❌ 无声输出
- ✅ 检查I²C是否成功配置?用逻辑分析仪查看SCL/SDA是否有ACK;
- ✅ 测量BCLK/LRCK是否存在?若无BCLK,则I²S无法启动;
- ✅ 查看WM8731的POWER DOWN引脚是否拉低?某些开发板需手动使能;
- ✅ 输入源是否正确?麦克风输入需启用增益,Line-in则要切换路径。
🔊 噪声大或有嗡嗡声
- ✅ 检查电源去耦:AVDD端应加10μF+0.1μF陶瓷电容;
- ✅ 地平面是否完整?模拟地与数字地应单点连接;
- ✅ MCLK走线是否过长?建议不超过5cm,避免高频辐射干扰;
- ✅ 使用独立LDO为CODEC供电,而非共用FPGA电源。
🎧 单声道无声
- ✅ 检查LRCK极性:某些设备定义高电平为左声道;
- ✅ 数据对齐方式是否匹配?I²S与Left-Justified时序不同;
- ✅ 是否因bit count错误导致某一声道未被写入?
💥 断续爆音
- ✅ 检查时钟抖动:尽量使用PLL而非纯逻辑分频生成BCLK;
- ✅ 避免跨时钟域访问共享变量,必要时加入异步FIFO;
- ✅ 组合逻辑路径过长?插入寄存器打拍缓解建立/保持违例。
超越直通:未来的扩展方向
一旦基础链路打通,这个平台的价值才真正显现。得益于FPGA的高度可编程性,你可以轻松拓展出多种高级应用:
🎛️ 实时音频处理
- FIR/IIR滤波器 :使用MATLAB生成系数,在FPGA中实现固定或可调截止频率的低通、高通、带阻滤波;
- 自动增益控制(AGC) :动态调整输入电平,防止削峰失真;
- 均衡器(EQ) :多段参量式调节,打造个性化音效;
- 混响与延迟 :借助片上RAM缓存历史样本,构造房间反射效果。
🧠 智能音频前端
- 语音活动检测(VAD) :结合能量阈值与频域特征判断是否有人说话;
- FFT频谱显示 :实时计算音频频谱并通过VGA或UART输出;
- 关键词唤醒预处理 :降噪、预加重、MFCC提取等可在硬件加速完成。
🖥️ SoC集成潜力
进一步可引入Nios II软核处理器,构建“软硬协同”的混合架构:
- Nios负责用户交互、文件读写(SD卡)、网络流媒体(Ethernet);
- FPGA逻辑专注底层音频流处理;
- 实现MP3播放器、网络广播终端、录音笔等完整产品原型。
写在最后
这套基于 DE2-EP2C35F672C6 + WM8731 的音频系统,表面上只是一个简单的“录音回放”实验,实则涵盖了现代嵌入式音频开发的几乎所有关键技术环节:
✅ 外设初始化(I²C)
✅ 高精度时序控制(I²S)
✅ 多时钟域协调(PLL、异步FIFO)
✅ 硬件/软件协同设计思维
更重要的是,它教会我们一种思维方式: 把时间当作资源来管理 。在FPGA世界里,每一个时钟周期都是宝贵的,每一次延迟都必须被预见和补偿。当你亲手写出第一个能在嘈杂环境中稳定工作的音频链路时,那种成就感远非仿真波形所能比拟。
这种高度集成的设计思路,正引领着智能音频设备向更可靠、更高效的方向演进。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)