基于 Vivado 的 SDR 射频信号采集:AD9361 驱动配置与数据抓取
AD9361 是 ADI 的 2×2 RF Agile Transceiver,覆盖 70 MHz–6 GHz,单通道带宽最高 56 MHz,配 Zynq(ZedBoard + FMCOMMS3 或 ZCU102 + FMCOMMS5)是 SDR 原型验证的主流组合。Vivado 侧负责 PL 逻辑(AXI_AD9361 + DMA + FIFO),PS 侧跑 Linux(ADI 的 Kuiper Linux)或 No-OS 裸库配寄存器,数据从 AD9361 → DMA → DDR,再给用户态做 FFT/解调。下面以 ZedBoard + FMCOMMS3(2.4 GHz ISM 接收,20 MHz 带宽)为主线。
一、Vivado 工程:ADI 参考设计别自己撸
ADI 开源了 analogdevicesinc/hdl 仓库,FMCOMMS3 对应 projects/fmcomms2/zed,直接用 Tcl 生成:
# 在 hdl/projects/fmcomms2/zed/ 下
source ../../scripts/adi_make.tcl
adi_make fmcomms2_zed
生成后的关键 IP:
• axi_ad9361:AD9361 的 LVDS/CMOS 接口解串 + IQ 重组,输出 16-bit I/Q(I 低 16 + Q 高 16,拼 32-bit AXI-Stream)
• axi_dmac:ADI 的 DMA 控制器,把 AXI-Stream 搬去 DDR,支持 cyclic(循环播放)和 oneshot
• util_ad9361_divclk:AD9361 输出的 DATA_CLK 进 PL,做分频/对齐
⚠ AD9361 默认 LVDS 接口(DDR 双沿 2×12 bit),Vivado 里 I/O 规划要设 LVCMOS25_LVDS_25(Bank 13/14 接 2.5 V),且 DATA_CLK、FB_CLK 要走 P→N 对,组内 skew < 50 ps。
二、PS 驱动配置:No-OS 结构体最直观
量产调试或裸跑用 No-OS(analogdevicesinc/no-OS),关键结构体 ad9361_init_param:
struct ad9361_init_param default_ad9361_params = {
.rx_lo_freq_hz = 2400000000ULL, // 2.4 GHz
.tx_lo_freq_hz = 2400000000ULL,
.rx_bw_hz = 20000000, // 20 MHz 接收带宽
.tx_bw_hz = 20000000,
.rx_sampl_freq_hz = 30720000, // 30.72 MSps(LTE 1.4M 整数倍)
.tx_sampl_freq_hz = 30720000,
.gain_ctrl_rx1 = RF_GAIN_MGC, // 手动增益,方便测 EVM
.gain_rx1 = 45, // 45 dB
.rf_port_rx = "A_BALANCED", // RX1A 平衡口
};
int main() {
struct ad9361_rf_phy *phy;
ad9361_init(&phy, &default_ad9361_params);
// 之后可以调 agc/校准
return 0;
}
Linux 侧更简单,设备树 adi,ad9361 节点把上面参数填进去,开机 ifconfig 能看到 cf-ad9361 字符设备,用户态用 libiio 拿 IQ。
几个易错参数:
• rx_sampl_freq 必须是 bbpll_div / (2×HB_dec×FIR_dec),HB 是半带滤波器,FIR 是 AD9361 内可编程 FIR——30.72 M 是常见甜点(bbpll=983.04 M,÷32 再 HB3×HB2×HB1 三级半带 + FIR ÷2 → 30.72 M)
• 增益模式:AGC(快/慢)适合实网,MGC 适合实验室测 EVM;2.4 G + 20 MHz 下 AGC 能压到 NF≈2.5 dB
三、数据抓取:ILA + DMA 双轨
1. ILA 抓实时 IQ(调试用)
在 Vivado Block Design 里 axi_ad9361 的 adc_data_* 后插 ila_v6(深度 4096,抓 30.72 M 下 4096 点 ≈ 133 µs),触发条件设 adc_valid=1:
# HW Manager 里触发
set_property CONTROL.TRIGGER_CONDITION CHIPSCOPE_ILA_0 {BASIC=AND}
set_property CONTROL.DATA_DEPTH 4096 CHIPSCOPE_ILA_0
run_hw_ila [get_hw_ilas]
导出 CSV 后 Python 画频谱:
import numpy as np, matplotlib.pyplot as plt
iq = np.fromfile('ila_adc_iq.csv', sep=',')
I, Q = iq[::2], iq[1::2]
sig = (I + 1j*Q).astype(np.complex64)
fft = np.fft.fftshift(np.fft.fft(sig * np.hanning(len(sig))))
plt.plot(20*np.log10(np.abs(fft)))
plt.title("AD9361 RX Spectrum @2.4G, 30.72Msps")
2. DMA 搬 DDR(长期采集)
axi_dmac 配成 S2MM(Stream→Memory),每次搬 8192 样点(I+Q 拼 32-bit,8192×4=32 KB 一帧),PS 侧用 libiio 的 iio_buffer_refill() 循环读:
// libiio 简易采集
struct iio_context *ctx = iio_create_local_context();
struct iio_device *dev = iio_context_find_device(ctx, "cf-ad9361-lpc");
struct iio_channel *ch = iio_device_get_channel(dev, 0);
iio_channel_enable(ch);
struct iio_buffer *buf = iio_device_create_buffer(dev, 8192, false);
while (1) {
iio_buffer_refill(buf);
int16_t *ptr = (int16_t*)iio_buffer_start(buf);
// ptr[2i]=I, ptr[2i+1]=Q
}
30.72 Msps × 4 字节 = 122.88 MB/s,ZedBoard 的 DDR3-533 能扛,ZCU102 跑 61.44 Msps(56 MHz BW)也稳。
四、三个翻车高发点
• DATA_CLK 和 BBPLL 没对齐:AD9361 的 rx_sampl_freq 改了,BBPLL 重锁,DATA_CLK 频率变,PL 侧 util_ad9361_divclk 的分频比要同步改,否则 ILA 抓到的 IQ 会"每几帧丢一段"——表现为频谱周期性毛刺。改采样率后必重跑 ad9361_set_rx_sampling_freq() 让驱动把 BBPLL + 接口时钟一起刷
• LVDS 模式 vs CMOS 模式搞混:FMCOMMS3 默认 LVDS,有些自制板走 CMOS(SDR 模式),Vivado 里 axi_ad9361 的 CMOS_LVDS_N=1(LVDS)还是 =0(CMOS)要跟硬件一致,错了 IQ 全 0 或乱码
• DMA 深度没对齐 AD9361 burst:AD9361 的 AXI-Stream 是 valid-not-ready 发,DMA 的 CYCLIC 和 ONE_SHOT 切换时机不对,会丢头尾 16 样点——第一次 refill 前先 iio_buffer_first() 扔掉前 16 样点,再进主循环
想抓 56 MHz 满带宽(61.44 Msps),ZedBoard 的 HP 口带宽不够(AXI 32-bit @150 MHz = 600 MB/s,61.44 M×4=245 MB/s 理论够但实际有瓶颈),建议换 ZCU102(HP 64-bit @333 MHz → 2.6 GB/s 稳跑)。
AD9361 + Zynq SDR 的真谛:Vivado 直接套 ADI fmcomms2 HDL 别自撸、No-OS/libiio 配 LO/采样率/带宽、ILA 抓 133 µs 看时域 IQ、DMA 搬 DDR 跑 libiio 长采;采样率改完记得同步 BBPLL 和 PL 分频,LVDS/CMOS 别焊错。这套走完 2.4 G/20 MHz 下 EVM < 2%,56 MHz 满带宽换 ZCU102 能扛,SDR 原型验证一周能出解调 demo。





