Cyclone II+Nios II软核实现FFT音频信号分析仪
2026/9/19 1:24:34 网站建设 项目流程

简介: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测量频率上限二次采样率原始采样率
100Hz25612.8kHz25.6kHz76.8kHz
50Hz51212.8kHz25.6kHz76.8kHz
20Hz102410.24kHz20.24kHz60.72kHz
10Hz204810.24kHz20.24kHz60.72kHz

提示:分辨率越低不代表越差。测基频低的信号时,谱线间隔小才分得开,这时才需要往高点数档切。默认档建议停在50Hz或100Hz,省CPU。

四档的切换由Nios II软件控制采样控制器分频系数和FFT点数,硬件侧只改分频寄存器,不需要重新综合。这点在FPGA里很关键:如果每次改分辨率都要重新编译,四天时间根本不够用。

3. 可控增益前端与8阶切比雪夫II型IIR滤波器落地

3.1 NE5532四档放大通道与峰值保持选档

前端要先解决动态范围:输入峰峰值从5Vpp一路到50mVpp,跨度100倍。如果只用固定增益,小信号进A/D后有效位太少,精度直接崩。方案是把放大电路分成四档,用模拟开关切换,放大倍数以当前信号峰值为选择基准:

档位放大倍数适配输入峰峰值主运放
1×110VppNE5532
2×25VppNE5532
3×20500mVppNE5532
4×20050mVppNE5532

输入级先用运放跟随器并联一只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%。

测试组信号频率理论总功率实测总功率误差
110kHz + 9kHz0.05W0.04978W0.80%
21kHz + 3kHz0.05W0.05021W0.36%
3110Hz + 100Hz0.05W0.05034W0.45%
430Hz + 20Hz0.05W0.05004W0.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序列判为非周期波,逻辑符合预期。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询