很多同学做电赛信号类题目(比如 2023 年 H 题信号分离装置)时,第一次接触 FIR 滤波器都会卡在「系数」上:
代码里这一长串数字到底是啥? 为什么注释说要替换成 MATLAB 生成的值? 我自己随便写几个数不行吗?
这篇文章全程用生活化类比 + 大白话,从底层逻辑到实操步骤讲透,零基础也能看懂,看完就能上手用。
一、先搞懂:FIR 滤波器到底是什么?
1. 一个生活化的类比
想象你手里有一杯混在一起的「细沙子」和「粗石子」,你想把两者分开,你会用什么? 答案很简单:筛子。
- 孔小的筛子:沙子漏下去,石子留在上面 → 留下细的、挡住粗的
- 孔大的筛子:石子也能漏下去 → 粗的细的都过去
对应到电路的世界里:
- 混合的沙子石子 = 混合在一起的不同频率的电信号(比如题目里的 50kHz 低频信号 + 100kHz 高频信号)
- 筛子 =FIR 滤波器,专门用来「按频率分拣信号」
- 细沙子 = 低频信号,粗石子 = 高频信号
- 让低频通过、挡住高频 → 低通 FIR 滤波器
- 让高频通过、挡住低频 → 高通 FIR 滤波器
2. 那「FIR 系数」又是什么?
筛子的过滤效果,完全由「孔的大小、孔的密度、筛子的层数」决定。 对应到 FIR 滤波器里,决定滤波效果的这整套「设计参数」,就是 FIR 系数。
更直白一点:
1024 阶的 FIR 滤波器,就有 1024 个系数; 每个系数,对应一个「权重值」; 滤波器干活的本质,就是把历史采样值按权重加权求和。
3. 对应代码:它到底是怎么干活的?
原理非常简单,就是小学级别的「乘法 + 加法」,没有任何玄学。
你的 FPGA 每秒对信号采样很多次(比如 100MHz 就是每秒采 1 亿次),每采一个数就存起来,一共存最近的 1024 个采样值。 每来一个新数据,滤波器就做一件事:
把这 1024 个历史数值,每个都乘上自己对应的权重系数,再把所有结果加起来,最终的和就是「滤波后的输出值」。
举个最小的例子(3 阶 FIR,只有 3 个系数):
- 滤波器系数:
[0.25, 0.5, 0.25] - 最近 3 个采样值:
[2, 4, 6] - 滤波结果 = 2×0.25 + 4×0.5 + 6×0.25 = 0.5 + 2 + 1.5 = 4
1024 阶的 FIR 逻辑完全一模一样,只是从 3 个数相加,变成了 1024 个数相加。 代码里的 BRAM 延迟线,就是用来存这 1024 个历史采样值的;MAC 乘累加单元,就是专门算「乘法 + 加法」的硬件模块。
二、为什么系数不能自己乱写,必须用 MATLAB 生成?
核心一句话:你想要的「刚好留住 50kHz、完美挡住 100kHz」这个效果,对应的 1024 个权重是多少,人工根本算不出来。
展开讲 3 个最实在的原因,每一个都对应电赛里的真实坑点:
1. 精度要求极高,差一点效果就崩
要做到「50kHz 信号几乎原样通过,80kHz 以上的信号削弱 100 倍」,这 1024 个系数的取值是非常精密的。 就像你要做一个筛子,刚好留住 0.05mm 的沙子、挡住 0.1mm 的石子,孔的大小、密度、排列都要精准计算,不是随手画几个孔就能用的。
系数差一点点,就会出现:
- 该保留的信号被衰减,输出幅值变小
- 该滤掉的信号没挡住,分离不干净
- 严重的时候甚至完全分不出两路信号
2. 数量太多,人工根本算不完
1024 个系数,每个都是精确的小数,还要满足「左右严格对称」的要求(这样输出波形才不会变形)。 人工算 1024 个精准的数,算到明年也算不对,还很容易算错。
MATLAB 里有现成的专业工具函数(比如fir1),你只要告诉它 3 件事:
- 你的采样率是多少(比如 100MHz)
- 截止频率是多少(比如 60kHz 以下通过)
- 滤波器要多少阶(比如 1024 阶)
它 1 秒钟就能算出 1024 个精准的系数,还能直接生成你需要的整数格式,复制粘贴就能用。这是电子行业的标准做法,不是什么 “偷懒技巧”。
3. 没有通用系数,参数一变全得换
FIR 系数不是万能的,它和你的硬件参数是强绑定的。只要下面任意一个参数变了,所有系数都要重新生成:
- 采样率变了(比如从 100MHz 改成 50MHz)
- 截止频率变了(比如从 60kHz 改成 70kHz)
- 滤波器阶数变了(比如从 1024 阶改成 512 阶)
不存在一套 “拿过来就能用在所有项目里” 的通用系数,必须根据你的实际电路参数,用工具生成对应的值。
三、为什么代码里的系数是 “占位符”?
很多网上的示例代码,只中间会写前十几个和最后几个系数,注释一句「由 MATLAB 生成」,不是作者不想写全,原因很现实:
- 太长了:1024 个系数全写出来,要占几十上百行代码,示例代码会非常臃肿,影响阅读;
- 不通用:作者的采样率、截止频率和你的硬件不一定匹配,全写给你也用不了。
⚠️重点提醒:直接用占位符代码编译上板,一定会出问题。 相当于一个筛子大部分孔的大小都是错的,根本滤不干净,轻则分离效果差,重则完全看不到分离后的波形,基本分都拿不到。
四、高频专业名词大白话对照表
碰到术语不用慌,下面这张表可以直接存下来对照看:
表格
| 专业名词 | 大白话解释 |
|---|---|
| 阶数(如 1024 阶) | 系数的总个数,相当于筛子的层数。阶数越高,筛得越干净,但运算量越大、占用资源越多。 |
| 低通 / 高通 FIR | 低通 = 低频信号通过、高频挡住;高通 = 高频信号通过、低频挡住。 |
| 线性相位 | 所有频率的信号通过滤波器后,延迟时间完全一样。最大的好处是波形不会变形,正弦波进去还是正弦波。要实现这点,系数必须左右严格对称,MATLAB 生成的系数天然满足。 |
| Q1.15 定点格式 | FPGA 不擅长算小数,所以把所有小数放大 32768 倍,转成整数来运算,最后再缩回去。比如 0.5 就存成 16384。 |
| 窗函数(汉宁窗、汉明窗等) | 设计滤波器的不同「配方」。不同配方效果不同:有的筛得干净但过渡慢,有的边缘锐利但旁瓣大,电赛常规题目用汉宁窗就足够。 |
| 通带 / 阻带 | 通带 = 能顺利通过的频率范围;阻带 = 被大幅削弱、挡住的频率范围。 |
| 阻带衰减 | 能把不需要的信号削弱多少倍,比如 40dB 就是削弱 100 倍,60dB 就是削弱 1000 倍。 |
| BRAM 延迟线 | FPGA 里的一块专用高速内存,专门用来存最近的 1024 个采样值,方便每次拿出来乘系数相加。 |
| MAC 乘累加 | FPGA 里专门算「乘法 + 加法」的硬件单元,FIR 滤波的核心运算全靠它。 |
五、电赛实操:FIR 滤波器完整落地步骤
讲完原理,给你一套电赛里直接能用的标准流程,照着做就能把 FIR 滤波器跑起来:
步骤 1:确定你的设计指标
先根据题目要求定参数,以分离 50kHz 和 100kHz 为例:
- 系统采样率:100MHz
- 低通滤波器:通带截止 60kHz,阻带起始 80kHz
- 高通滤波器:通带起始 90kHz,阻带截止 70kHz
- 阶数:1024 阶
- 窗函数:汉宁窗
步骤 2:用 MATLAB 生成系数
把下面的脚本复制到 MATLAB 里运行,一键就能生成 Verilog 能用的系数文件:
matlab
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % 电赛FIR滤波器系数生成脚本 % 输出16位Q1.15定点格式,可直接复制到Verilog代码 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% clear; clc; %% 1. 设置参数 Fs = 100e6; % 采样率 100MHz N = 1024; % 滤波器阶数 F_pass_low = 60e3; % 低通通带截止 60kHz %% 2. 设计低通滤波器并转定点 h_low = fir1(N-1, F_pass_low/(Fs/2), 'low', hann(N)); h_low_fix = round(h_low * 32767); % 转16位Q1.15整数 %% 3. 输出Verilog格式系数到文件 fid = fopen('coeff_low.txt', 'w'); for i = 1:N val = h_low_fix(i); if val >= 0 fprintf(fid, '16''d%d,', val); else fprintf(fid, '-16''d%d,', abs(val)); end if mod(i, 8) == 0 fprintf(fid, '\n'); end end fclose(fid); %% 4. 查看幅频响应,验证效果 fvtool(h_low, 1, 'Fs', Fs); title('低通FIR滤波器幅频响应'); disp('系数生成完成,已保存为 coeff_low.txt');运行后会弹出一个窗口,显示滤波器的幅频曲线,你可以直观看到通带平不平、阻带衰减够不够。
步骤 3:把系数替换进 Verilog 代码
打开生成的coeff_low.txt,把里面的内容全选复制,替换掉代码里系数数组的占位内容,高通滤波器同理操作。
步骤 4:上板验证
- 先输入单频信号,用示波器测输出幅值,确认通带内衰减小、阻带内衰减大;
- 再输入混合信号,观测分离效果,确认两路信号无明显串扰。
六、电赛常见踩坑提醒
- 不要随便改单个系数:改一个就破坏了整体对称性,线性相位会消失,输出波形会失真。
- 注意采样率匹配:MATLAB 里填的采样率,必须和 FPGA 实际的采样时钟一模一样,差一点截止频率就会偏。
- 防止溢出:两路信号相加、乘累加后都要注意位宽,要么加饱和截断,要么扩展位宽,避免波形削顶。
- 阶数不是越高越好:阶数越高延迟越大,资源占用也越多,满足题目要求即可,不用盲目堆高阶。
最后总结
- FIR 滤波器本质就是一个「按频率分拣信号的电子筛子」;
- FIR 系数就是这个筛子的「设计图纸」,决定了滤波效果的好坏;
- 系数数量多、精度要求高,人工算不现实,用 MATLAB 生成是行业标准做法;
- 电赛落地流程:定指标 → MATLAB 生成系数 → 替换进代码 → 上板验证。
FIR 滤波器是电赛信号题的基础核心,搞懂原理再动手,比盲目抄代码调 bug 效率高得多。
我接着用「筛沙子」的类比,把这些设计参数一个个掰开讲,每个都讲清:是什么、对应筛子的啥属性、改了会有什么影响,全程不用公式,看完就知道为什么要这么设。
这些参数本质上就是你给 MATLAB 下的「定制筛子的规格要求」,你说清楚要求,工具才能给你生成刚好能用的系数。
一、逐个参数大白话拆解
1. 采样率(Fs)
大白话定义:FPGA 每秒对输入信号采集多少个数据点,单位是 Hz。
- 100MHz 采样率 = 每秒采 1 亿个点
- 8.192MHz 采样率 = 每秒采 819.2 万个点
筛子类比:相当于你每秒舀多少勺沙子过筛。舀得越快,单位时间处理的量越大。
为什么重要:
- 它决定了你能处理的最高信号频率(奈奎斯特极限:最高只能处理采样率一半的频率)。100MHz 采样率,最多只能处理 50MHz 以内的信号,超过就会混叠失真。
- 设计系数和实际硬件必须严格一致。你 MATLAB 里填 100MHz 生成的系数,放到 50MHz 的 FPGA 上,截止频率会直接偏一半,滤波器完全不准。
2. 滤波器阶数(N,也叫抽头数)
大白话定义:滤波器一共有多少个系数,也就是加权求和的时候,要拿多少个历史数据来算。
- 1024 阶 = 1024 个系数 = 每次算结果要乘 1024 次、加 1024 次
筛子类比:筛子的层数。一层筛不干净,就叠十层、一百层。层数越多,筛得越干净,但筛子越沉、筛得越慢。
改了会怎样:
- 阶数越高 → 筛选的边界越锐利(过渡带越窄)、拦得越干净(阻带衰减越大)
- 阶数越高 → 占用 FPGA 资源越多(乘法器、内存都要更多)、信号延迟越大
- 电赛里够用就行,不用盲目堆高阶,1024 阶应对基础题完全足够。
3. 通带截止频率(Fpass)
大白话定义:能几乎原样通过、几乎不被削弱的频率边界。
- 低通滤波器:低于这个频率的信号,都能顺利通过
- 高通滤波器:高于这个频率的信号,都能顺利通过
筛子类比:筛子的标准孔径。小于孔径的沙子,全能漏下去,几乎不被拦住。
举例子: 低通设置通带截止 60kHz → 50kHz 的信号在通带里,输出幅值和输入几乎一样,损失很小。
4. 阻带起始频率(Fstop)
大白话定义:从这个频率开始,信号会被大幅削弱,基本过不去。
- 低通滤波器:高于这个频率的信号,都会被大幅度衰减
筛子类比:肯定拦得住的石子尺寸。大于这个尺寸的石子,基本全被扣在筛子上,漏不下去。
举例子: 低通设置阻带起始 80kHz → 100kHz 的信号在阻带里,会被削弱几十上百倍,基本被滤干净。
5. 过渡带(Transition Band)
大白话定义:通带和阻带中间的那段频率区间,信号是慢慢被削弱的,不是一刀切。
- 比如通带 60kHz、阻带 80kHz,过渡带就是 60kHz ~ 80kHz 这 20kHz 的区间
筛子类比:筛孔不是 100% 均匀的,总有一些不大不小的颗粒,会漏下去一部分、拦住一部分,不是全过也不是全拦。
重点常识: 现实里不存在「60kHz 全过、60.001kHz 全挡」的理想滤波器,所有真实滤波器都有过渡带。
- 阶数越高,过渡带越窄,筛选边界越锐利
- 阶数越低,过渡带越宽,边界越模糊
这也是为什么题目要分离 50kHz 和 100kHz,我们不把截止设成 50kHz:要把 50kHz 放在通带里、100kHz 放在阻带里,中间留够过渡带的空间,不然 50kHz 刚好落在过渡带上,会被衰减。
6. 阻带衰减
大白话定义:阻带里的信号,会被削弱到原来的多少分之一,单位是 dB(分贝)。
- 不用记公式,记住常用值就行:
- 20dB = 削弱到 1/10
- 40dB = 削弱到 1/100
- 60dB = 削弱到 1/1000
- 数字越大,衰减越狠,滤得越干净
筛子类比:筛子拦石子的干净程度。40dB 就是 100 颗石子里只能漏下去 1 颗;60dB 就是 1000 颗里才漏 1 颗。
电赛参考:一般 40dB 以上就够用,分离出来的两路信号互相串扰很小,示波器上基本看不到干扰。
7. 通带纹波
大白话定义:通带里的信号,幅值波动有多大,也就是通带平不平。
- 纹波小 = 通带里所有频率的增益几乎一样,输出幅值稳定
- 纹波大 = 通带里有的频率增益高、有的低,信号会变形
筛子类比:合格的筛子,所有小于孔径的沙子都应该漏得一样快,不能有的漏得快、有的漏得慢。
电赛参考:用窗函数法设计的 FIR,通带纹波本身就很小,比如汉宁窗的纹波可以忽略,基础题基本不用操心这个参数。
8. 窗函数(Window Function)
大白话定义:设计滤波器的「数学配方 / 工艺」,不同的窗函数,做出来的滤波器特性不一样。
筛子类比:不同的筛子编织工艺。
- 有的工艺做出来的筛子,边缘过渡快,但拦得不够干净
- 有的工艺拦得特别干净,但边缘过渡慢、边界模糊
常见的几种窗函数对比(电赛够用版):
表格
| 窗函数 | 阻带衰减 | 过渡带 | 特点 |
|---|---|---|---|
| 矩形窗 | 最差(21dB) | 最窄 | 边界最锐利,但拦得最不干净,一般不用 |
| 汉宁窗 | 中等(44dB) | 中等 | 均衡性最好,电赛最常用,不容易出错 |
| 汉明窗 | 较好(53dB) | 略宽于汉宁 | 比汉宁窗干净一点,过渡差不多 |
| 布莱克曼窗 | 很好(74dB) | 最宽 | 滤得最干净,但相同阶数下过渡带最宽 |
电赛选型建议:无脑选汉宁窗就行,均衡、好调试、不容易出问题。
二、参数之间的「此消彼长」关系
设计滤波器不是参数越极端越好,它们之间是互相约束的,核心记住 3 条:
- 相同阶数下:要阻带衰减越大,过渡带就越宽;要过渡带越窄,阻带衰减就越小。
- 既要窄过渡带、又要大衰减:只能提高滤波器阶数,代价是占用更多资源、延迟变大。
- 采样率越高:相同阶数下,过渡带的绝对宽度就越大(比如 100MHz 下过渡带 20kHz,换成 50MHz 下过渡带就只有 10kHz)。
三、结合电赛 H 题,完整的参数选型逻辑
给你捋一遍为什么常规方案会选这些参数,你就懂设计思路了:
- 需求:分离 50kHz 和 100kHz 的混合信号
- 选采样率:FPGA 系统时钟 100MHz,直接用 100MHz 采样,简单省事,也远高于信号频率的 2 倍,不会混叠。
- 定通带和阻带:
- 低通:要留住 50kHz,所以通带截止设 60kHz(留 10kHz 余量);要挡住 100kHz,所以阻带起始设 80kHz(留 20kHz 余量)。过渡带 20kHz。
- 高通:反过来,通带起始 90kHz,阻带截止 70kHz。
- 选阶数:1024 阶汉宁窗,刚好能在 20kHz 过渡带里做到 40dB 以上的衰减,资源占用也不高,性价比最高。
- 选窗函数:汉宁窗,均衡稳定,调试简单。
最后总结
这些参数本质就是「你想要什么样的筛子」的完整描述:
- 采样率 = 处理速度
- 阶数 = 筛子层数
- 通带 / 阻带 = 孔的大小、拦石子的尺寸
- 窗函数 = 制作工艺
你把这些要求告诉 MATLAB,它就会算出对应的 1024 个系数,你粘进代码里,滤波器就能按你的要求干活了。