第一次接触AD9361官方例程,大部分人其实是卡在“官方例程”这四个字上。GitHub上东西一堆,readme也写得很全,但它默认你已经同时懂射频前端、FPGA工程、Linux设备树和上层信号处理四件事,缺一块就启动不了。AD9361这款70MHz到6GHz的宽带收发器,官方例程给的不是一个能直接跑出数据的完整工程,而是一整套软硬件配合的参考体系,需要你自己串起来。这篇文章我想把官方例程的组成、跑通流程、以及两个搜索度极高的实操点——BPSK调制解调数据链路和设备树迁入新建PetaLinux工程——都摊开来讲一遍。
1. 官方例程不是“一个例程”,是一整套软硬件配合体系
1.1 最常见的误解
很多人以为官方例程就是某个zip包,下载解压,打开就能编译出带数据的demo。实际上ADI围绕AD9361给出了三个层面的参考工程:HDL参考设计、Linux内核驱动、无操作系统的No-OS例程。三者对应不同的使用场景,互相之间没有代码上的强依赖,但数据要跑起来时又必须协同工作。
- HDL参考设计解决的是“FPGA和AD9361的物理引脚怎么连、数据流怎么搬运”的问题,产出物是一个Vivado工程和bit文件;
- Linux驱动解决的是“寄存器配什么值、校准怎么触发、增益怎么控制”的问题,产出物是设备树节点和内核模块;
- No-OS例程解决的是“没有Linux时,如何在裸机上初始化并读出一段IQ数据”的问题,产出物是一套API和示例主函数。
如果你用的是带处理器的平台(比如Zynq),最常走的路线是“HDL参考设计 + Linux内核 + libiio工具”。如果你用的是纯FPGA平台(比如Kintex 7 + MicroBlaze),才会认真看No-OS。这一点不先想清楚,后面很容易在错误的例程里翻个底朝天也找不到想要的代码。
1.2 HDL参考设计里到底有什么
ADI将HDL仓库维护在analogdevicesinc/hdl,项目结构是projects目录下按板卡和FPGA型号组织的。常用的几个目录是projects/fmcomms2、fmcomms3、fmcomms4、fmcomms5,对应不同版本的AD-FMCOMMS系列评估板。每个目录下还要细分zc702、zc706、zcu102等FPGA开发板型号。
进入具体工程目录后,你会看到sdk、hdl和scripts三个子目录。hdl目录里的核心是一个设计顶层,内部例化了AD9361的AXI接口IP核、AXI DMA、以及一组config寄存器。它负责完成三件事:
- 把AD9361的P0/P1并行数字接口或LVDS接口接收到的IQ数据,打包成AXI Stream流入AXI DMA;
- 将Linux端需要发送的IQ数据从AXI DMA取出,按AD9361要求的时序送到数据接口;
- 提供一组用于控制收发方向、使能数据路径、读取状态的寄存器,Linux驱动通过AXI-Lite访管。
这个IP核不会帮你做任何BPSK/QPSK之类的调制解调运算,它只是赤裸裸的搬运工。AD9361的TX接口只输出模拟射频信号,RX接口只把天线信号变成基带IQ采样流,真正的DSP处理要么在FPGA内部自己写,要么在主机侧用GNU Radio这类工具做。
1.3 Linux驱动与libiio工具链
Linux驱动仓库是analogdevicesinc/linux,它是ADI维护的分支内核,里面包含了ad9361驱动、ad9528时钟驱动等补丁。官方Wiki强烈建议直接用这个仓库编译内核,而不是自己在vanilla内核上补驱动,因为ad9361驱动依赖很多IIO子系统的扩展接口。
驱动在运行时会把AD9361注册成一个或多个IIO设备,对应的用户空间访问库是libiio。libiio提供一组统一的API,支持通过网络、USB、本地文件系统三种方式访问IIO设备。常用的上层工具包括:
- iio_info:列出系统里所有IIO设备及其通道;
- iio_attr:直接读取或修改某个IIO属性;
- iio-oscilloscope:ADI官方做的图形化波形示波器;
- gr-ad9361:GNU Radio下的AD9361源/接收模块。
调试链路时,iio_info和iio_attr要比任何图形工具都先上手,因为它们能快速确认驱动是否加载、通道是否存在、某个配置项是否写入成功。很多人在图形工具里点了半天没波形,回头检查才发现驱动根本没认到芯片。
2. 从拿到官方例程到跑通一条收发链路
2.1 先把版本关系理清楚
AD9361例程最容易栽跟头的地方是版本匹配。HDL参考设计、Linux内核、Vivado、PetaLinux四者之间往往存在严格的版本对应关系。ADI在HDL仓库的README里会写明master分支对应的Vivado版本,Linux仓库也会在文档里标注支持的内核版本。
建议按照官方Release页面提供的版本组合来使用,先不要追求最新版。实测下来,一套比较稳的组合是:
- Vivado 2020.2对应较老但非常稳定的HDL参考设计版本;
- Linux内核使用ADI对应分支的2020_R2补丁;
- PetaLinux使用2020.2。
把Vivado、PetaLinux和内核版本混搭,通常会导致设备树与驱动接口不匹配,比如驱动尝试读取某个在旧版本里不存在的设备树属性,导致初始化中止。这类问题光看编译日志很难定位,因为错误信息往往只以“failed to probe”的形式出现在dmesg里。
2.2 按官方流程跑一个最小收发验证
拿到一块带AD9361的板卡后,完整验证链路可以分成四个步骤。
第一步,构建HDL工程。如果用的是FMCOMMS2和ZC706,在hdl仓库根目录下执行:
cd projects/fmcomms2/zc706 make等待编译完成后,会在runs/目录下生成bit文件、hdf文件和xsa文件。如果你用的是PetaLinux,建议直接把xsa交给petalinux-config使用。
第二步,搭建Linux镜像。用PetaLinux构建时,先通过petalinux-config指定xsa文件,再确保设备树中ad9361节点存在,然后编译生成BOOT.BIN和image.ub,写入SD卡启动。
第三步,确认IIO设备出现。系统启动后执行:
iio_info -s如果看到ad9361-phy和cf-ad9361-lpc两个设备,说明驱动已经成功加载。ad9361-phy负责寄存器控制和参数配置,cf-ad9361-lpc是数据流接口,两者同时出现才表示数据通道正常。
第四步,用iio-oscilloscope或命令行触发一帧数据。最简单的方法是在iio-oscilloscope里分别打开TX和RX,设置相同频率,然后把发射连到接收端进行有天线回环或线缆回环测试。如果RX波形出现杂乱噪声但能看到明显的中心频点突起,说明链路硬件基本通畅,接下来可以做细致调试。
2.3 验证指标:dmesg里该看什么
踩过几次坑后,我把dmesg日志中需要重点关注的字段整理成了一个清单:
- 是否出现“ad9361 spi0.0: AD9361 Rev.X successfully initialized”之类字样,表示PHY驱动初始化完成;
- 是否出现与校准有关的警告,常见问题包括TX quadrature calibration failed、RX quadrature calibration failed;
- 是否出现与CLK相关的报错,表明AD9528时钟芯片或内部PLL锁定失败;
- 是否出现“tx calibration failed: RF not connected”这类提示,这种情况通常是TX输出端没接负载或天线导致的。
日志里只要出现calibration failed,基本上硬件通路已经存在问题。先检查天线、衰减器、线缆是否接好,再审视电源是否稳定,最后怀疑FPGA和AD9361之间的LVDS/CMOS接口配置是否与板卡设计一致。不要一上来就怀疑驱动。
3. 用GNU Radio在官方例程基础上快速调通BPSK调制解调
3.1 为什么推荐先在主机侧实现,而不是一上来就在FPGA里写DSP
“AD9361实现BPSK调制解调出数据”这个问题经常出现在方案选型初期,很多人甚至打算直接在HDL参考设计里自己集成一个调制器。我的建议是分两步走:先在主机侧用GNU Radio调通整个链路算法,确认调制方式、滤波参数和同步策略都没问题,再考虑是否要把算法搬进FPGA。
原因是AD9361官方例程的数据通路是透明的,主机侧拿到数据后可以由软件定义信号处理,调参数只需要改GNU Radio流图,不需要重新综合FPGA。一个符号率试错的过程,在GNU Radio里是几分钟的事,在FPGA里是几个小时的布局布线问题。链路一旦确认,再固化到FPGA里会少走很多弯路。
3.2 搭建BPSK发射链路的关键参数
用GNU Radio实现BPSK调制并不复杂,但参数设置容易出问题。我在低版本和高版本GNU Radio中都用过,尽量选择自己熟悉的版本,模块名在各版本间略有差异。以常用的GRC为例,发射链路的核心模块如下:
- 随机源或向量源产生0/1比特流;
- 通过“Map”模块把0映射为-1、1映射为+1,从而完成BPSK最基本的符号映射;
- 使用“Root Raised Cosine Filter”进行脉冲成型,滚降系数建议0.35,过采样倍数设为采样率与符号率的比值;
- 将信号送入“AD9361 Sink”(该模块来自gr-iio或gr-ad9361),设置采样率和中心频点。
具体参数建议这样设:基带采样率10 MHz,符号率1 Msps,过采样倍数10,射频中心频率2.4 GHz,发射增益先用-10 dBm量级起步。这段配置的优势在于过采倍数足够,接收端做定时恢复时有充分的采样点可用,调试信号质量也更直观。
需要特别注意的是,AD9361的驱动层已经做了数字上变频和模拟混频,GNU Radio流图里不需要再做频率搬移,只需要给基带I/Q数据。很多人误以为要在GNU Radio里把信号搬到中心频率,结果出现双边的镜像频谱,本质上是理解错了AD9361的基带接口。
3.3 接收端解调链路怎么搭
接收端的标准BPSK解调链路要比发射复杂一些,按照模块顺序依次是:
- AD9361 Source,设置与发射端相同的采样率和中心频率;
- 自动增益控制(AGC)模块,尽量让信号幅度稳定在满量程附近;
- 根升余弦匹配滤波,系数与发射端一致;
- 定时恢复模块,最常用的是Mueller & Muller恢复环路,这一步解决符号起点偏移问题;
- Costas环载波恢复,用于消除残余频偏和相位偏移;
- 符号判决和星座图显示。
实际操作中,最容易导致解调失败的是残余频偏。AD9361的TX和RX虽然使用同一块板上的时钟源,但主机侧两次独立配置的载波频率之间仍可能存在少量频偏。Costas环能够纠正一定范围内的频偏,但超出范围就锁不住。常用的解决方法是先做一次粗略的频率校准,再用接收信号频谱的峰值位置估计频偏,把它手动补偿进AD9361的RX中心频率。
3.4 让调制解调结果真正“出数据”
当链路参数设置正确后,星座图上会出现两个清晰的点,分布在-1和+1位置附近。此时再接一个符号判定模块,就能把解调符号映射回比特流,进而与发送端比特流对比计算误码率。
在无回环测试时,我还发现几个容易让数据错乱的小细节:
- 收发两端过采样倍数不一致,导致符号宽度不同,解调出来的数据全是乱码;
- 接收端没有做时间同步,直接在符号边界上采样,也会出现大量错误;
- 如果使用同一块AD9361板卡做射频回环,发射和接收之间必须加足够衰减,防止RX进入饱和失真。
完整BPSK链路跑通后,再考虑往FPGA移植就心中有数了。你会在软件里已经确认好的关键参数,就是FPGA实现时的硬性指标。
4. 把AD9361原有设备树搬进新建PetaLinux工程
4.1 设备树在官方例程里究竟起了什么作用
设备树负责告诉Linux内核三件事:AD9361挂在哪条SPI总线上、Reset和使能引脚连到哪个GPIO、以及它使用哪种接口模式和数据端口配置。官方Wiki上FMCOMMS2的设备树案例,核心节点如下:
&spi0 { ad9361@0 { compatible = "adi,ad9361"; reg = <0>; spi-max-frequency = <16000000>; reset-gpios = <&gpio0 79 GPIO_ACTIVE_HIGH>; adi,rx-rf-port-input-select = <0>; adi,tx-rf-port-input-select = <0>; }; };这些节点中任何一个与实际硬件设计不匹配,就会导致驱动探针失败。设备树不是从老工程里拷出来就能用的,必须根据新工程的FPGA引脚分配、GPIO编号和SPI控制器配置进行核对。
4.2 移植到新建PetaLinux工程的完整操作顺序
新建PetaLinux工程时最稳妥的做法不是手动从零写设备树,而是先把老的设备树作为参考,再用新工程生成的设备树做适配。我的操作顺序如下。
第一步,创建一个标准PetaLinux工程:
petalinux-create -t project --template zynq --name ad9361_peta cd ad9361_peta第二步,灌入新版HDL生成的xsa文件:
petalinux-config --get-hw-description=path/to/xsa这一步会自动生成与当前FPGA硬件匹配的pl.dtsi、ps7_init_gpl.c等文件。
第三步,打开旧工程中设备树文件,将AD9361相关的节点复制到新工程的自定义设备树中。PetaLinux推荐将自定义节点放在meta-user/recipes-bsp/device-tree/files/system-user.dtsi里。这样不会破坏自动生成的基础设备树结构。
第四步,重点核对以下几个节点是否与旧工程一致:
- SPI节点的基地址和外设时钟;
- reset-gpios和ensm-gpios等GPIO编号;
- 全部或半双工接口模式(adi,full-port等属性);
- 使用的RX/TX FIR滤波器配置是否被引用。
第五步,编译并生成启动镜像:
petalinux-build petalinux-package --boot --fsbl zynq_fsbl.elf --fpga --u-boot这里有句忠告:不要直接拿旧工程编译出的BOOT.BIN塞到新板子上,哪怕芯片型号相同。不同版本的U-Boot、FSBL和设备树之间可能有细微差异,最典型的例子就是GPIO pinmux配置在不同版本里可能发生变化,导致AD9361的复位引脚失效。
4.3 报错排查:按“驱动-节点-时钟-中断”四层进行
移植后如果系统起不来,或者起来了但找不到ad9361-phy,排查思路建议按下面四层顺序来,而不是一上来就猜测设备树语法错误。
第一层看驱动是否编译进内核。执行:
zcat /proc/config.gz | grep AD9361如果CONFIG_AD9361没有配置,设备树再正确也没用。PetaLinux里可以通过petalinux-config → Device Drivers → IIO → ADI IIO drivers 进行配置。
第二层看设备树节点是否被解析。在Linux启动阶段日志中搜索“spi0.0”或“ad9361”关键字,如果完全没有出现,说明设备树里SPI节点地址或compatible匹配失败,或者是SPI控制器本身没有被启用。
第三层看时钟是否初始化成功。AD9361通常需要外部参考时钟,部分板卡还依赖AD9528芯片提供多路时钟。启动日志里如果涉及ad9528或者adf4351相关报错,优先检查SPI地址是否冲突、晶振是否起振。
第四层看中断是否正常。AD9361数据路径在多数设计中不依赖中断,但ENSEM状态机可能通过GPIO中断上报,如果中断号与设备树不一致,驱动会进入一个奇怪的半初始化状态。用cat /proc/interrupts观察是否有ad9361相关中断号被触发。
4.4 移植后校准值变化过大的处理
如果设备树移植成功但校准性能异常,比如TX输出功率比老工程低很多,或者RX灵敏度差,先别急着改驱动。检查新旧硬件是否真的完全一致。很多第三方“兼容板”虽然号称AD9361核心相同,但匹配网络、时钟树和电源方案都有差异。
官方校准算法会在初始化时自动运行,但校准结果对上变频器的负载条件很敏感。如果你的板卡在TX输出端多了一级放大器或少了一级滤波器,都会让校准得到不同的数值。这不是驱动写错了,是硬件边界条件变了。
这种场景下,我建议先用频谱仪直接看TX端口的载波功率和杂散,确认硬件通路正常后再去审视设备树。设备树只负责告诉驱动“硬件长什么样”,并不负责补偿射频通路差异。
5. 官方Wiki不会写清楚的几个实战细节
5.1 电源纹波和时钟抖动最容易让校准失败
在多个板卡上排查“AD9361偶尔初始化失败”的问题时,最终定位到的原因基本都与电源或参考时钟的稳定性有关。AD9361内部有多个LDO和PLL,对电源纹波比较敏感,尤其是数字内核供电。如果板卡上给AVDD或DVDD的LDO输出纹波过大,系统上电后第一次初始化大概率失败,但复位重试几次又能成功。
判断方法很简单:用示波器或频谱分析仪观察供电轨上的高频纹波,正常情况应控制在几十毫伏以内。参考时钟则建议用相噪指标较好的晶振或有源温补晶振,避免使用廉价无源晶振直接驱动。
5.2 TX输出功率不是想调多高就调多高
官方驱动和libiio属性中显示了“TX gain”的值,但很多人忽略了它是数字增益与射频增益的整体效果。AD9361内置的TX输出功率存在上限,典型值在几dBm量级,超过这个范围内部PA会出现明显压缩。如果你需要更大功率,必须外接功率放大器。
在日常调试中,我习惯先把TX增益设得很低(比如-20dBm),确认信号路径无误后再逐步提高。这样能避免一开始功率过大导致接收端饱和,也会减少对频谱仪或功放前端的损坏风险。
5.3 学会用IIO属性而不是反复改设备树来排查问题
很多参数调整其实不需要修改设备树,直接在运行时写IIO属性就能生效。常用的属性包括:
- in_voltage0_frequency,对应RX中心频率;
- out_voltage0_frequency,对应TX中心频率;
- out_voltage0_hardwaregain,对应TX发射增益;
- in_voltage0_gain_control_mode,控制RX增益模式为slow_attack还是manual。
排查问题时,先用iio_attr改这些属性确认能否生效,再决定是否动设备树。设备树修改一次需要重新编译镜像和重启系统,效率很低,而IIO属性是实时生效的,调试效率能翻好几倍。
以设置RX频率为例:
iio_attr -c ad9361-phy voltage0 frequency 2400000000设置后立刻用iio_oscilloscope观察频谱变化,能快速验证频率配置是否生效。
5.4 回环测试时,别一上来就满功率
回环测试是验证整个链路最快的方式。无论是使用功分器还是射频线缆把TX输出直接连到RX输入,都要先加衰减器。AD9361的RX最大输入电平典型值只有几dBm,而TX输出即使设到0dBm也可能超过RX输入耐受范围。
正确的顺序是:先用30dB衰减器回环,确认RX能看到清晰信号,然后逐步减小衰减,观察星座图和EVM变化。这种做法能让你同时验证接收链路的线性度和发射链路的信号质量,而不会因为某一端饱和而误判整个链路有问题。
最后一点体会
官方例程下载下来之后,先不要急着看代码细节,更不要马上往自己的板卡上搬。花半天时间把HDL仓库、内核补丁、设备树示例三者的版本对应关系捋清楚,后续遇到的绝大多数问题都会迎刃而解。我自己遇到过太多因为版本混搭导致“玄学故障”的案例,最终排查无非是回到版本匹配上。还有一个习惯也值得分享:无论做定制板还是直接拿官方评估板,我都会先跑通一个最小回环,再把调制解调或无线协议叠加上去。射频链路没验证之前,所有的软件调试都是空中楼阁。