ARTICLE DETAIL

资讯详情

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

基于Cyclone IV FPGA的Class-D数字功放Σ-Δ调制器设计

基于Cyclone IV FPGA的Class-D数字功放Σ-Δ调制器设计 简介面向电子信息工程、FPGA与EDA方向的课程实训与毕业设计场景这份PDF是一份基于Cyclone-IV FPGA芯片的数字音频功放设计技术报告涉及Verilog/VHDL逻辑设计与Quartus Prime开发流程。报告完整梳理了系统框架、可行性分析、板载资源与原理图分析并展开电源电路、时钟电路、LED与按键、数码管、液晶及扩展接口等硬件模块同时给出数字电位器、继电器、CD4053通道切换与MIC信号处理等驱动程序实现思路。资源包为1个PDF文件共2.88MB适合直接查阅与二次整理。已有1246人学习读者可借此掌握音量调节、通道切换、静音与抽头位置检测等功能的软硬件协同方法并对照测试流程完成调试与验证。1. 为什么把数字音频功放的调制器放进 Cyclone IV FPGA做过四通道功放的工程师大概都碰过这种局面板上并排放四颗专用 Class-D 调制芯片外围 RC 网络调得刚刚好客户一句「换成 96 kHz 采样率」或者「四路之间相位要对齐」整套硬件就得推倒重来。专用调制器的死区、载波频率、滤波器系数烧在硅片里动一个数就是改一版板子。Cyclone IV 这类低成本 FPGA 的价值就在这里把 PCM 到开关波形的整条调制链路做成可重构逻辑改死区就是改一个参数改采样率就是改一个分频比重编译一次即可。多通道同步也变成天然的——同一个 PLL、同一个计数器四路 PWM 的相位误差就是布线延迟量级。适合谁一是做非标采样率或多通道同步功放的团队二是在功放前面还想塞数字音量、限幅、简单的 EQ 补偿的人——这些算法本来就是 FPGA 里几个乘法器的活。Cyclone IV E 的 EP4CE6 逻辑单元不到七千个跑四通道一阶 Σ-Δ 调制绰绰有余3.3V LVCMOS IO 直接对接 I2S 音源和栅极驱动连电平转换都省了。2. Class-D 数字功放的调制原理与 Cyclone IV 型号选型2.1 从 PCM 到开关波形PWM 与 Σ-Δ 两条路线脉宽调制PWM是最直观的固定载波频率把 PCM 样本映射成占空比。假设调制时钟 50 MHz、载波 384 kHz那么每个载波周期有 130 个时钟台阶占空比分辨率就是 1/130约 7.7 位有效精度。想要 16 位分辨率载波周期得拉到 65536 个时钟也就是载波降到 763 Hz——这在音频里根本不能用。所以纯 PWM 路线要么提高时钟到几百兆要么靠过采样加噪声整形补救。Σ-Δ 调制走的是另一条路输出只有 1 bit但用极高的更新率换精度。一阶环路的噪声传递函数是1 - z⁻¹量化噪声被推到高频音频带内的信噪比随过采样率OSR以 30 dB/十倍频程增长二阶是(1 - z⁻¹)²斜率变成 50 dB/十倍频程。48 kHz 采样率配 12.288 MHz 调制时钟OSR 正好 256一阶环路带内 SNR 约 75 dB二阶能到 110 dB 以上。选哪条我的经验是需要极低静态功耗、载波频率固定的场合用 PWM需要低 THD、不想把时钟拉到几百兆的场合用 Σ-Δ。Cyclone IV E 的 PLL 输出 12.288 MHz 很轻松1-bit 输出又天然适配半桥栅极所以下面按 Σ-Δ 展开。2.2 Cyclone IV E 的型号与资源对照选型先看通道数和是否需要 DSP 乘法器做数字滤波。下表数据取自器件手册的典型值最终以 Altera 官方手册的对应版本为准。型号逻辑单元 LEM9K 存储 (Kbit)18×18 乘法器PLL最大用户 IOEP4CE66272270152179EP4CE1010320414232179EP4CE1515408504564343EP4CE2222320594664153单通道的粗略占用I2S 从机接收约 150 LE一阶 Σ-Δ 环路约 80 LE二阶约 200 LE死区与栅极成形约 30 LE再加一个异步 FIFO 约 300 LE用 M9K 实现。两个通道加起来不到 1200 LEEP4CE6 还剩一大半资源。真要用到 EP4CE15 及以上多半是因为要跑多路数字分频、参数均衡或者几路 ADC 反馈采集。2.3 时钟树MCLK、BCLK、LRCLK 与载波的关系I2S 链路有三根时钟主时钟 MCLK 通常是 256×fs位时钟 BCLK 是 64×fs声道时钟 LRCLK 就是 fs。48 kHz 下分别是 12.288 MHz、3.072 MHz、48 kHz。Σ-Δ 的调制时钟最好直接取 MCLK12.288 MHz 除以 256 得到 48 kHz 使能脉冲OSR 就是 256。这里有个常见误区图省事用音源送来的 LRCLK 直接当采样使能。音源的 LRCLK 抖动通常在几十皮秒到几纳秒看起来无害但它会调制 Σ-Δ 环路的采样时刻把抖动噪声搬进音频带内实测底噪会抬高一截。稳妥做法是本地 PLL 产生干净的 12.288 MHz用它分频出使能音源数据先过异步 FIFO 做速率匹配。# clock_plan.py 从采样率推算各时钟与载波流频点 fs_list [44100, 48000, 96000] for fs in fs_list: mclk 256 * fs # 主时钟 bclk 64 * fs # 位时钟 for div in (256, 128, 64): rate mclk // div # 调制时钟不变使能率 采样率 osr mclk // fs print(ffs{fs:6d}Hz MCLK{mclk/1e6:7.3f}MHz BCLK{bclk/1e6:6.3f}MHz fOSR{osr:4d} 使能分频{div})这段脚本用来确认 OSR 是否落在预期区间。48 kHz 采样率下 OSR256 是舒服的选择96 kHz 时 OSR 掉到 128一阶环路的带内 SNR 只有约 66 dB这时候要么换二阶环路要么把 MCLK 提到 24.576 MHz。3. 用 Quartus 搭 I2S 接收与 Σ-Δ 调制链开发环境用 Quartus Prime这是 Altera FPGA 的官方工具链Lite 版对 Cyclone IV 免费。仿真可以用 ModelSim 或者 Quartus 自带的仿真器下面给的是 RTL 和仿真命令都能直接抄的写法。3.1 I2S 从机接收边沿选择与三级同步I2S 标准时序里WS 翻转之后还有一个 BCLK 周期的延迟才输出 MSB数据在 SCK 下降沿变化、上升沿采样。如果直接把 BCLK 当本地时钟用等于把整条接收链路挂在一个异步时钟上跨时钟域问题会在后面每一个模块里冒出来。更稳的做法是用本地 50 MHz 时钟过采样 BCLK检测上升沿取数。亚稳态是绕不开的BCLK 相对本地时钟是异步的第一级同步寄存器的输出可能停在中间电平必须再打两拍把 MTBF 拉到可接受范围。这是 fpga 复位信号亚稳态之外另一个典型场景思路一样——异步输入进来先同步。// i2s_rx.v I2S 从机接收本地系统时钟过采样 BCLK // clk 建议 8 倍 BCLK48kHz/24bit 时 BCLK3.072MHz50MHz 足够 module i2s_rx #( parameter DW 24 // 有效位宽 )( input wire clk, // 系统时钟 input wire rst_n, // 异步复位低有效 input wire bclk, // I2S 位时钟 SCK input wire lrclk, // 声道时钟 WS input wire sdin, // 串行数据 SD output reg [DW-1:0] ch_l, // 左声道样本有符号补码 output reg [DW-1:0] ch_r, // 右声道样本 output reg tick // 一帧接收完成拉高一个 clk 周期 ); // 三级同步链第一级可能进入亚稳态后两级把失效概率压下去 reg [2:0] bclk_s, lrclk_s, sdin_s; always (posedge clk or negedge rst_n) begin if (!rst_n) begin bclk_s 3b000; lrclk_s 3b000; sdin_s 3b000; end else begin bclk_s {bclk_s[1:0], bclk}; lrclk_s {lrclk_s[1:0], lrclk}; sdin_s {sdin_s[1:0], sdin}; end end wire bclk_rise (bclk_s[2:1] 2b01); // 同步后的 BCLK 上升沿 wire lr lrclk_s[2]; // 同步后的声道标志1 为右声道 reg [DW-1:0] shift; reg [5:0] bit_cnt; reg lr_d; always (posedge clk or negedge rst_n) begin if (!rst_n) begin shift 0; bit_cnt 0; lr_d 1b0; ch_l 0; ch_r 0; tick 1b0; end else begin tick 1b0; if (lr ! lr_d) begin // WS 翻转进入新声道 lr_d lr; bit_cnt 0; // I2S 在 WS 翻转后先有 1 位延迟 end else if (bclk_rise) begin if (bit_cnt 1 bit_cnt DW) begin shift {shift[DW-2:0], sdin_s[2]}; end if (bit_cnt DW) begin if (lr_d) ch_r {shift[DW-2:0], sdin_s[2]}; else ch_l {shift[DW-2:0], sdin_s[2]}; tick lr_d; // 右声道收满表示一帧结束 end bit_cnt bit_cnt 1b1; end end end endmodule逻辑说明bit_cnt 0对应 I2S 标准里那一位延迟从bit_cnt 1开始才是真正的 MSB。tick只在右声道末尾拉高下游用它触发一次 Σ-Δ 的采样使能。参数DW改成 16 或 32 即可适配不同位深前提是 I2S 帧内该声道确实有那么多位。3.2 一阶 Σ-Δ 调制器的 Verilog 与位宽设计环路的数学很简单累加器加上输入减去反馈输出取累加器的符号位。麻烦的是位宽——24 位输入加满量程反馈累加器如果只开 24 位几毫秒就会回绕输出直接变成噪声。// sd1_mod.v 一阶 sigma-delta 调制器 // 环路方程acc pcm - dout*FULLdout (acc 0) // 噪声传递函数 NTF 1 - z^-1带内噪声按 30dB/十倍频程整形 module sd1_mod #( parameter DW 24 )( input wire clk, // 调制时钟例如 12.288MHz input wire rst_n, input wire en, // 每个采样周期一个 clk 脉冲即 fs 使能 input wire signed [DW-1:0] pcm, // 有符号 PCM 输入 output reg dout // 1bit 输出直连半桥栅极 ); // 反馈满量程取 2^(DW-1)用 DW1 位表示防止取反时溢出 localparam signed [DW:0] FULL (1 (DW-1)); // 累加器再宽一位保证加法结果不会回绕 reg signed [DW1:0] acc; always (posedge clk or negedge rst_n) begin if (!rst_n) begin acc 0; dout 1b0; end else if (en) begin acc acc pcm - (dout ? FULL : -FULL); dout (acc 0); // 符号位判决 end end endmodule参数说明en是采样率使能每 256 个调制时钟拉高一次就等于把输入更新率定在 fs。FULL对应满量程如果输入是 -1.0 到 1.0 的归一化范围dout 为高时反馈 FULL为低时反馈 -FULL环路自动把平均占空比锁到输入值。判决门限取 0 意味着输出在 ±FULL 之间跳平均值为输入。累加器用DW2位是留给加法中间结果的余量别省。调试时会遇到综合工具把整个环路当成冗余逻辑优化掉的情况尤其是输出没接到顶层端口的时候。加(* keep *)或者(* preserve *)属性可以拦住这类优化。3.3 用 ModelSim 跑仿真验证一阶环路的噪声整形仿真不需要跑很久几百微秒就能看出噪声整形的形状低频段输出频谱应该随频率上升而下降高频段抬起来。# 在工程目录下跑仿真tb_sd.v 里给 1kHz 正弦作为 pcm 输入 vlib work vmap work work vlog ../rtl/i2s_rx.v ../rtl/sd1_mod.v ../tb/tb_sd.v vsim -c -do run -all; quit tb_sdtestbench 里要做两件事一是产生节拍正确的 I2S 波形BCLK 周期 2 个 clk 的一半、WS 每 32 个 BCLK 翻转二是把dout收集到一个足够长的数组里导出成文本再用脚本做 FFT。// tb_sd.v 片段产生 1kHz 正弦输入并采集 1bit 输出 integer fd, i; reg signed [23:0] sine; initial begin fd $fopen(sd_out.txt, w); for (i 0; i 200000; i i 1) begin (posedge clk); if (en) begin // 48kHz 采样率下 1kHz 正弦周期 48 个样本 sine $rtoi(8388607.0 * $sin(2.0 * 3.1415926 * (i % 48) / 48.0)); $fwrite(fd, %d\n, dout); end end $fclose(fd); $stop; end参数说明8388607是 24 位有符号的上限附近留一点余量避免削顶。导出文件之后用 numpy 做 FFT看 1 kHz 主瓣旁边的底噪地板就能判断环路是否正常工作。一阶环路的底噪地板在 48 kHz 带内大概在 -75 dB 附近如果看到 -40 dB 说明判决门限或者位宽有问题。3.4 Quartus 约束与 SignalTap 观测点时钟约束用 SDC 写清楚否则时序分析器按默认 1 GHz 分析报一堆假违规。# audio.sdc 关键时钟与异步路径约束 create_clock -name clk50 -period 20.000 [get_ports clk50] # 50MHz 系统时钟 create_clock -name mclk -period 81.380 [get_ports mclk] # 12.288MHz 调制时钟 derive_pll_clocks set_false_path -from [get_ports bclk] -to [get_keepers *] # BCLK 已同步无需时序 set_false_path -from [get_ports lrclk] -to [get_keepers *]create_clock的周期要和实际晶振对上12.288 MHz 对应 81.380 ns。异步输入的set_false_path是必须的不然工具会尝试对跨时钟域路径做建立保持收敛越收越乱。SignalTap 的采样深度开 4K 就够看 PWM 边沿和死区触发条件挂在tick上每次采样看一帧能直接对上 I2S 波形。4. 死区插入、栅极驱动与 LC 滤波的参数落地4.1 死区时间的计算与 Verilog 实现半桥的上下管不能同时导通FPGA 输出的互补 PWM 必须插入死区。死区太短会直通炸管太长会让输出波形在过零点附近失真THD 明显变差。死区时间等于计数时钟的周期乘以计数值。50 MHz 时钟下20 个计数就是 400 ns30 个是 600 ns。常见做法取 200 到 500 ns具体看 MOSFET 的开关速度和栅极驱动能力。// deadtime.v 对互补 PWM 插入死区输出上下管栅极信号 module deadtime #( parameter CNT_W 8 // 死区计数位宽 )( input wire clk, // 计数时钟例如 50MHz input wire rst_n, input wire pwm_in, // 来自 sigma-delta 的 1bit 流 output reg gate_h, // 上管栅极 output reg gate_l // 下管栅极 ); reg [CNT_W-1:0] cnt; // 死区倒计数 reg pwm_d; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 0; pwm_d 1b0; gate_h 1b0; gate_l 1b0; end else begin pwm_d pwm_in; if (pwm_d ! pwm_in) begin // 桥臂需要换向 cnt {CNT_W{1b1}}; // 载入死区计数值 gate_h 1b0; // 上下管先关断 gate_l 1b0; end else if (cnt ! 0) begin cnt cnt - 1b1; // 死区倒计时 end else begin gate_h pwm_d; // 死区结束后恢复输出 gate_l ~pwm_d; end end end endmodule逻辑说明CNT_W决定死区最大值8 位在 50 MHz 下最大约 5.1 μs覆盖绝大多数场景。换向检测用了一级寄存pwm_d所以死区起点比pwm_in边沿晚一个时钟这个误差在百纳秒量级对功放没影响。4.2 载波频率与死区对 THD 的取舍下表是几个常见组合在 48 kHz 采样、8 Ω 负载下的经验值实测会因 MOSFET 和 LC 参数而有出入。调制时钟OSR环路阶数带内 SNR 理论值死区 300 ns 时的 THD 量级6.144 MHz128一阶约 66 dB0.1%12.288 MHz256一阶约 75 dB0.05%12.288 MHz256二阶约 110 dB0.02%24.576 MHz512二阶约 125 dB0.01%死区带来的失真是「线性度换安全」的代价300 ns 死区在 384 kHz 的等效载波下占空比误差不大但换成 100 kHz 的低载波300 ns 就是 3% 的周期过零失真会明显听出来。选择上载波频率越高、死区占比越小THD 越好代价是栅极损耗上升。4.3 LC 低通滤波器的截止频率与元件取值Σ-Δ 输出的是高频开关流必须靠 LC 把 12 MHz 附近的噪声滤掉。截止频率一般取载波频段的十分之一到五分之一也就是 30 到 80 kHz 之间。# lc_filter.py 给定 L、C 求截止频率并算特征阻抗 import math def fc_khz(L_uh, C_uf): return 1.0 / (2 * math.pi * math.sqrt(L_uh * 1e-6 * C_uf * 1e-6)) / 1e3 def z0(L_uh, C_uf): return math.sqrt(L_uh * 1e-6 / (C_uf * 1e-6)) for L in (10, 22, 33): for C in (0.47, 1.0, 2.2): print(fL{L:3d}uH C{C:4.2f}uF fc{fc_khz(L,C):6.1f}kHz Z0{z0(L,C):5.1f}ohm)参数说明特征阻抗Z0要贴近负载阻抗通常是 4 或 8 Ω否则滤波器 Q 值过高会在截止点附近隆起听感上低频发闷。22 μH 配 1 μF 得到 33.9 kHz 截止和 4.7 Ω 特征阻抗是比较常见的搭配。电感要选饱和电流足够大的型号功放峰值电流下饱和会让 THD 急剧恶化。4.4 栅极驱动与自举电容FPGA 的 3.3V LVCMOS 输出直接驱动 MOSFET 栅极电流不够必须经过半桥驱动芯片。自举电容取 100 nF 到 1 μF 之间容值太小在上管长时间导通时会掉压导致上管驱动不足发热。栅极串联电阻取 4.7 到 22 Ω太小会让开关沿过快、EMI 超标太大会拉长开关时间、增加死区压力。5. 进阶SignalTap 抓波形、THDN 定位与几个必踩的坑5.1 复位同步与跨时钟域打拍上电偶发不启动十次里坏一次八成是复位没同步。异步复位释放的时刻如果落在时钟沿附近触发器可能进入亚稳态。标准做法是异步复位、同步释放用两级触发器对复位信号打拍再分布到各模块。跨时钟域的数据流不止 BCLK 一处。如果后面加了数字音量或者 EQ模块之间的采样率可能不同必须用异步 FIFO 或者握手不能直接把数据线跨过去。用双口 M9K 搭一个 256 深的异步 FIFO读写指针用格雷码同步是这类设计里最省心的做法。5.2 用 FFT 定位底噪和量化噪声折叠输出底噪偏高先别急着换电容。把功放输出经过衰减后接回 ADC用 FPGA 采一段 65536 点数据做 FFT看噪声分布。如果是低频段抬升通常是音源抖动或者地环路如果是 100 kHz 到 1 MHz 之间有一堆尖峰那八成是 LC 滤波没滤干净或者 Σ-Δ 环路的 OSR 设低了。还有一种隐蔽情况调制时钟和采样率成整数比量化噪声会周期性折叠回音频带内形成非谐波杂散。解决方法是把 MCLK 换成和 fs 不成简单整数比的频率或者把环路阶数提高把噪声推得更远。5.3 一张排错对照表现象可能原因排查手段上电偶发不工作复位未同步检查复位路径是否打两拍输出电平只有一半上管自举电容失效示波器看栅极波形测上管 Vgs静态电流偏大、发热死区不足导致直通用 SignalTap 抓 gate_h/gate_l 重叠底噪有 50 Hz 及其谐波地环路或数字地污染模拟地分离地平面单点接地高频段有密集尖峰LC 截止频率过高按 4.3 节重算 L、C综合后输出全是噪声环路被优化掉加 keep 属性并检查输出端口连接通道间相位不一致各路独立计数器未复位用统一 tick 同步所有通道抓 gate_h 和 gate_l 的重叠时SignalTap 的采样率要至少是系统时钟的 4 倍50 MHz 时钟下用 200 MHz 采样才看得清百纳秒级的死区。触发条件设成gate_h gate_l一旦抓到就说明死区逻辑有洞。本文还有配套的精品资源点击获取
返回列表