资讯动态

AD9361+Vivado例程调通指南:从信号链到避坑实战

发布时间:2026/10/21 4:13:22 来源:尧图企业网站定制
简介AD9361 Vivado例程是一套面向FPGA与射频通信开发者的工程模板基于Xilinx Vivado 2016.4环境演示从IP核集成、接口设计到配置控制、时序约束及仿真验证的完整流程。资源包共2000个文件约95MB主要包含Verilog源码、Tcl脚本、C/H驱动、Makefile、XDC约束、BIT比特流和XCI工程配置另有大量文档说明目录结构清晰便于查阅。目前已有4861人学习适合需要快速上手AD9361的工程师。通过研读工程文件和脚本可掌握AD9361 IP核配置、AXI总线连接及常见排错方法为自主设计无线通信系统提供可复用模板。1. AD9361 Vivado 例程为什么拿到源码还是调不通做软件无线电的朋友大多有过这种经历从某个地方搞到一套基于 AD9361 的 Vivado 例程兴冲冲打开工程跑完综合实现加载到板卡上却发现 IIO 设备节点不出现、DMA 读不到数据、频谱上是白噪。问题不在你而在于这类射频收发例程是一个“串行依赖链”FPGA 逻辑、管脚约束、嵌入式驱动、设备树、射频前端配置任何一环没对齐整条链路都是哑的。这篇文章要拆的资源就是一套能直接复现的 AD9361 Vivado 参考例程包含 HDL 工程生成脚本、顶层模块、约束文件和 Linux 侧的设备树与驱动适配脚本。它解决的核心问题只有一个让你越过四处拼凑的坑从零把射频收发链路点亮拿到连续正确的 IQ 数据。适合正在做 Zynq 或纯 FPGA 驱动 AD9361 的开发者也适合刚接触软件无线电、想用现成例程验证自己算法的从业者。注意这不是让你“打开工程就跑”而是要你理解例程里那些默认参数为什么这么定以及改动之后该怎么调。2. 从数据手册到上板AD9361 例程里的信号链与关键 IP2.1 AD9361 射频前端与 FPGA 接口为什么例程必须用 LVDSAD9361 是一颗集成度很高的射频收发器覆盖 70 MHz 到 6 GHz接收通道带宽从 200 kHz 到 56 MHz。它内部有混合信号前端、ADC/DAC、抽取/插值滤波器和数字增益控制但对外与 FPGA 的接口并不复杂一组 SPI 用于寄存器配置一组数据总线用于采样数据还有若干控制 GPIO。数据总线有两种模式CMOS 和 LVDS。大多数 Vivado 例程默认使用 LVDS 双通道模式原因很简单在采样率超过 40 MSPS 时CMOS 接口的 12 位双通道数据会占用大量管脚而且时序容易恶化。LVDS 模式下I 路和 Q 路各自差分成两对线用 DDR 时钟在上升沿和下降沿分别传两个 bit这样单通道 12 位数据被折叠成 6 对差分线管脚压力小得多。我在写顶层连线前会先确认 AD9361 的 datasheet 里数据接口时序FPGA 侧需要提供一个数据时钟通常由 AD9361 输出也可以由 FPGA 生成例程中会有一个ad9361_clkgen模块负责产生采样时钟、数据时钟和字节时钟。这个模块的输出频率计算很关键如果你修改了采样率配置而没同步调整时钟分频LVDS 数据采样就永远跟不上星座点会绕成圆圈。例程中通常有一个顶层 IP 叫axi_ad9361它把所有 FPGA 与 AD9361 之间的数据、时钟和状态线集成在一起。外部看是 AXI 接口内部有接收数据通道、发送数据通道、SPI 控制器和状态寄存器组。下图是典型数据流的文字描述不是图AD9361 的 RX 数据经 LVDS 进来axi_ad9361按协议解析出 I/Q 样本并打包成 AXI Stream然后交给axi_dmac搬运到 DDR。发送路径相反CPU 把 I/Q 样本写入 DDRaxi_dmac把数据流送入axi_ad9361的 TX 通道再按 LVDS 时序送到 AD9361 的 DAC。2.2 Vivado 例程的工程结构src、ip、约束、脚本拿到例程第一步不是双击 xpr而是先看目录结构。一套规范的 AD9361 Vivado 例程通常长这样ad9361_vivado/ ├── projects/ │ ├── common/ │ │ ├── ad9361_bd.tcl │ │ └── ad9361_constr.xdc │ └── zc706/ │ ├── system_top.v │ ├── system_bd.tcl │ └── makefile ├── library/ │ ├── axi_ad9361/ │ ├── axi_dmac/ │ └── util_pack/ ├── scripts/ │ └── make_tcl.tcl └── README.md这个结构中projects/common里的 TCL 脚本是模板ad9361_bd.tcl定义了与板卡无关的 IP 连接ad9361_constr.xdc定义的是通用管脚约束。真正的板级细节放在projects/zc706下面比如时钟源、DDR型号、FMC连接器位置。如果你用的是自己的板卡需要改的主要是system_top.v和对应的约束文件而不是去动 common 里的 TCL 脚本。很多人在这一步犯的错是直接改 common 中的约束结果导致生成工程时管脚冲突。这里有个值得注意的习惯例程里system_top.v是顶层它会实例化一个 Block Design同时把外部差分时钟、复位、UART、网络接口等引出。调试时如果需要加 ILA最好不要改这个顶层而是另建一个 wrapper 文件把顶层实例化后再在内部对axi_ad9361的关键总线挂 ILA。这样能避免每次重新生成工程时手工修改被覆盖。2.3 顶层模块与数据通路接收链的寄存器配置以接收链路为例FPGA 内部的数据流是这样的AD9361 的 RX 差分数据 -axi_ad9361的 LVDS 接口 - 内部 FIFO - AXI Stream 输出 -axi_dmac- AXI4 写入 DDR。axi_ad9361内部有一些寄存器用来配置数据格式二进制补码还是偏移二进制、通道交换、数据类型。这些寄存器可以通过 AXI 接口写入也可以在 Block Design 里设置默认值。常见例程的顶层实例化片段大致如下不同版本 IP 名字可能有差异但思路不变axi_ad9361 #( .ID(0), .DELAY_DATA(1sb0), .DELAY_CLK(0), .DELAY_RX_DATA(1sd0) ) i_rx_ad9361 ( .adc_clk_in_p(adc_clk_p), .adc_clk_in_n(adc_clk_n), .adc_data_in_p(rx_data_p), .adc_data_in_n(rx_data_n), .adc_valid(rx_valid), .dac_clk_out_p(dac_clk_p), .dac_clk_out_n(dac_clk_n), .dac_data_out_p(tx_data_p), .dac_data_out_n(tx_data_n), .dac_valid(tx_valid), .s_axi_aclk(s_axi_clk), .s_axi_aresetn(s_axi_rstn), .adc_enable_iq0(adc_enable_iq0), .adc_enable_iq1(adc_enable_iq1), .dac_enable_iq0(dac_enable_iq0), .dac_enable_iq1(dac_enable_iq1), .s_axi_awaddr(s_axi_awaddr), .s_axi_awvalid(s_axi_awvalid), .s_axi_awready(s_axi_awready), // ... AXI write and read ports );这段代码里有几个信号值得注意adc_valid是接收数据有效标志adc_enable_iq0和adc_enable_iq1分别使能两路接收通道。如果你只需要单通道可以把另一个使能拉低但数据总线仍然按双通道宽度传输控制器内部会屏蔽无效通道。DELAY_RX_DATA参数用于调节接收数据的采样相位这是调试 LVDS 接口时最重要的参数后面避坑章节会专门展开。3. 让例程跑起来的完整流程工程生成、约束修改与比特流构建3.1 通过脚本重建工程make 与 TCL 流程拿到 Vivado 例程我一般不会直接用 GUI 打开 .xpr 工程而是先执行它的工程生成脚本。原因有两个一是 .xpr 里记录的 IP 路径可能指向原作者机器上的绝对路径换一台电脑大概率会失效二是脚本能统一管理 Vivado 版本、IP 缓存和输出目录。大多数 AD9361 例程的生成逻辑是通过 make 调用 Vivado 的批处理模式执行一个 TCL 脚本比如cd projects/zc706 make vivado-2019.1这条命令会在projects/zc706/下生成zc706_vivado_2019.1目录里面才是可用的工程文件。如果没有 make 环境也可以手动打开 Vivado 并执行source ../common/ad9361_bd.tcl source ./system_bd.tcl source ./system_constr.xdc但手动执行时要注意脚本之间的依赖关系system_bd.tcl里会调用ad9361_bd.tcl所以必须先设置好搜索路径。TCL 脚本中通常会包含set_part、create_bd_design、add_board_connections等命令。如果你要改板卡比如把目标器件从 xc7z045 换成 xc7z035需要修改所有出现set_part的地方但更推荐的做法是直接在 makefile 里传入PART变量make vivado-2019.1 PARTxc7z035ffg676-2这个参数会覆盖脚本中默认的器件型号避免你改错 TCL。注意不同封装的器件管脚坐标完全不同换了器件之后 XDC 文件里的引脚位置必须同步更新否则综合后布局布线必然会报错。3.2 管脚约束与时钟最容易翻车的第一道坎AD9361 例程中XDC 约束文件通常分成两部分一部分是通用约束比如时钟周期、差分对规则另一部分是板级管脚分配。官方例程的约束文件默认对应某一块特定的 FMC 板卡如果你用的是其他厂商的板卡最稳妥的办法是只保留端口名称然后把端口绑定到你自己板卡上实际连接 AD9361 的那个 FMC 连接器引脚。下面是一段典型的差分数据约束set_property -dict { PACKAGE_PIN AE14 IOSTANDARD LVDS } [get_ports {adc_data_p[0]}] set_property -dict { PACKAGE_PIN AF14 IOSTANDARD LVDS } [get_ports {adc_data_n[0]}] set_property -dict { PACKAGE_PIN AC16 IOSTANDARD LVDS } [get_ports {adc_clk_p}] set_property -dict { PACKAGE_PIN AD16 IOSTANDARD LVDS } [get_ports {adc_clk_n}]这里有一个容易忽略的细节AD9361 的 LVDS 接口使用的电平标准是LVDS_25还是LVDS取决于硬件设计。大部分 FMC 板卡供电为 2.5V所以用LVDS有时 Vivado 会根据电压自动选择。如果你的板卡上 AD9361 的接口电平是 1.8V那就必须改成LVDS_18否则上板后数据线没有电压摆幅死活采不到正确数据。这个现象极其坑人因为综合实现不会报错仿真也正常只有实测时数据全为 0。时钟约束方面例程里通常有一个create_clock约束用于接收 AD9361 输出的数据时钟create_clock -name adc_clk -period 8.000 [get_ports {adc_clk_p}]这个 period 值对应 125 MHz如果例程默认采样率是 61.44 MSPSDDR 数据时钟是 122.88 MHz实际周期约 8.138 ns。如果你在 Block Design 里改了采样率别忘了同步修改这条时钟约束。否则时序报告会异常或者下到板卡后采样时间不对。3.3 生成比特流并验证从硬件管理器到 ILA 抓信号工程生成后先做一次 “Generate Bitstream”等待综合、实现、生成 bit。这个过程在只改动约束的情况下大约需要 30 分钟到 1 小时主要取决于电脑配置。生成结束后打开 Hardware Manager加载 bit 文件到 FPGA。如果只是纯数字验证可以先不连 AD9361 的射频前端直接跑 FPGA 内部回环。在 FPGA 内部axi_ad9361通常支持数字回环模式把发送数据直接环回到接收数据通路这样不需要外部射频信号就能验证 AXI DMA 通路。开启这个模式需要往axi_ad9361的寄存器写值。常见做法是通过 AXI GPIO 或 JTAG 的 ILA 触发器来修改。更方便的是通过 PSZynq 处理器端写寄存器但如果你没有 PS或者例程配置的是微盲区standalone模式那就要用 Vivado 的 VIO 核来手动置位。使用 ILA 时建议把axi_ad9361的接收数据接口挂上观察adc_valid是否周期性拉高以及数据是否在变化。许多人第一次抓 ILA 遇到的问题是adc_valid一直为低这通常不是信号链问题而是 ILA 的时钟没选对。axi_ad9361的数据时钟和 AXI 总线时钟是异步的如果 ILA 挂在 AXI 时钟域而数据是数据时钟域必须用async方式跨时钟否则 ILA 采样到的adc_valid会一直是一个错误电平。我一般在调试时挂两套 ILA一套挂 AXI 总线时钟域的 DMA 接口另一套挂数据时钟域的原始数据接口分开观察。4. 调试射频链路从丢包到频谱异常的排查清单4.1 回环测试校准基带不管最终要用射频收发还是只做基带验证我都建议先跑一遍数字回环。AD9361 的参考例程中会在顶层预留一个dcxo或gpio控制位用来切换 AD9361 的工作模式。在没有 AD9361 驱动初始化之前FPGA 的数字回环可以直接在axi_ad9361内部完成TX 数据在输出前被复制到 RX 数据入口。这个操作可以通过修改寄存器实现也可以通过一个简单的 AXI GPIO 控制。在 Zynq 方案中如果使用 PetaLinux 和 IIO 框架可以通过iio_regmap或直接操作寄存器地址来开启回环。更简单的方法是先在裸机环境跑一个最小测试例程只配置 AXI 总线不配置 AD9361 芯片然后尝试写一个已知序列到 TX DMA再从 RX DMA 读回。如果读回的数据与写入完全一致说明 FPGA 内部通路是通的。如果读回的数据不一致重点排查 AXI DMA 的缓冲长度是否正确。很多例程在顶层配置了 8KB 的 DMA buffer但你实际搬运的样本数超过这个长度数据就会被截断或覆盖。具体操作步骤是先用一个数组填充发送缓冲区比如填充递增数 0x000 到 0xFFF然后启动 TX DMA再启动 RX DMA完成后比较接收缓冲区。代码里可以用一个简单函数int fill_and_check(uint32_t *buf, int len) { for (int i 0; i len; i) { tx_buf[i] (i 0x0FFF) | 0x1000; // 加入固定标记位 } dma_start_tx(0, tx_buf, len); dma_start_rx(0, rx_buf, len, 1); for (int i 0; i len; i) { if (rx_buf[i] ! ((i 0x0FFF) | 0x1000)) { return i; } } return -1; }这个函数里的dma_start_tx和dma_start_rx是封装的寄存器操作。关键在于 RX 需要比 TX 提前启动或者至少要在 TX 写完之前启动否则会丢开头几个样本。例程中axi_dmac支持一种“循环模式”它会让 DMA 不断填充新数据比较适合这种回环验证。4.2 射频回环与增益控制数字回环通过后再打开 AD9361 的射频回环。此时需要先完成 SPI 初始化然后配置收发频率。通常 AD9361 的例程会提供一个配置文件或者初始化脚本用一组寄存器表写入芯片。常见的操作是在 Linux 里用 IIO 设备来配置echo 2430000000 /sys/bus/iio/devices/iio:device0/out_altvoltage0_frequency echo 20 /sys/bus/iio/devices/iio:device0/out_voltage0_hardwaregain echo 1 /sys/bus/iio/devices/iio:device0/out_altvoltage0_att_ctl这些参数分别是设置 TX 本地振荡器频率为 2.43 GHz发送通道增益为 20 dB以及使能衰减控制。RF 端需要一个衰减器或者天线负载否则回环可能引发自激。如果你没有频谱仪也可以把 TX 输出直接通过一段同轴电缆连到 RX 输入注意加一个 30 dB 固定衰减器。很多人在这里翻车直接连一根线结果 AD9361 的 LNA 饱和接收频谱变成一片平顶。正确做法是先设置 RX 自动增益控制为慢速模式并观察 RSSI 是否在合理范围。用 IIO 读 RSSIcat /sys/bus/iio/devices/iio:device0/in_voltage0_rssi如果 RSSI 接近满量程说明输入功率过高应该增加衰减器或者降低 TX 增益。这一条调试经验比你在误码率上纠结半天更管用。4.3 常见问题排查位宽不匹配、数据顺序错、时钟域问题在排查过程中有几个高频问题值得单独拎出来说。第一个是位宽不匹配。AD9361 的 ADC 和 DAC 是 12 位但通常以 16 位样本存储到 DDR 中低 4 位要么是符号扩展要么是零填充。如果你发现数据幅度只有预期的一半或者信号有直流偏置检查一下是否把 12 位数据当成了 16 位来解释。很多例程会提供ad9361_adc_interface模块它负责把 12 位复用信号扩展成 16 位 I/Q。第二个是数据顺序错。在 LVDS 双通道模式下数据线上传输的顺序是 IQ 交替还是 I 两路后再 Q 两路取决于配置。如果你的星座图出现镜像或者 Q 路变成 I 路多半是通道交换配置没对齐。axi_ad9361中有寄存器可以设置 IQ 顺序也可以在驱动初始化脚本里修改IQCORRECTION相关参数。有一个肉眼可判断的方法发送一个单音信号观察 I 路和 Q 路的频谱能量如果能量在正负频段对称说明通道没问题如果只有一边有能量则可能 I/Q 交换了或者正交度出了问题。第三个是时钟域问题。当 FPGA 逻辑自定义部分比如用户自己的 FFT与axi_ad9361接口交接时很容易产生跨时钟域数据丢失。例程中通常提供了axis_clock_converterIP你应该保证用户逻辑的 AXI Stream 时钟与 DMA 的时钟一致或者在快慢时钟域变换时使用足够的 FIFO 深度。不要图省事直接用寄存器打两拍来跨时钟因为 AXI Stream 的 valid-ready 信号需要严格握手简单打拍会导致数据丢失。5. 避坑指南AD9361 Vivado 例程的五个典型翻车现场5.1 IP 核版本不匹配导致综合报错现象打开例程工程后点击 Generate Bitstream在综合阶段报出一堆端口不匹配错误比如Port adc_valid_i0 does not exist。原因例程是用某个特定 Vivado 版本创建的你用的版本更高IP 核被自动升级但底层端口变化了。解决先不要升级 IP右键 Block Design 的 IP 核选择 “IP Settings - Compatible Families”或者在生成工程前确保环境变量里指定的 Vivado 版本和例程 README 中的版本一致。如果非要使用新版本那就需要手动检查所有实例化端口特别是自定义的axi_ad9361的端口列表。更稳妥的做法是使用脚本重新生成整个工程而不是打开 .xpr 后让 Vivado 自动升级因为脚本会直接适配当前版本创建 IP。5.2 现象DDC/DUC 配置后输出频率偏移现象配置好 LO 频率后接收信号的中心频率与预期偏移了几十 kHz 到几百 kHz。原因AD9361 内部的抽取滤波器系数或者采样率设置与 FPGA 侧的数据时钟不同步。使用例程时如果只修改了 RF 频率而没有修改 PLL 的参考分频、采样率和 FPGA 时钟生成部分就会出现频率偏移。解决例程中一般有一个ad9361_clkgen模块它会根据配置寄存器自动生成时钟但前提是驱动侧的时钟配置表与 HDL 侧完全一致。建议检查 Linux 下iio_info -s输出的采样率是否和 FPGA 实例化参数一致如果不一致用echo new_rate /sys/bus/iio/devices/iio:device0/in_voltage_sampling_frequency重新配置。如果仍然偏移就要重新生成 AD9361 的配置表而不是靠软件去修正一个小数倍频。5.3 现象LVDS 数据采样相位不对星座图散乱现象射频回环正常但接收到的单音信号在 IQ 图上是一个圆环而不是一个点。原因LVDS 接口的采样窗口没有对准。AD9361 输出的数据在时钟沿前后一段时间有效FPGA 内部需要在正确的边沿进行抽样。如果时钟延迟或数据延迟设置不对抽样点会落在数据翻转区域。解决调整axi_ad9361的DELAY_RX_DATA参数这个参数本质上是 FPGA 的 IODELAY 配置。你可以从 0 开始逐渐增大每次增加 1 延迟步进重新采集数据并观察 EVM。找到 EVM 最低点后固定参数。有些例程会在初始化脚本里设置这个值建议放在一个便于修改的位置例如ad9361_bd.tcl顶部的局部参数。5.4 现象DMA 无法工作读取不到数据现象寄存器配置成功ILA 看到 AXI Stream 上有数据但读取 DDR 里的缓冲全是 0。原因通常不是axi_dmac本身问题而是 MMU 或缓存一致性问题。在 Zynq 上运行 Linux 时DMA 写入的内存如果不能保证缓存一致CPU 读到的是陈旧缓存。解决使用 IIO 内核驱动时驱动会使用dma_alloc_coherent分配一致内存理论不存在这个问题。但如果是在裸机上自己写 DMA就要确保缓冲区地址是物理连续地址并且设置 MMU 的 Cache 属性为 Non-Cacheable。另外检查axi_dmac的DMA_SRC_ADDR寄存器的地址如果例程默认是 0x40000000而你实际 DDR 是 0x00000000就要修改设备树或者寄存器值。5.5 现象设备树和驱动加载失败现象加载 bit 文件后Linux 启动时dmesg提示Failed to probe ad9361或者 IIO 设备节点没有生成。原因设备树里 spi 节点、使能引脚、中断号与实际硬件不匹配。解决对比你的板卡原理图确认 AD9361 的 SPI 片选接到了 PS 的哪个 SPI 控制器中断线接的是哪个 GPIO。常见例程中设备树会写为spi0 { ad93610 { compatible adi,ad9361; reg 0; spi-max-frequency 10000000; adi,frequency-integer 1; }; };这个spi-max-frequency很容易出问题。AD9361 的 SPI 最高频率根据数据手册不同有些版本只支持到 50 MHz写太快会导致寄存器配置出错。如果你在初始化过程中发现校准失败尝试把 SPI 频率降到 10 MHz 以下这是不少开发者折腾几天才找到的原因。6. 把官方例程改成适合自己的设计裁剪与验证的三个技巧官方例程的价值在于它给了你一个已知正确的地基你可以在这个地基上修自己的楼。三个技巧我认为最实用。第一个技巧是保留 Block Design 中的验证环境把你的自定义 DSP 模块挂到 AXI Stream 总线上。不要直接改axi_ad9361的内部而是新建一个 AXI 外围设备把axi_ad9361的 M_AXI 接口接收数据连到你的模块你的模块处理后再连到axi_dmac。这样如果数据不对可以先把自定义模块置为直通验证官方通路还是好的再逐步打开自己的处理逻辑。这个“直通开关”一定要做成 AXI 寄存器方便运行时切换。我通常会在自定义模块里加一个 32 位控制寄存器bit0 为 1 时输出等于输入bit0 为 0 时输出是自己的处理结果。这个小小的设计能省下大量来回烧 bit 的时间。第二个技巧是学会修改采样率和带宽。很多人在例程上想把自己的信号采样率从 10 MSPS 改成 2 MSPS但只改了驱动侧参数没改 FPGA 侧时钟。正确路径是先确认 AD9361 的 BB 带宽和 ADC 抽取率再计算出数据时钟频率然后回到 Vivado 工程修改ad9361_clkgen的目标频率最后同步调整 XDC 里的时钟约束。如果路径复杂最省事的办法是直接用官方提供的配置工具生成新的配置表然后用脚本替换旧表。不要相信驱动会自动重配 FPGA 时钟至少在我见过的例程中FPGA 的实际时钟是固化在 bit 里的驱动侧只能配置芯片内部的分频无法改变 FPGA 和 AD9361 之间的数据传输频率。所以当你发现驱动设置采样率后数据速率对不上优先回源头检查时钟。第三个技巧是用 Python 脚本实时解析接收数据快速验证射频通路。不要每次都用 ILA 去抓波形效率太低。在 Linux 上通过 IIO 读取数据后用 Python 处理 IQ 样本绘制频谱和星座图。我在调试时有一个固定流程启动后先用一个 L脚本设置中心频率 2.4 GHz采样率 10 MSPS然后让 DMA 连续采集 1 秒数据保存到文件再用 Python 读文件做 FFT。如果频谱上只有一个主峰说明例程配置成功如果出现谐波或杂散则检查功率是否过高或者滤波器是否设置错误。这个习惯帮我避开了“盯着 ILA 时序看半天却忘记看整体频谱响应”的老路。这套 AD9361 Vivado 例程里真正值钱的不是那些 IP而是它把射频与 FPGA 之间的所有细节都填好了。从那以后我每次拿到新板卡都强制走一遍数字回环 → 射频回环 → IIO 数据采集的流程先确认官方通路再做自己的改动。希望你也能少走这些弯路一次点亮。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑