接手一块AD9361板卡,最容易被忽略也最容易翻车的环节,往往不是射频前端,而是数字域里的FIR滤波器设计。AD9361作为射频捷变收发器,片内集成了完整的数字下变频/上变频链路,FIR就是这条链路里唯一可以由你完全掌控的“音色旋钮”:它决定了通带多宽、带外抑制多狠、群延迟曲线长什么样,直接关系到你BPSK、QPSK甚至更高阶调制信号的EVM和解调成功率。这篇文章主要围绕AD9361 FIR滤波器设计展开,从链路结构、指标计算、系数生成,到No-OS和Linux/Petalinux下的实操配置,再到我实际调试中踩过的坑,一次性讲清楚。适合正在用AD9361做定制带宽收发链路、或者被半带滤波和可编程FIR组合搞到头大的嵌入式射频工程师参考。
1. 先搞清楚FIR在AD9361里的位置
1.1 接收链路:ADC出来的信号要过哪些滤波
AD9361是零中频架构,RF信号经过LNA、混频之后直接落到基带I/Q,再经过可编程增益放大器后进入ADC采样。ADC出来之后可不是直接就能往DMA里丢的,中间隔了一整条数字信号处理链。典型顺序是:ADC输出 → HB3 → HB2 → HB1 → FIR → 可编程抽取 → 数字接口。
这条链路可以理解成两级设计:半带滤波器负责“粗拆”,用固定系数把高频分量快速砍掉,同时完成2倍抽取;可编程FIR负责“精修”,按照你项目需要的信道带宽、阻带衰减、通带平坦度去塑形。三个半带滤波器HB1/HB2/HB3,每一级都是2倍抽取,而且可以各自旁路。FIR则更灵活,支持1/2/4倍抽取,最大抽头数以官方手册为准,我常用的配置里128 taps足够覆盖绝大多数应用。
数据速率的关系是:输出数据速率 = ADC采样率 ÷ (HB3抽取比 × HB2抽取比 × HB1抽取比 × FIR抽取比)。
比如ADC采样率61.44 MSPS,三个半带全开,FIR再抽2,那输出就是61.44 ÷ (2×2×2×2) = 3.84 MSPS。这个公式很重要,你会反复用到。
1.2 发射链路:和接收镜像,但方向相反
发射链路是接收的镜像:数字基带数据先经过插值,然后送入FIR,再经过HB1、HB2、HB3,最后进入DAC。FIR在这里承担脉冲成型和预加重的作用,半带滤波器负责把DAC输出端的镜频压下去。
很多项目只盯着接收FIR,忽略了发射FIR。实际上发射FIR设计得过宽,DAC的镜频抑制会不够,频谱模板测试很难过;设计得过窄,又限制了调制带宽和符号速率。而且发射和接收的抽取/插值倍率必须和整个时钟树匹配,否则会出现“接收好好的,发射出去别人收不到”这类诡异问题。
1.3 为什么不能全靠模拟滤波器
有人会问:AD9361前端不是有模拟低通滤波器吗?把模拟LPF阶数做高一点不就行了?答案是工程上行不通。模拟滤波器阶数越高,面积、功耗、器件失配都跟着上来,阻带深度还受工艺和温度影响。今天调好的参数,明天板子热了可能就飘了。
数字FIR没有器件容差,一组系数在-40℃到85℃表现完全一致,调试时改系数不用换电容电阻,重新烧个固件就搞定了。但有一点必须明确:AD9361前端模拟LPF不能省,它的作用是抗混叠、防止带外强信号把ADC顶到饱和。如果模拟域就饱和了,数字域再狠也救不回来。这就是为什么你会看到AD9361参考设计里既有模拟LPF,又有数字半带和FIR,它们各管一段,谁也替不了谁。
2. FIR滤波器参数设计:从指标到系数
2.1 先定指标,再选抽取倍数
我见过不少新手上来就打开MATLAB设计一个“看起来不错”的低通滤波器,然后往AD9361里一填,结果链路就是不通。正确顺序是先定系统级指标,再算滤波器参数,最后生成系数。
举个例子,一个BPSK解调项目,符号速率1 Msps,ADC采样率30.72 MSPS,我们期望基带输出数据速率15.36 MSPS。这时FIR抽取倍率选2就合适,三个半带滤波器也要一起参与抽取,最终把速率从30.72降到15.36。BPSK信号主瓣宽度是2倍符号速率,即2 MHz双边带宽,考虑脉冲成型余量,通带可以定在±0.7 MHz左右,阻带起始点设在±1.5 MHz,这样过渡带大约0.8 MHz。带外抑制要求60 dB以上,通带纹波不要超过0.1 dB。
这里有一个工程常识:通带宽度和符号速率之间要留够余量,尤其是BPSK这种恒包络但主瓣比较宽的调制方式,FIR通带设成跟信号带宽一样,星座点一定会发散。后面我会专门讲这个现象。
2.2 阶数到底怎么算
滤波器阶数取决于过渡带宽度和阻带衰减。过渡带越窄、阻带衰减越大,需要的抽头数越多。工程上可以用凯泽窗近似公式估算:N ≈ (Astop - 7.95) ÷ (2.285 × Δω),其中Δω是过渡带宽度归一化到2π的值。
以上面的例子来算,输入到FIR的数据速率是15.36 MSPS,过渡带0.8 MHz,Δω = 2π × 0.8 ÷ 15.36 ≈ 0.327 rad/sample。Astop取60 dB,N ≈ (60 - 7.95) ÷ (2.285 × 0.327) ≈ 69.6,取整大概是70个抽头。工程上我不会卡着70去设计,直接上128 taps,把过渡带做陡一点、阻带衰减再压深一点,反正AD9361的FIR资源足够。
这里要提醒一句:抽头数越多,群延迟越大,环路延迟也越大。做简单点对点通信无所谓,但如果要做闭环或对时延敏感的系统,阶数就要有所取舍。
2.3 系数生成和归一化处理
我一般用MATLAB的Filter Designer,或者直接用等纹波设计函数生成系数。等纹波设计比窗函数设计在同等阶数下阻带衰减更均匀,更适合AD9361这种可编程FIR。
fs = 15.36e6; Fpass = 0.7e6; Fstop = 1.5e6; Apass = 0.1; % dB Astop = 60; % dB d = fdesign.lowpass('Fp,Fst,Ap,Ast', Fpass, Fstop, Apass, Astop, fs); Hd = design(d, 'equiripple'); b = Hd.Numerator; % 归一化到0 dB直流增益 b = b / sum(b); % 转16位有符号补码 b16 = round(b * 2^15); b16(b16 > 32767) = 32767; b16(b16 < -32768) = -32768;生成的b16就是可以直接填进AD9361寄存器或者No-OS配置数组的系数。注意这里的归一化是除以sum(b),让直流增益为1,而不是除以max(abs(b))。两者区别在于:除以max会把通带增益压到0 dB以下,白白浪费动态范围;除以sum能保留通带增益,同时确保系数不溢出。
另外,AD9361的FIR系数格式是16位有符号定点数,范围[-1, 1),写入时要用补码形式。用MATLAB导出时,负数会显示成补码值,比如-1对应0xFFFF,这个在C数组和设备树里都要注意,别拿原码直接填。
3. 实操:在AD9361上把FIR跑起来
3.1 推荐用ADI官方Filter Design Wizard
自己手算系数然后填寄存器当然可以,但效率不高。ADI官方提供了一个叫Filter Design Wizard的工具,专门用来给AD9361生成整条滤波链路配置,包括时钟树、半带抽取组合、FIR抽取倍率、FIR系数,一步到位。
操作流程大概是:先设置参考时钟频率和本振频率,再设定需要的RX/TX数据速率,工具会自动帮你搭配半带和FIR的抽取/插值倍率。然后在FIR参数页面填通带、阻带、纹波和衰减,工具会实时显示系数长度和预估的群延迟。最后直接导出C头文件、寄存器脚本或者设备树片段,非常方便。
这个工具的最大好处是不会算错时钟链。手动算的话,半带和FIR组合错了,初始化时AD9361直接给你返回错误,或者输出速率对不上,查半天都查不到原因。
3.2 No-OS工程里如何配置FIR
如果你用的是ADI的No-OS驱动,配置FIR很简单。打开ad9361_config.c,修改这几个地方:
#define RX_FIR_DECIMATION 2 #define TX_FIR_INTERPOLATION 2 #define RX_FIR_NUM_TAPS 128 #define TX_FIR_NUM_TAPS 128 int16_t rx_fir_coeff[RX_FIR_NUM_TAPS] = { 0x0001, 0xFFFE, 0x0002, // 示例系数,实际用工具生成 }; int16_t tx_fir_coeff[TX_FIR_NUM_TAPS] = { 0x0001, 0xFFFE, 0x0002, };然后在主流程里,初始化顺序非常关键:先ad9361_init做时钟链和基础配置,再调用ad9361_set_rx_fir_config和ad9361_set_tx_fir_config下发系数,最后设置采样率和增益。如果你的工程里顺序反了,或者只配置了RX没配置TX,初始化极大概率会失败。
我给一个调试建议:第一次跑通时,把所有半带旁路、FIR旁路,先把最原始的链路打通,确认ADC/DAC和DMA没问题。之后再一级一级使能HB3、HB2、HB1,最后再开FIR。这样做的好处是,一旦哪一级把信号搞坏了,频谱上一眼就能看出来。
3.3 在Linux/Petalinux里配置FIR和设备树迁移
很多实际项目不是在裸机上跑,而是在嵌入式Linux里用ADI的驱动。这种情况下,FIR系数的下放通常是靠设备树完成。AD9361的Linux驱动支持从设备树读取adi,rx-fir-config和adi,tx-fir-config属性,里面就是16位补码系数数组。
设备树片段大致长这个样子:
&ad9361 { adi,rx-fir-config = <0xFFF20000 0x00040002 0x00010003 ...>; adi,rx-fir-decimation = <2>; adi,tx-fir-interpolation = <2>; };具体属性名和内核版本有关,拿到一个新BSP先查Documentation/devicetree/bindings/iio/adc/adi,ad9361.yaml,别凭记忆写。
回到一个很多人问过的实际问题:如何将AD9361原有设备树移到新建Petalinux工程里。我的做法分三步:
- 在旧工程里找出一份验证过的设备树源文件,提取
ad9361节点下的FIR相关属性,原样复制到新工程的dtsi文件里。 - 如果旧工程只有寄存器脚本,没有设备树,那就先跑No-OS,把系数导出来,再用ADI的Filter Design Wizard重新生成设备树片段。
- 给新Petalinux工程编译前,先确认meta-adi层的版本和内核版本匹配。不匹配的话,驱动可能根本不读你填的属性名,这时候去驱动源码里搜“fir-config”关键字,看它实际读的属性名是什么。
迁移完成后,可以通过驱动暴露的调试节点检查系数有没有加载成功。拿libiio或者直接读/sys/kernel/debug/adi_axi-ad9361/下面的filter_fir_config节点,打印出来的数值和设备树里填的一致,才算真正生效。
4. 常见问题和排查实录
4.1 BPSK信号过FIR后星座发散
这是我在实际项目中遇到最多的问题,也是很多人卡住的第一关。现象是BPSK解调后星座点不是聚在+1和-1两个点附近,而是糊成一团,EVM高得离谱。
最常见原因是FIR通带设置太窄。假设符号速率1 Msps,你把FIR通带设计成±0.5 MHz,信号主瓣的能量被削掉一大块,码间串扰自然大。BPSK符号速率1 Msps时,主瓣零点就在±1 MHz,通带至少要放到±0.7 MHz以上,实际我经常放到±0.8 MHz,配合发射端成型滤波,接收端EVM会明显改善。
另一个容易被忽略的原因:FIR系数左右顺序写反了。数字滤波器的系数顺序决定了滤波器的相位响应,写反了会出现群延迟异常、波形变形。检查方法很直接,把I/Q两路分别抓下来,对比两者波形是否一致,不一致就查系数数组。
如果发射和接收都经过FIR,还要注意两级滤波的总带宽。发射FIR和接收FIR的级联等效带宽如果小于信号带宽,星座照样发散。所以我会先把接收FIR通带放宽,确认星座聚拢后再逐步收紧,找到一个不牺牲EVM的最窄带宽。
4.2 级联半带和FIR之后的增益与削波问题
整条数字链路是级联的:HB3、HB2、HB1每一级都有增益,FIR也有自己的增益配置。很多人在配置完FIR后发现信号顶到削波,或者信号太小动态范围不够,十有八九是增益预算没算清楚。
我建议调试时先把AGC关掉,固定手动增益,灌一个点频单音进去,然后抓IQ看FFT。观察主峰幅度,如果到0 dBFS附近就往下压一点增益,留3到6 dB余量,防止后面调制信号峰值过来时削波。FIR系数归一化到直流增益为1,理论上不引入额外增益,但半带滤波器各级之间可能存在bit growth,预留余量最稳妥。
注意:AD9361内部有自动增益控制可以补偿链路增益变化,但AGC生效需要时间。滤波链路本身如果发生削波,不是AGC能救回来的。调FIR之前先保证手动增益下不削波,再开AGC做系统联调。
4.3 通带纹波对EVM的影响
很多人设计FIR时只盯阻带衰减,对通带平坦度不上心,默认0.1 dB和1 dB没区别。实际上通带纹波直接变成幅度误差进入EVM预算。粗略估算一下,0.1 dB纹波带来的幅度误差大约1.1%,0.3 dB纹波大约3.4%,对QPSK可能还能忍,对256QAM这类高阶调制就是灾难。
我一般把Apass设在0.05到0.1 dB之间。这样设计出来的FIR阶数会比1 dB纹波多十几个抽头,但AD9361有128 taps预算,这点开销完全可以接受。代价是过渡带可能稍微变宽,需要靠阻带起始频率调整来平衡。调试的时候,可以抓一个单音扫频信号,看带内频谱的起伏是否和设计指标一致,不一致就先查系数类型和量化是否到位。
4.4 设备树迁移时最容易踩的坑
新Petalinux工程include旧设备树,最典型的问题有三类。
第一类是属性名变了。内核版本升级后,驱动从adi,fir-config改成adi,rx-fir-config和adi,tx-fir-config,旧文件复制过来驱动完全不认,链路初始化直接跳过FIR。别猜,直接在驱动源码里搜“fir-config”,看到底读哪个属性。
第二类是系数数组长度对不上。驱动按rx_fir_num_taps指定的长度读取数组,如果你的设备树数组比预期短,后面会自动补零,滤波器响应就完全变了。我遇到过有人把128 taps的系数只复制了前64个,结果通带直接扭曲,花了两天才排查出来。
第三类是补码格式问题。设备树里16位有符号数可以写成负数,比如-2写成0xFFFFFFFE,也可以直接写0xFFFE,取决于驱动解析方式。我之前从No-OS工程转换到设备树时,就因为在负数表示上没对齐,导致整个FIR频响不对。最稳的办法是用ADI官方工具导出设备树片段,不要手工改二进制补码。
4.5 顺手讲个容易混的概念:LCL滤波器
搜“滤波器设计”的时候,很容易搜到LCL滤波器参数设计的内容。这里我特意说一下:LCL滤波器是功率变换器输出侧的模拟滤波器,用来滤PWM开关纹波,设计时关注谐振频率、阻尼电阻、电感电流纹波,和AD9361里的数字FIR完全不是一个领域。做射频板的人别拿LCL的思路来调数字FIR,做电源的人也别把AD9361的FIR当成LCL来用。概念分清楚,项目才不会跑偏。
调试遇到问题的时候,多想想是模拟域的问题还是数字域的问题。AD9361把模拟和数字封装在一起,很容易让人混淆。我的经验是:先用频谱仪看模拟中频或基带输出,确定前端没问题;再抓数字IQ数据,在MATLAB里做FFT看频谱;最后才下结论是不是FIR的问题。定位清楚再动手,效率比盲目试系数高得多。
最后分享一个我自己调AD9361 FIR的习惯。拿到新板卡,我不会一上来就花一整天设计高性能滤波器,而是先把所有数字滤波全部旁路,用最粗的链路把数据跑通,抓频谱确认信号链是通的;然后一级一级使能HB3、HB2、HB1,最后才把FIR打开。这样哪一级把信号搞坏了,在频谱上一眼就能看出来。FIR要动,最好一次只改一个参数,改完看频谱和EVM再改下一个。这类板级调试的问题,九成都是配置组合问题,而不是系数本身算错了。希望这篇AD9361 FIR滤波器设计的经验能帮你少走点弯路。