简介:2007年全国大学生电子设计竞赛A题「音频信号分析仪」完整设计方案文档,面向备赛电赛的本科生、指导教师以及关注音频频谱测量的电子工程师。文档以在Altera Cyclone II FPGA中嵌入Nios II软核、用FFT实现数字式频谱分析为主线,逐项论证方案取舍:FFT计算方式选择、12位AD1674采样电路、NE5532与TL084前端可控增益放大与峰值保持电路(×1、×2、×20、×200四档)、8阶IIR切比雪夫II型数字低通滤波替代有源模拟滤波,并给出按帕斯瓦尔定律计算总功率与各频率分量功率的方法,50mV至5V输入范围内误差控制在1%以内。压缩包为1个docx文件,约175KB,体积轻便。目前已有365人学习,适合赛前梳理系统框架、对照选型依据与电路细节,也可作为课程设计报告的模板参考。
1. 一块Cyclone II上跑Nios II做频谱分析,2007年A题的取舍
2007年全国大学生电子设计竞赛A题要求做一个音频信号分析仪,四天三夜,要测总功率、各频率分量功率、周期性、失真度。拿到题的人大致分两派:一派堆模拟滤波器做扫描式频谱仪,另一派找DSP芯片或者高性能单片机。真正落地稳的往往是第三条路——用Altera Cyclone II FPGA内嵌Nios II软核,把FFT放在软核里用C跑,硬件只负责采样控制和数字滤波。反直觉的地方在于:FFT是运算密集型,很多人以为必须上VHDL硬件实现才够快,但四天内写出稳定的VHDL FFT几乎不可能,而Nios II支持C语言,一份现成的C版FFT稍改就能烧进去。这套方案后来成了不少电赛队伍找参考模板时会翻到的经典结构,也适合想搞清FPGA软硬协同分工的工程师顺着看一遍。
2. 采样率、频率分辨率与AD1674的选型账
2.1 奈奎斯特、二次采样与分辨率三角关系
本题输入信号最高频率10kHz,奈奎斯特要求采样率不低于两倍,也就是20kHz。但直接用20kHz采再送去FFT,抗混叠压力全压在前端模拟滤波器上。工程做法是先以较高速率采样,在FPGA里用数字滤波器做精确抗混叠,滤波后再二次采样(降采样)把速率降下来,减少FFT的运算量。原始采样率一般取二次采样率的3倍左右,给数字滤波器的过渡带留余量。三个量里,频率分辨率Δf、FFT点数N、二次采样率fs2满足:
fs2 = N × Δf,FFT能表示的最高频率 = fs2 / 2
要做100Hz分辨率,N取2的幂方便蝶形运算,N=256时fs2 = 256×100 = 25.6kHz,测量频率上限25.6k/2 = 12.8kHz,覆盖10kHz够用。分辨率和点数不能随便拍,浪费点数就是浪费Nios II的CPU周期,这在软核上是要命的。
2.2 为什么用12位AD1674而不是8位
题目要求功率测量误差在5%以内。8位A/D满量程256级,双极性输入10Vpp时每级约39mV,量化噪声摊到功率上已经吃不消。AD1674是12位、双极性满偏±5V(即10Vpp),相对5Vpp信号分辨率约1.2mV,余量足够。它100kHz的采样速率也覆盖了前面算出的76.8kHz原始采样需求。下面这段脚本用来反推每档分辨率的参数,顺手验证选型:
import math fmax = 10000 # 输入信号最高频率 Hz def design(resolution): # N 取满足 N*dF >= 2*fmax 的最小 2 的幂 n = 2 ** math.ceil(math.log2(2 * fmax / resolution)) n = max(n, 256) fs2 = n * resolution # 二次采样率 fs_raw = fs2 * 3 # 原始采样率,留 3 倍抗混叠余量 return n, fs2, fs_raw, fs2 / 2 for r in [100, 50, 20, 10]: n, fs2, fs_raw, f_meas = design(r) print(f"分辨率{r:>3}Hz: N={n:>4d}, 二次采样={fs2/1000:6.2f}kHz, " f"原始采样={fs_raw/1000:6.2f}kHz, 测量上限={f_meas/1000:6.2f}kHz")逻辑上,2*fmax/resolution就是让测量上限刚好压过fmax所需的最小点数;取2的幂是为了复用基2 FFT;fs2*3是给数字滤波器的过渡带留空间,二次采样前带外已经被压得足够低。参数说明:resolution是要设的频率分辨率档位,fmax是本设计的10kHz。跑出来会发现100Hz和50Hz两档二次采样率都是25.6kHz,20Hz和10Hz档降到20.24kHz附近。
2.3 四档分辨率实际参数
设计里定了四档分辨率,方便在不同场景切换。低分辨率档刷新快、运算省,高分辨率档能分辨更密的谱线:
| 频率分辨率 | FFT点数 | FFT测量频率上限 | 二次采样率 | 原始采样率 |
|---|---|---|---|---|
| 100Hz | 256 | 12.8kHz | 25.6kHz | 76.8kHz |
| 50Hz | 512 | 12.8kHz | 25.6kHz | 76.8kHz |
| 20Hz | 1024 | 10.24kHz | 20.24kHz | 60.72kHz |
| 10Hz | 2048 | 10.24kHz | 20.24kHz | 60.72kHz |
提示:分辨率越低不代表越差。测基频低的信号时,谱线间隔小才分得开,这时才需要往高点数档切。默认档建议停在50Hz或100Hz,省CPU。
四档的切换由Nios II软件控制采样控制器分频系数和FFT点数,硬件侧只改分频寄存器,不需要重新综合。这点在FPGA里很关键:如果每次改分辨率都要重新编译,四天时间根本不够用。
3. 可控增益前端与8阶切比雪夫II型IIR滤波器落地
3.1 NE5532四档放大通道与峰值保持选档
前端要先解决动态范围:输入峰峰值从5Vpp一路到50mVpp,跨度100倍。如果只用固定增益,小信号进A/D后有效位太少,精度直接崩。方案是把放大电路分成四档,用模拟开关切换,放大倍数以当前信号峰值为选择基准:
| 档位 | 放大倍数 | 适配输入峰峰值 | 主运放 |
|---|---|---|---|
| 1 | ×1 | 10Vpp | NE5532 |
| 2 | ×2 | 5Vpp | NE5532 |
| 3 | ×20 | 500mVpp | NE5532 |
| 4 | ×200 | 50mVpp | NE5532 |
输入级先用运放跟随器并联一只50欧姆精密电阻,满足50欧姆输入阻抗要求,同时增强带负载能力;再经隔直电容进后级。NE5532是音频信号处理里的经典运放,低噪、低失真,比通用运放更适合这一档。选档靠峰值保持电路:用TL084配精密二极管和47uF充电电容记录一个FFT周期内的峰值,和参考电压比较后决定下一次采样的通道。47uF不是随便选的,电容太小漏电流会让保持电压往下掉,采样还没结束峰值就失真了。每次采样前要对电容放电,重新记录。
3.2 Chebyshev II型IIR低通滤波器设计
FFT前的抗混叠靠数字滤波器做,选切比雪夫II型(Chebyshev II)低通,原因是它通带平坦、阻带等波纹、过渡带适中,相频和群延迟特性也还过得去,很适合音频。8阶、低通用的FIR的话阶数要翻好几倍,Nios II扛不住。系数用Matlab的Filter Design & Analysis Tool算,也可以用命令行直接生成:
% 8阶 Chebyshev II 型数字低通滤波器系数设计 fs = 76800; % 原始采样率 fp = 10000; % 通带截止 10kHz fst = 12000; % 阻带起始 12kHz Rp = 1; % 通带波纹 dB(II型通带平坦,这里影响阻带) Rs = 60; % 阻带最小衰减 dB % 先估算满足指标需要的阶数 [n, Wn] = cheb2ord(fp/(fs/2), fst/(fs/2), Rp, Rs); [b, a] = cheby2(n, Rs, Wn); % 得到传递函数系数 fprintf('实际阶数 n=%d\n', n); fprintf('分子系数 b = %s\n', mat2str(b, 6)); fprintf('分母系数 a = %s\n', mat2str(a, 6));cheb2ord先根据通带、阻带、波纹指标反推最小阶数,cheby2再算出具体的b、a系数。参数说明:fst-fp是过渡带宽度,越窄需要的阶数越高;Rs=60dB决定了带外压得有多狠,混叠主要靠它挡。得到系数后进Altera DSP Builder,在可视化界面里按二阶节(biquad)级联搭出滤波器,每个二阶节一组系数。
3.3 定点化与软硬分工
DSP Builder搭出来的滤波器默认浮点,上FPGA要定点化。常见做法是系数放大2的幂次后取整,输出再右移相应位数还原。定点位宽要算好:位宽太窄,滤波器极点附近的量化误差会放大;太宽浪费逻辑资源。8阶IIR级联一般给到18~24位累加器比较稳。
分工上,自动增益控制、A/D转换控制、数字滤波用VHDL实现,FFT、功率计算、周期性判断、失真度、键盘液晶这些交给Nios II软核。两部分并行跑,硬核负责实时性强的数据通路,软核负责灵活的计算逻辑。这个分工的好处是:滤波器改系数不用动C代码,FFT算法改逻辑也不影响采样控制。
4. FFT结果怎么变成功率、周期性和失真度
4.1 帕斯瓦尔定律从连续到离散
测功率不用真有效值芯片,直接用FFT结果配合帕斯瓦尔定律算。连续形式是时域能量等于频域能量:时域积分等于频域积分。离散形式对应到DFT是:
Σ|x[n]|² = (1/N)Σ|X[k]|²
对单频余弦信号x[n] = A·cos(2πk₀n/N),它在第k₀点的频谱幅度|X[k₀]| = A·N/2,代回去就能反推出该频率分量的物理功率A²/2。所以每个频点的功率都能从FFT结果直接算,同时总功率就是各分量之和。这样做比真有效值芯片强的地方在于:真有效值只能测总功率,测不了单个频点的功率。
4.2 Nios II上的C FFT实现
软核跑FFT,用的是一份标准的基2 Cooley-Tukey迭代版本,稍改就能用。核心是位反转重排加三级循环的蝶形运算:
#include <math.h> #define N 256 typedef struct { double re, im; } cpx; static cpx x[N]; static cpx w[N >> 1]; /* 预计算旋转因子 W_N^k = cos(2πk/N) - j·sin(2πk/N) */ void init_twiddle(void) { for (int k = 0; k < N / 2; k++) { w[k].re = cos(2.0 * M_PI * k / N); w[k].im = -sin(2.0 * M_PI * k / N); } } /* 位反转置换:把 x[i] 与二进制倒序的 x[j] 交换 */ void bit_reverse(void) { for (int i = 1, j = 0; i < N; i++) { int bit = N >> 1; for (; j & bit; bit >>= 1) j ^= bit; j ^= bit; if (i < j) { cpx t = x[i]; x[i] = x[j]; x[j] = t; } } } void fft(void) { bit_reverse(); for (int len = 2; len <= N; len <<= 1) { /* 蝶形级数:log2(N) 级 */ int half = len >> 1; int step = N / len; /* 旋转因子抽取间隔 */ for (int i = 0; i < N; i += len) { for (int k = 0; k < half; k++) { cpx u = x[i + k]; cpx v = x[i + k + half]; cpx wv; /* v × W_N^{k·step} */ wv.re = v.re * w[k*step].re - v.im * w[k*step].im; wv.im = v.re * w[k*step].im + v.im * w[k*step].re; x[i + k].re = u.re + wv.re; x[i + k].im = u.im + wv.im; x[i + k + half].re = u.re - wv.re; x[i + k + half].im = u.im - wv.im; } } } }init_twiddle只在初始化时算一次旋转因子,避免在蝶形里反复调三角函数——软核上三角函数开销很大,这是最容易拖慢速度的坑。bit_reverse的循环用位运算做二进制倒序,比字符串反转快。fft外层控制级数,中层按块走,内层做蝶形。参数说明:N必须取2的幂;旋转因子按N/len的间隔抽取,保证每级用到的角度正确。
4.3 周期性判断与失真度
周期信号的频谱是谐波离散的,只含基频和基频整数倍分量,谱线间隔就是基频。判断方法是:从最小频率分辨率点开始往上扫,如果最低那几个点频率分量接近0(实际不为0,是一个很小的数),第一个出现峰值的地方就是基频。这个方法对基频大于最小分辨率的信号有效,基频更低的信号测不了,得加FFT点数。前面四档分辨率的用意就在这里。
失真度用谐波能量和与基波能量之比的开方表示:
/* 频域法算失真度:谐波功率和开方除以基波功率 */ double calc_thd(const double *power, int k0, int kmax) { double fund = power[k0]; /* 基波功率 */ double harm = 0.0; for (int k = 2 * k0; k <= kmax; k += k0) harm += power[k]; /* 累加各次谐波功率 */ return sqrt(harm) / fund; /* 结果乘 100% 即失真度 */ }k0是基频对应的谱线索引,kmax是测量上限;循环从2倍基频开始,每次跨一个基频,只挑谐波点,避免把噪声也累进去。算的时候要记得补偿滤波器对信号的衰减、按放大倍数还原到真实值,否则误差会偏大。
4.4 功率计算的补偿细节
FFT出来的幅度是ADC满量程下的相对值,要变成物理功率必须过两关:一是除掉前端放大倍数,二是补回数字滤波器在通带内的衰减。每档增益对应的还原系数不同,滤波器在通带内的衰减也不是常数,严格做法是按频点查补偿表。工程上如果通带平坦度足够好,可以近似成常数处理,但测误差要控制在1%以内时,最好还是逐点补偿。
5. 实测验证:输入阻抗、功率误差与动态范围
5.1 分压法测输入阻抗
输入阻抗用分压法测,简单可靠。在输入端串联一只0.1%精度的50欧姆电阻,用61/2位数字万用表分别测电阻两端和系统输入端的电压。实测时用函数发生器输出5kHz、1Vpp正弦波,电阻两端测得0.501Vpp,系统输入端0.499Vpp。按分压关系 Zin = R × (Vin / Vr),代入50 × 0.499/0.501,算出约49.8欧姆,和50欧姆目标值差距在1%以内,说明跟随器加并联电阻这一级做得对。
5.2 功率测量误差
两路信号叠加测试,一路1Vpp(0.01W)、1kHz,另一路2Vpp(0.04W)、3kHz,理论总功率0.05W。实测总功率0.05021W,最大功率分量0.0399W,次大0.01013W,各频点功率和0.05003W,各频率功率和与总功率误差0.36%。四组测试下来,总功率相对误差小于1%,各频率功率误差小于2%。
| 测试组 | 信号频率 | 理论总功率 | 实测总功率 | 误差 |
|---|---|---|---|---|
| 1 | 10kHz + 9kHz | 0.05W | 0.04978W | 0.80% |
| 2 | 1kHz + 3kHz | 0.05W | 0.05021W | 0.36% |
| 3 | 110Hz + 100Hz | 0.05W | 0.05034W | 0.45% |
| 4 | 30Hz + 20Hz | 0.05W | 0.05004W | 0.67% |
第三组110Hz和100Hz只差10Hz,落在100Hz分辨率档下能分开,误差0.45%;第四组30Hz和20Hz差10Hz,得切到20Hz或10Hz档,误差0.67%。低频率、近间隔这两组能把误差压到这个水平,说明滤波和二次采样没引入额外混叠。
5.3 动态范围与转换时间
动态范围测的是小信号和强信号共存时能不能分辨。用5V、1kHz信号叠加5kHz小信号,从100mV往下调小信号幅度,观察频谱图上能否分辨。结果100mV、50mV、30mV、10mV都能分辨,到5mV才不可分辨,动态范围比题目要求宽。转换时间方面,把分辨率设到10Hz档,用LED翻转配合秒表测,单次转换平均0.83秒,对应2048点FFT加全部计算和显示,在软核方案里属于合理区间。
注意:软核跑FFT的刷新速度受分辨率影响最大。需要实时看频谱变化时,把档位切到100Hz,单次刷新能压到0.1秒量级,用10Hz档看稳态谱就行。
5.4 失真度测量的门道
失真度测试有两组结果值得看。用1kHz方波测,理论失真度46%(算到10次谐波),系统测得46.8%,差0.8个百分点,说明高次谐波没被滤波器吃掉。用不太理想的正弦波测,失真度测试仪读0.9%,本系统读0.54%,差别来自系统测到的是10次谐波以内的能量,而失真度测试仪可能带更宽的带宽或者别的计权方式。这类偏差不是系统不准,而是两者测量口径不同,比较的时候要看清参考仪器的带宽设置。周期性测试则更直接:方波、正弦波、三角波都判为周期波,M序列判为非周期波,逻辑符合预期。
本文还有配套的精品资源,点击获取