简介:面向FPGA开发者的AD9361射频收发器Vivado例程,依托赛灵思Vivado 2016.4环境,典型应用覆盖软件定义无线电、无线基础设施、测试测量设备等。例程按完整工程流程组织,从IP核集成、AXI总线接口设计,到工作频率与增益参数配置、时序约束、仿真验证、比特流生成及硬件测试,均有对应源码与注释,可帮助工程师快速掌握这款射频收发器的寄存器配置与数据通路设计,大幅减少在Zynq嵌入式平台上搭建底层驱动的时间。资源共2000个文件,主体为Verilog/VHDL硬件描述源码,辅以XCI工程配置、XDC引脚时序约束、Tcl自动化脚本及Makefile构建脚本,并附带已生成的Bit比特流、ELF可执行程序、JPG/SVG原理图、PDF手册等说明材料,整体约95MB,目录结构清晰,便于按模块检索与工程移植。目前已有4852人学习使用,适合需要直接导入工程二次开发或系统研究AD9361配置逻辑的中高级硬件工程师。
1. 先说一个反直觉的结论:例程跑通不等于射频链路通了
你以为跑通了 Vivado 例程,AD9361 的收发链路就算通了?很多时候恰恰相反——比特流能生成、IIO 工具能建链、数据能推到 DMA,这三件事里任何一件都验证不了“AD9361 真的在收发”。这套 ad9361_vivado 例程真正的价值,不在于让你“点亮”一块板子,而在于它把寄存器配置、数字接口时序、DMA 数据通路完整地组织在一个工程里,让你可以对照着逐段排查。它适合两类人:一类是正在做软件无线电或射频板卡开发、需要把 AD9361 挂到 FPGA 上的工程师;另一类是刚开始接触宽带收发器数字基带接口、想从例程里看懂协议层设计的同学。接下来我就按“架构是什么-怎么跑起来-核心代码怎么读-踩了什么坑-怎么二次开发”这条线,把这套例程拆给你看。
2. 技术栈与整体架构:一套例程,三块拼图
2.1 AD9361 是什么,例程又是什么
AD9361 是一颗宽带射频收发芯片,覆盖 70MHz 到 6GHz,支持 200kHz 到 56MHz 的射频带宽,内部集成了本振、混频器、滤波器、AGC、12 位 ADC/DAC。它对外提供一组数字接口,接口协议是 CMOS 或 LVDS,支持 SDR/DDR 双沿采样。这颗芯片本身不跑协议栈,它需要外部控制器通过 SPI 配置内部寄存器,同时从数字接口输入输出 IQ 数据流。
所以这套 Vivado 例程本质上是两部分:第一部分是 FPGA 侧的 HDL 工程,负责把 AD9361 的数字接口接到 AXI 总线上,再用 DMA 把数据搬到内存;第二部分是软件侧,负责通过 SPI 初始化芯片、配置频率和增益、启动校准流程。两者缺一不可。
给你一个更直白的比喻:AD9361 是一个没有大脑的射频前端,FPGA 是搬运工,软件是大脑。例程把大脑怎么想、搬运工怎么动、前端怎么干活,全部串成了可运行的样例。
2.2 数据通路的骨架:RX 进、TX 出
先看接收方向。天线收到的射频信号经过 AD9361 内部混频、滤波、AGC 后,变成模拟基带 I/Q 信号,再由芯片内部的 12 位 ADC 数字化。数字接口按约定好的格式把 I/Q 数据发出来。FPGA 侧收到的原始数据是打包好的比特流,例程里通常先做解包,把 I 路和 Q 路拆开,拼成 32 位或 64 位总线,再转成 AXI4-Stream 送给 DMA,最后由 DMA 写进 DDR。
发射方向刚好反过来。软件把要发的 I/Q 数据从 DDR 读出来交给 TX DMA,DMA 通过 AXI4-Stream 把数据送进打包模块,打包模块按 AD9361 接口协议重新组织成发射格式,再通过数字接口交给芯片内部的 DAC 变成模拟信号,经混频和放大后从天线发出去。
这块链路里有三个关键模块值得你先记住名字:接口适配模块(负责与 AD9361 数字接口时序对齐)、打包/解包模块(负责数据格式转换)、DMA 模块(负责与内存交互)。后面讲核心代码时会逐个展开。
2.3 例程的目录结构与关键文件
一套完整的 AD9361 Vivado 例程,拿到手后目录结构大致如下表:
| 目录或文件 | 作用 |
|---|---|
| hdl/ | FPGA 工程,包含 Verilog 源码与约束文件 |
| hdl/projects/<板卡名>/ | 按板卡区分的工程,Vivado 工程文件或 Tcl 脚本在此目录 |
| linux/ | 嵌入式 Linux 侧的驱动与设备树配置 |
| no-OS/ | 裸机软件,主要用来做寄存器配置参考 |
| docs/ | 寄存器映射表与配置说明 |
你在 hdl 目录下看到的 IP 核主要来自 FPGA 厂商的标准库,比如 AXI DMA、AXI Interconnect、FIFO Generator 等。例程中真正定制化的 HDL 模块一般叫axi_ad9361、ad9361_rx、ad9361_tx,这类名字在不同的例程版本里稍有差异,但职责相近。
3. 从拿到例程到跑出数据:完整操作链路
3.1 打开工程的三种姿势
拿到例程后,打开工程的方式取决于分发形式。我见过的有三种:第一种是直接给出.xpr工程文件,打开即用;第二种是给你一个 Tcl 脚本,在 Vivado Tcl Console 里source一下就能生成工程;第三种是给 Makefile,用命令行批量生成。例程里最常见的是前两种。
如果你打开.xpr文件时弹出版本升级提示,那是因为创建工程用的 Vivado 版本和你本机版本不一致。比较稳妥的做法是先「Cancel」,看一眼工程文件的版本号,再决定用哪个 Vivado 打开。强行升级 IP 往往会把时序约束关系改乱,后面综合出来的时序结果差异很大。
如果你拿到的是 Tcl 脚本,执行前先确认当前工作目录在工程目录下。一般在 Vivado 的 Tcl Console 里执行:
cd /path/to/ad9361_project source ./scripts/project.tcl此时会自动创建工程并添加所有源文件和约束,当控制台输出结束且没有任何ERROR时,工程就创建好了。有一点要提醒你:这套例程对 Vivado 版本敏感,不同版本下脚本语法和 IP 版本都可能变化,最好先用vivado -mode batch跑一次脚本,避免 GUI 环境下中途弹窗阻塞流程。
3.2 生成比特流之前的约束检查
工程创建完成后,先别急着Generate Bitstream。你要检查三处约束文件:引脚约束(XDC)、时钟约束、时序例外。引脚约束决定了 AD9361 的DATA_CLK、RX_FRAME、TX_FRAME、I/Q 数据引脚接到 FPGA 的哪些管脚,这部分通常由板卡原理图决定,你手里拿到的例程已经写好了,关键是确认它和你实际板卡一致。
时钟约束是最容易出问题的地方。AD9361 的DATA_CLK由芯片内部时钟产生,频率与采样率、接口模式相关。在 LVDS DDR 双通道模式下,DATA_CLK频率通常是采样率的一半,但数据沿是在时钟上下沿同时采样,所以逻辑侧看到的有效数据速率等于采样率。你需要自行创建这个时钟约束,因为它是外部引入的,不会自动推断。常见写法类似:
create_clock -name data_clk -period 6.510 [get_ports DATA_CLK_P]这里6.510是周期,对应 153.6MHz,如果你配置的采样率恰好是 30.72MSPS 的倍数关系,这个数值可能需要调整。我一般会在改完采样率后,复查这一条约束,它是最容易被忽略的。
检查完约束后(记得运行Report Timing Summary确认没有关键路径违规),再执行综合和实现。这一步在工程较大时耗时较长,耐心等待即可。
3.3 上板验证:从 dmesg 到 IIO 工具
比特流下载到 FPGA 后,软件侧的验证流程一般是这样的。先确认 SPI 通信正常,因为 AD9361 的初始化全靠 SPI 写寄存器。在 Linux 环境下,加载驱动后看内核日志,搜索器件名称,如果能看到类似 “AD9361 initialized” 的打印,说明 SPI 通路已通。
接着用 IIO 工具检查设备列表:
iio_info -s如果能列出ad9361-phy和ad9361-dac两个设备节点,说明驱动枚举成功。此时读取一个接收通道的原始值,看数据是否连续变化:
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw连续执行几次,如果读数呈明显跳动,说明 ADC 数据已经进到内存了。到这一步,例程的链路就算真正跑通了。很多人走到这里会以为大功告成,但接下来我要讲的坑,全部发生在“看起来跑通了”之后。
4. 核心代码拆解:一条数据从天线到内存的完整旅程
4.1 接口适配模块:时序在谁手里
AD9361 的数字接口时序由芯片输出的DATA_CLK控制,RX_FRAME信号标志一帧数据的开始。例程中的接口模块通常是一个简单的同步逻辑块,用DATA_CLK打拍采样数据总线。以 LVDS DDR 模式为例,每个时钟沿采到 12 位单通道数据,两个沿组合成 24 位,再把 I/Q 两路拼成 48 位数据字。
例程里一段类似这样的代码很值得细看:
// ad9361_rx_if.v wire [11:0] rx_data_p, rx_data_n; // LVDS差分数据 reg [11:0] data_rise, data_fall; // 上下沿捕获 always @(posedge data_clk) begin data_rise <= rx_data_p; // 上升沿采样 end always @(negedge data_clk) begin data_fall <= rx_data_p; // 下降沿采样 end assign rx_data_24b = {data_rise, data_fall}; // 拼成24位这段的逻辑不复杂,但它有一个关键点:data_clk是外部输入时钟,采样数据时必须保证rx_data_p相对data_clk的建立保持时间满足要求。如果约束没做对,这里采到的数据就是乱的。你看到 I/Q 数据异常时,优先检查的不应该是代码逻辑,而是这一级的时序余量。
4.2 解包与打包模块:数据格式的对齐
AD9361 支持多种数据格式,常见的是二进制补码表示,I/Q 各 12 位。例程里通常会做一个宽度转换和符号位扩展。接收侧,FPGA 把 24 位 AD9361 数据字扩展成 16 位 I 和 16 位 Q(高 12 位有效,低 4 位补零),打包成 32 位总线。
发射侧刚好反向,取 32 位总线的 I/Q 各取高 12 位,拼回 24 位发送格式。这段代码大多数是个简单拼接,真正的坑在于TX_FRAME的时序。AD9361 发射方向要求TX_FRAME与数据总线的相对关系满足接口规范,如果 FPGA 侧生成TX_FRAME的时钟与数据错位,射频端会收到错帧。
你可以到板级调试工具(比如 ILA)里实际抓一下TX_FRAME和TX_DATA的波形,确认每帧第一个采样点与TX_FRAME对齐。这是排查发射方向问题的标准操作。
4.3 DMA 配置:流式传输的阈值与突发长度
数据从解包模块出来,进入 AXI4-Stream 后交给 AXI DMA。例程里 DMA 的初始化配置大部分在软件侧完成,FPGA 侧只需要保证流接口的信号齐全:tvalid、tready、tdata、tlast。
你会发现例程在 DMA 配置里有一组「突发长度」参数,这是指一次传输请求中连续搬运的数据个数。调大突发长度能提高吞吐率,但也会增加延迟。接收场景下,如果数据来得太快而 DMA 没有及时发起请求,FIFO 会溢出,表现为tready长时间拉低。这种问题看现象就像“数据丢包”,但本质是软件侧缓冲区或 DMA 描述符配置不当。
调试时抓两个信号就够:s_axis_tready和s_axis_tvalid。如果两者同时为高的时间占比很低,说明瓶颈在软件侧消费速度;如果占比很高但内存里的数据仍然错乱,那问题可能出在打包模块的字节序。
4.4 寄存器配置:初看不值钱,改起来想哭
这套例程里唯一让人看几遍都不腻的就是寄存器配置序列。AD9361 初始化要经历:软件复位、SPI 读校验、设置参考时钟分频、配置本振频率、设置增益模式、触发内部校准、等校准完成标志。
例程里通常给一段初始化序列:
static const struct reg_val initial_cfg[] = { {0x000, 0x01}, // 软件复位 {0x002, 0x00}, // 解除 SPI 接口降功耗 {0x003, 0x62}, // 参考时钟倍频与分频设置 {0x007, 0x01}, // 使能 RF 前端 LDO {0x00A, 0x00}, // 默认增益表 {0x2C5, 0x03}, // 数据接口格式:LVDS、DDR };这段序列并不需要你背,但有两个参数你需要看懂:0x003与参考时钟相关,决定芯片内部工作时钟;0x2C5与数字接口模式相关,直接决定 FPGA 侧接口模块该按哪种时序工作。例程里这两处的值是配套出现的,你如果改了接口模式却没同步改 FPGA 侧逻辑,板上表现就是“偶尔通、偶尔不通”。我在实际开发中遇到的最多问题,都是这种配套参数不一致造成的。
5. 常见问题排查与避坑记录
5.1 高版本 Vivado 打开工程,IP 状态全部失效
现象:用新版本 Vivado 打开例程工程,IP 核显示OOC或锁死,综合时报错提示 IP 需要升级。
原因:例程创建时间较早,IP 版本不兼容。自动升级 IP 会带来两个问题:一是 IP 端口结构变化,代码里例化参数对不上;二是时序约束被重新生成,原本调好的约束可能失效。
解决:打开工程时不升级,先把例程的源码和约束文件拷到新工程里,手动重新生成 IP,参考例程里 IP 的配置参数,逐一填入。这工作量可控,尤其老例程只用个位数的 IP,并不复杂。
5.2 LVDS 模式下数据错位,I 通道串到 Q 通道
现象:接收数据在软件里看到的 I/Q 波形不对称,信号明显混叠,像是两个通道互换。
原因:LVDS 数据总线的引脚交换或差分对正负极性接反。通常是 PCB 布线时为了绕等长做了交换,而例程里的引脚约束没有跟着调整。
解决:先用软件把发射通道设为单音信号,接收自环,观察 I/Q 两路功率是否一致。不一致时,直接修改约束文件中对应引脚的差分对极性或位置。
5.3 SPI 寄存器写进去读出来不一致
现象:驱动报告初始化失败,回读寄存器值与写入值不一致。
原因:SPI 的时钟极性和相位没对上,或者 AD9361 的ENABLE引脚没拉高,芯片处于低功耗状态。
解决:先在逻辑分析仪里确认 FPGA 侧的 SPI 主控时钟相对CS的时序是否符合 AD9361 手册要求。常见的配置是 CPOL=0、CPHA=0,但例程里为了满足时序会做延迟。此时不要照抄例程,按实际波形的建信时间调整 SPI 时钟频率。
5.4 改了采样率之后,IIO 读到数据速率不符
现象:软件里配置采样率 61.44MSPS,但 DMA 搬运到内存的数据量只对应约一半速率。
原因:改了 AD9361 采样率之后,数据接口时钟频率变化,但 FPGA 侧的时钟约束还是旧的,导致综合工具按错误时序布线,接口模块在高速下丢样本。
解决:回头改 XDC 里create_clock的周期,重新综合。记住一个规律:LVDS DDR 模式下DATA_CLK频率等于采样率的一半,SDR 模式下等于采样率。先算好再动手。
5.5 校准流程永远卡住,日志停在初始化中途
现象:驱动在等待 AD9361 校准完成时超时,寄存器忙标志位置位。
原因:参考时钟没起振或频率不对,芯片内部 PLL 锁不住,校准无法执行。
解决:用频谱仪或示波器检查参考时钟引脚,确认频率是否符合配置的分频系数。造成功率不足或波形质量差时,可以单独用信号源直接注入参考时钟验证。
6. 二次开发思路:给例程加一个自定义 DSP 模块
6.1 先明确改动边界
AD9361 例程不适合大改,但它留了一个很舒服的插入点:解包模块和 DMA 之间。你的自定义逻辑理想位置就在这两者之间。以接收链路为例,数据在解包后变成 32 位 AXI4-Stream,你在这条流上串一个处理模块,再输出到 DMA,链路其余部分完全不动。
改法就三步:写一个 AXI4-Stream 接口的处理模块,把它插进数据通路,验证处理后的数据形态。下面以一个抽取滤波器为例,说明具体怎么落。
// cic_decimate.v —— 简易抽取滤波器,插在解包与DMA之间 module cic_decimate #( parameter DATA_WIDTH = 32, parameter DECIMATION = 2 )( input wire clk, input wire rst_n, input wire s_axis_tvalid, input wire s_axis_tlast, input wire [31:0] s_axis_tdata, output reg m_axis_tvalid, output reg m_axis_tlast, output reg [31:0] m_axis_tdata ); reg [DATA_WIDTH-1:0] acc; reg [1:0] cnt; wire signed [15:0] i_in = s_axis_tdata[31:16]; wire signed [15:0] q_in = s_axis_tdata[15:0]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt <= 2'd0; m_axis_tvalid <= 1'b0; m_axis_tlast <= 1'b0; m_axis_tdata <= {DATA_WIDTH{1'b0}}; end else if (s_axis_tvalid) begin cnt <= cnt + 1'b1; if (cnt == DECIMATION - 1) begin m_axis_tvalid <= 1'b1; m_axis_tdata <= {i_in, q_in}; m_axis_tlast <= s_axis_tlast; // 周期与输入保持一致 cnt <= 2'd0; end else begin m_axis_tvalid <= 1'b0; end end end endmodule这段代码的关键点在于:输出tvalid只在每两个输入样本时拉高一次,实现了 2 倍抽取。tlast跟随输入最后一个样本的标志,但因为tlast在输入侧是每帧末尾才拉高,抽取后如果帧长度不是偶数,会丢帧尾。实际工程中应该把tlast推迟到输出侧最后一个有效样本,这个细节务必注意,不然 DMA 会一直等帧结束而超时。
插进通路的办法是改上层模块的例化,把原来ad9361_rx_out到axi_dma_in的直接连接改为经过该模块。接着综合、时序检查、上板。用 ILA 抓m_axis_tvalid,正常情况下它的平均频率应该约是s_axis_tvalid的一半。
6.2 改采样率的完整清单
如果你要做的不是加模块,而是改例程的工作采样率,有一套固定的变动清单,按顺序执行可以少走很多弯路:
| 改动项目 | 位置 | 说明 |
|---|---|---|
| AD9361 参考时钟分频配置 | 软件寄存器配置 | 决定芯片 PLL 输出频率 |
| 数据接口模式配置 | 软件寄存器配置 | LVDS/CMOS、SDR/DDR 要同步调整 |
| 数据时钟约束周期 | XDC 文件 | 用公式重新计算周期 |
| 采样率设置 | 软件 IIO 接口 | 设置后回读确认生效 |
| 数字滤波器系数 | 软件配置 | FIR 系数要按新采样率重新归一化 |
每改一项,都要在板上验证一项。不要指望一次全改完再看结果,出问题时不好定位。我的习惯是每改完一个点就抓一次波形,确认链路局部正常再往下走。
这套例程最折磨人的地方,恰恰是你以为“只改一个参数就行”的时候。后来我每次动采样率或接口模式,都会强制自己走一遍从寄存器到 XDC 的完整变更清单。希望这个习惯也能帮到你。
本文还有配套的精品资源,点击获取