☰
AD9361官方例程实战:从HDL到BPSK调制与PetaLinux设备树移植
2026/10/4 15:09:02 网站建设 项目流程

做射频或者软件无线电这行的,应该没人不知道 AD9361。这颗由 ADI 出的宽带收发器,几乎成了 SDR 硬件平台的代名词。很多项目开发者的第一站,都是去 GitHub 上找“官方例程”,我也是这么过来的。但真正下载下来之后,不少人会愣住:例程不是一个工程文件,而是一大堆 HDL、C 代码、设备树和脚本的集合,和平时点个 STM32 例程的体验完全不同。

这篇文章我就围绕 AD9361 官方例程这件事,把整个例子体系的组成、怎么把它跑起来、怎么基于它做 BPSK 调制解调,以及怎么把老工程里的设备树平滑搬到新建的 PetaLinux 工程里,这几条最常被问的路线,按我自己的实际操作经验,一条一条拆开讲。内容偏向工程落地,不堆理论公式,适合刚拿到开发板准备开干的人,也适合被设备树和编译流程折腾到头疼的进阶玩家。

1. 例程全景:先搞明白官方仓库里到底有什么

1.1 三条主线:HDL、No-OS、Linux 驱动的关系

AD9361 的官方例程体系里,最基本的其实是三套东西,它们负责的层次完全不同,但项目里通常要一起出现。

第一套是 HDL 参考设计,存放在 ADI 的 hdl 仓库里,对应目录是projects/ad9361。它解决的是“FPGA 和 AD9361 之间的数字接口怎么搭”的问题,包含 LVDS/CMOS 数据接口、SPI 控制模块、DMA 引擎、jesd 之类的逻辑(实际上 AD9361 走的是并行接口,没有 JESD,这里重点是数据通路和控制通路)。Xilinx 的 Vivado 工程直接关联这个目录,跑一下 tcl 脚本就能生成完整的 bitstream。

第二套是 No-OS 驱动与示例应用,代码在 no-OS 仓库的ad9361部分。它面向“没有操作系统的裸机环境”,主要提供 AD9361 的寄存器读写、初始化序列、校准流程、收发使能状态机等。它的核心价值不是给你一个可以跑业务的应用,而是把 AD9361 的“脾气”摸透了,你拿到手可以立刻完成芯片的初始化和数据收发测试。

第三套是 Linux 下的 IIO 驱动和 libiio 用户态工具。ADI 把 AD9361 做成了标准 IIO 设备,内核里有ad9361驱动,用户态可以用iio_oscilloscope、iio_reg这些工具直接读写寄存器、采集波形、配置频率。工程上绝大多数最终产品都会跑 Linux,这一条线才是真正能让你快速验证射频链路通不通的锤子。

所以,当你听到“AD9361 官方例程”时,要先建立这个概念:它是一个三层结构。最底下是 HDL 比特流,中间是驱动(裸机或 Linux),最上层是你自己的调制解调算法或者应用。三层之间靠接口、设备树和寄存器约定串联起来。后面我所有的经验都围绕这个三层结构来讲。

1.2 为什么第一件事不是改代码,而是跑通自带脚本

很多开发者的习惯是把源码拉下来以后,先去读代码、改代码,想一上来就针对自己的需求调整。AD9361 官方例程上,这个习惯要改一改。我的建议是,第一件事是完完整整地把官方脚本跑一遍,先让默认工程在硬件上工作起来。

理由很简单,AD9361 的数字接口不是一根线那么简单。FPGA 和 AD9361 的时序必须严格匹配,涉及数据速率、接口模式(CMOS 还是 LVDS)、单端还是差分、延迟对齐、双通道还是单通道。这些参数只要错了一个,表现出来都是“数据收不到”或者“波形是花的”,但根因极其隐蔽。官方 HDL 工程里已经帮你把这些时序约束做死了,只要你用配套的板卡和配置,跑出来的状态就是对的。在这个基础上再改你自己的逻辑,才有一个可信的基线。

我见过太多人一上来就按自己的理解改了数据接口模式,结果三天没调通,最后回退到默认配置才发现时钟极性反了。所以说,官方例程用得好不好,区别不在于你会不会改,而在于你知不知道什么不能改。

1.3 版本配套:一个容易被忽略的大坑

AD9361 官方例程的第二个大坑是版本配套。ADI 在 GitHub 上维护代码时,HDL、No-OS 和 Linux 驱动经常同步更新。比如你在 2023 年拉了一个 HDL 主分支,再用两年前的 No-OS 代码去配它,很可能出现寄存器映射不匹配、SPI 命令格式不同的问题。因为 AD9361 的 API 版本(尤其是一些校准算法相关的配置)会跟着驱动一起演进。

我的习惯是,项目一开始就固守一个“版本快照”。建议从 ADI 的 release 标签里选一个稳定的组合,比如 HDL 的hdl_2022_r2,配合 No-OS 对应 tag,Linux 内核也固定到同样的 Yocto release。这样遇到问题可以在社区里找到相同版本的环境,网上搜到的答案才不会和你本地的代码对不上号。

2. 硬件准备与工程目录解构

2.1 最小系统搭建:开发板、载板与电源检查

跑官方例程,硬件平台最好用一个“官方验证过”的组合。ADI 自己的 FMComms2/3/4 载板,搭配 Xilinx 的 Zedboard、ZC706 或者 ADI 自家的 ADRV9361 系列,是最不容易出幺蛾子的选择。你当然可以用国产 FPGA 板卡自己画接口转接板,但那就意味着 HDL 工程的引脚约束、时钟输入方式全都要重新适配,这块工作量和调试风险会非常大。

电源是第一个要检查的点。AD9361 的供电轨比较多:1.3V 数字、2.5V 模拟、3.3V 接口等,载板上通常由 LDO 从 5V/6V 输入转出来。跑例程之前,用万用表确认各供电轨的电压值是否在 AD9361 数据手册规定的范围内,尤其是 AVDD1P3 和 DVDD1P3,偏差超过 5% 就可能出现寄存器读回值随机跳变的现象。这个问题和软件完全无关,但经常被误判成驱动 bug。

第二个检查点是参考时钟。AD9361 需要一路参考时钟,FMComms2 板上一般由 40MHz 晶振产生。如果参考时钟不稳,后面所有载波频率都会偏,可能导致接收端解调出来的星座图一直在旋转。这一点在 BPSK 这种对相位敏感的应用里尤其不能凑合。

2.2 HDL 工程的目录结构与 IP 含义

官方 HDL 工程打开以后,很多人会被目录结构绕晕。这里挑最核心的几块说。

projects/ad9361/common下存放的是 AD9361 相关的通用逻辑,包括数据接口模块ad9361_data、 SPI 控制器ad9361_spi、 以及把这两者封装成 AXI 外设的axi_ad9361。在 Vivado 里,你会在 Block Design 中看到axi_ad9361这个 IP,它对外提供 AXI 接口给 PS 或软核访问,对内连接 AD9361 的并行数据引脚。

projects/ad9361/zc706_fmcomms2这样的板级目录下,是具体板卡的顶层逻辑、引脚约束(xdc 文件)和 Block Design tcl 脚本。你直接打开这个目录里的 Vivado 工程或跑make,就能得到适配这块板卡的 bitstream。

还有一部分容易被忽略但是至关重要的逻辑是 DMA 引擎。axi_dmacIP 负责把 AD9361 的接收数据流搬到内存,或者把内存里的发射数据搬到 AD9361。它和axi_ad9361配合,就是官方例程里“FPGA 到 PS 内存”的主干道。理解这一点之后,你在 IIO Oscilloscope 里看到数据,本质上就是 DMA 从内存里抓出来的。

2.3 No-OS 应用层的模块划分

No-OS 工程里,ad9361目录下有几个文件是理解整个例程的钥匙。

ad9361_api系列文件实现了对外 API 层,比如ad9361_set_tx_fir、ad9361_set_rx_gain、ad9361_rf_pll_set_freq。这些是你做应用配置的时候直接调用的函数,底层细节被封装好了。

ad9361.c和ad9361_conv.c是核心驱动文件,内部处理寄存器初始化序列、校准、增益表加载、滤波器配置下载等。普通应用开发基本不用改这里,但其中ad9361_init这个函数的参数结构体adi_ad9361_init_param里,有一堆与硬件连接相关的信息,比如 GPIO 引脚号、接口模式、频率规划,你配置错了后面全错。

ad9361_util.c提供一些寄存器读写内部函数,其中 SPI 底层通过ad9361_spi模块实现,在 No-OS 环境里通常用 GPIO 模拟或直接映射到 AXI SPI。

3. 把官方例程跑起来:编译与下载

3.1 获取代码与工具链准备

代码获取建议直接 git clone 官方仓库,并且用 tag 固定版本。比如:

git clone https://github.com/analogdevicesinc/hdl.git cd hdl git checkout hdl_2022_r2

No-OS 仓库类似:

git clone https://github.com/analogdevicesinc/no-OS.git cd no-OS git checkout 2022_R2

工具链方面,HDL 工程需要 Xilinx Vivado,版本最好与 ADI 仓库说明一致,避免综合或实现阶段出现莫名其妙的 IP 版本报错。No-OS 裸机工程需要对应 FPGA 芯片的 SDK 或 Vitis,如果只跑 Linux 就不需要 No-OS。

3.2 Vivado 工程编译流程

在hdl/projects/ad9361/zc706_fmcomms2目录下,官方提供了 Makefile 风格的编译方式:

make

这条命令会自动调用 Vivado 的 batch 模式,运行system_project.tcl,生成 bitstream。首次运行时间长,因为它会把所有 IP 重新生成一遍。如果你的机器配置一般,建议耐心等,同时观察终端输出里有没有时序失败(timing failed)的提示。AD9361 接口的时序要求比普通逻辑高,如果出现 setup time violation,先不要急着改约束,检查是不是 Vivado 版本太老、编译策略太激进,或者直接跑make AD9361_LVDS=1切到 LVDS 模式看是否更稳。

编译完成后,在runs目录下会生成system_top.bit和system_top.hdf(或 xsa)。这个 bit 文件就是要下载到 FPGA 的镜像。

3.3 在 Linux 下快速跑一个收发环回

如果用的是 PetaLinux 或 Yocto 出来的系统镜像,把 bit 文件烧进 FPGA 后,系统起来就会自动加载ad9361驱动,IIO 设备节点会在/sys/bus/iio/devices/下出现。这时你不需要写任何 C 代码,就能用官方工具验证射频链路。

最直接的办法是用 ADI 提供的 IIO Oscilloscope(图形化工具)或者命令行工具iio_readdev、iio_writedev。比如用 iio_readdev 抓一段接收数据:

iio_readdev -s 1024 cf-ad9361-lpc 2>/dev/null | xxd | head -20

这段命令从接收 DMA 设备cf-ad9361-lpc读取 1024 个样本,输出十六进制数据。正常工作时,如果天线口有信号进来,这里的 I/Q 数据会不断变化;如果没有信号,可能是全零或者一个接近直流的偏置值。这时候你可以先不接天线,用一个射频信号源给 RX 口一个单载波,看能否在时域看到正弦波形的 IQ 轨迹。

提示:AD9361 的接收链路默认有一个直流失调校正,所以非常强的 DC 信号会被自动扣除,这是正常现象。调试时不要看到直流分量消失就以为链路断了。

3.4 用 IIO Oscilloscope 验证整条链路

IIO Oscilloscope 是 ADI 为 Linux 下的 IIO 设备做的跨平台示波器。连接到目标板的 IP 后,它能同时显示时域波形、频域频谱和 IQ 星座图。

实测下来,这个工具最大的价值是能快速确认“RF 前端 + 数据接口 + DMA”三者是否协同。你把本振设到 2.4GHz,用信号源发一个 -30dBm 的 2.4GHz 单音,示波器上应该能看到一个以 0 为中心的旋转向量,对应 IQ 平面上的圆。如果看到的是直线或很扁的椭圆,说明 IQ 幅度不平衡或者相位不正交,多半是配置里 RX 增益或滤波器参数有问题。

官方例程跑通这一整套之后,你才算真正“摸到了”AD9361 的完整数据通路,后面做 BPSK 调制解调就有一个非常扎实的底子。

4. 从官方例程到 BPSK 调制解调的工程化实现

4.1 官方例程里没有 BPSK,但提供了所有积木

AD9361 官方例程不会直接给你一个 BPSK 调制解调的完整工程,因为 AD9361 本身只是射频收发器,调制解调功能要由外部 FPGA 或处理器完成。但它把最难的射频前端和高速数据接口给你准备好了,你真正要做的,是在 Zynq 的 PS 端或者 PL 端,把基带数据流“加工”成 BPSK 信号,再送进 AD9361 发射;接收时从 AD9361 拿回基带 IQ 数据,完成解调和判决。

所以实现 BPSK 的过程,本质上就是在官方例程的骨架上,搭自己的算法。你可以用 PS 端纯软件实现低速率的 BPSK,也可以用 PL 端 Verilog 实现高速率的 BPSK。下面我以“FPGA 生成 BPSK 基带数据 + 软件控制收发”这个组合为例,讲具体步骤。

4.2 关键配置:时钟树、带宽与数据率

AD9361 的采样率决定了一切。AD9361 的 ADC/DAC 采样率范围在几十 MSPs 量级(具体数值和接口模式有关)。BPSK 的符号速率必须低于这个采样率,并且 AD9361 内部 FIR 滤波器的通带要能容纳你的信号带宽。

假设你设定了 ADC 采样率为 40MSPS,BPSK 符号速率为 1MSPS,那么每个符号有 40 个采样点。在发射端,先要把每个 bit 映射成 +1/-1 的幅度序列,然后经过成型滤波(通常是根升余弦滤波器)产生基带波形,再按每符号 40 个点的节奏写入发射 DMA 的缓冲区。AD9361 会自动把这个基带波形上变频到射频载波频率上。

用 No-OS 的 API 设置采样率时,可以这样:

ad9361_set_tx_sampling_freq(ad9361_phy, 40000000); ad9361_set_rx_sampling_freq(ad9361_phy, 40000000); ad9361_set_tx_rf_bandwidth(ad9361_phy, 1000000); ad9361_set_rx_rf_bandwidth(ad9361_phy, 1000000); ad9361_set_tx_lo_freq(ad9361_phy, 2400000000); ad9361_set_rx_lo_freq(ad9361_phy, 2400000000);

这里设置带宽 1MHz 是为了配合 1MSPS 的 BPSK 信号。带宽太大会引入更多噪声,太小会把信号边缘切掉。

4.3 调制端:用 DMA 持续发送基带数据

发射路径的基带数据,是放在内存里的一个数组,里面是交替的 I 和 Q 样本(16bit 有符号整数)。BPSK 里 Q 路可以直接置 0,只把数据放在 I 路。这样发射出来的就是标准的二相键控信号。

一个最简单而有效的发送流程是:

  1. 准备一段伪随机 bit 序列(比如 PN9)。
  2. 把 0 映射为 +2000(一个合适的幅度),把 1 映射为 -2000。
  3. 按每符号 40 个样点重复,得到 IQ 交错数组。
  4. 通过iio_writedev或 No-OS DMA 接口,把这个数组循环写入发射设备。
  5. 射频端接上频谱仪或者另一个接收机来验证。

如果你用 Linux 下的 libiio,核心代码大概这样:

struct iio_device *tx = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc"); // 直接写 raw 数据块到 TX 设备 iio_device_open(tx, 0); iio_device_write(tx, buffer, buffer_size);

这里cf-ad9361-dds-core-lpc是官方 HDL 里 DDS/DMA 发送设备的默认名称。写进去的数据会被 FPGA 以发射采样率不断重复发送,形成连续的 BPSK 帧。

注意一个实测经验:发射幅度不要给满。AD9361 的 DAC 满量程附近非线性会略微增加,星座点会向内弯。一般用满量程的 50%~70% 比较合适。示例中幅度 2000/32767 大约是 6%,偏保守了实际可以到 8000~12000 都没问题,但别一口气加到 30000 以上。

4.4 解调端:同步、判决与误码验证

接收端比发射端复杂。BPSK 解调除了要看准符号,还要解决载波同步和定时同步。官方例程不会帮你做这层处理,所以你要自己写。

我的做法是分三步走:

第一步,先不加任何同步算法,把 IQ 数据直接抓下来,用 Python 或 MATLAB 做离线分析。用一段已知的 PN9 序列做前导码,在接收数据流里滑动相关,找到帧头位置。这一步能验证射频环路通不通。

第二步,实现定时同步。因为发射和接收的采样时钟都是同一个 AD9361 参考时钟派生出来的,短时间内的采样偏差很小。你可以把“每 40 个点取一个符号中心点”当作最简单的定时恢复。如果发现符号边缘漂移,可以在 FPGA 里做一个简单的 Gardner 定时同步环。

第三步,实现载波同步。BPSK 信号解调后,如果接收本振和发射本振之间存在频率偏移,星座图上的点会缓慢旋转。一个经典的解决方法是使用 Costas 环。在 Zynq 的 PL 端写一个 Costas 环并不复杂,核心就是一个鉴相器、一个环路滤波器和一个数控振荡器。锁定之后,星座图稳定在 +1/-1 两个点上,判决就非常容易。

判决时取 I 路的符号位即可:大于 0 判为 0,小于 0 判为 1。然后把判决结果和原始 bit 比对,统计误码率。在信噪比正常、频偏纠正好的情况下,BPSK 链路的误码率应该可以做到非常低,实测在 -10dBm 以上的信号强度下,千帧数据无误码是很正常的表现。

5. 把原有设备树移到新建 PetaLinux 工程里

5.1 为什么设备树不能直接拷

很多人做 PetaLinux 移植时,习惯直接拷贝旧工程里的设备树文件,也就是.dts或.dtsi,结果在新工程里编译报错,或者启动时驱动加载失败。这里面的核心问题在于:设备树不只是描述硬件,还描述硬件在某个特定内核版本下的访问方式。内核版本变了,中断控制器类型、时钟框架、GPIO 控制器的组织方式都会变,老设备树里的节点在新内核下可能根本找不到对应的驱动总线或父节点。

还有一个容易踩的坑是:PetaLinux 新工程会自动生成一套基于模板的设备树,里面已经有很多板级节点。你直接把整棵旧树覆盖过去,等于丢了新内核需要的节点,比如新的 CPU 中断控制器标识、FPGA 管理的相关节点、内存节点。所以正确做法是“按需移植”,只搬和 AD9361 相关的部分。

5.2 迁移流程:从 sysuser.dtsi 到顶层 dtsi

我的实践经验是,不修改 PetaLinux 自动生成的顶层设备树,而是在meta-user/recipes-bsp/device-tree/files/system-user.dtsi里做增量修改。这个文件最后会被包含进最终的设备树编译流程里,不破坏原有自动生成的节点。

在原有例程里,与 AD9361 相关的设备树节点一般包括:

  • ad9361@...,compatible 为adi,ad9361,挂在 SPI 总线上。
  • 一组时钟节点,通常引用adi,ad9361-clkout或ad9361_clk作为 FPGA 数据接口的时钟来源。
  • FPGA 侧的 AXI 外设节点,比如cf-ad9361-lpc,compatible 为adi,axi-ad9361。
  • 复位和使能引脚,通过reset-gpios、enable-gpios引用 GPIO 控制器。
  • 如果有 TX 监控、RX 监控,还会有对应的 DMA 设备节点cf-ad9361-dds-core-lpc和指令处理器节点。

迁移时,把这些节点完整复制到system-user.dtsi,然后按新工程的实际总线地址调整reg属性。旧工程里如果 SPI 控制器地址是0xE0006000,新工程里如果用的是同一个 Zynq 芯片,这个地址通常不变。但要注意中断号,新内核如果从interrupt-parent = <&intc>改成了 GIC v2 或其他中断控制器,中断号要重新核对。

下面是一个典型的 AD9361 设备树迁移片段:

&spi0 { status = "okay"; ad9361: ad9361@0 { compatible = "adi,ad9361"; reg = <0>; spi-max-frequency = <16000000>; clocks = <&ad9361_clk>; clock-names = "ad9361_ext_refclk"; reset-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; enable-gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; }; };

这里ad9361_clk需要单独定义,一般用fixed-clock或者从 PL 端导出的时钟。最省事的方式是引用原有工程里的时钟定义,复制过来后修改为:

ad9361_clk: clock@0 { compatible = "fixed-clock"; clock-frequency = <40000000>; #clock-cells = <0>; };

5.3 内核配置与驱动匹配检查

设备树搬过去还只是第一步,新 PetaLinux 工程的内核配置里如果没有编译 AD9361 相关驱动,设备树写得再漂亮也白搭。检查的项目包括:

  • CONFIG_AD9361 必须为 y 或 m,驱动文件是drivers/iio/adc/ad9361.c(ADI 树里是drivers/iio/adc/ad9361.c)。
  • CONFIG_CF_AXI_AD9361,对应 FPGA 里axi_ad9361IP 的驱动。
  • CONFIG_AD9361_DDS 之类和 TX 发射 DMA 相关的配置。
  • 如果用到 libiio,CONFIG_IIO_BUFFER、CONFIG_IIO_TRIGGERED_BUFFER 也要打开。

在 PetaLinux 里,可以用petalinux-config -c kernel进入内核配置界面,搜索AD9361确认这几项都被勾选。

驱动匹配靠的是compatible = "adi,ad9361",这个字符串在内核源码的of_device_id表里必须存在。如果驱动编译进内核了但设备没 probe,可以查启动日志:

dmesg | grep ad9361

如果看到ad9361: probe of spi0.0 failed with error -22,多半是设备树里某个属性写错或者时钟没配对。如果看到-517,说明某个依赖的 GPIO 或时钟还没 probe,属于顺序问题,可以考虑加depends-on或者检查时钟节点的状态。

5.4 迁移之后怎么验证

设备树改完,系统启动后先确认驱动绑定成功:

ls /sys/bus/iio/devices/

正常情况下会出现iio:device0(对应 ad9361-phy)和iio:device1(对应 cf-ad9361-lpc)等节点。然后用 libiio 工具做一次收发环回验证,比如先把发射频率设为 1GHz,接收频率也设为 1GHz,从发射 DMA 发一组正弦波,接收 DMA 抓回来,用 Python 的 numpy 做一次相关分析,确认波形的频率和初相是否对得上。

这一步通过,说明设备树移植没有破坏 AD9361 的软件链路,后续在上面跑 BPSK 或 OFDM 这类业务算法,就安心了。

6. 高频问题排查实录

6.1 采集数据全零或恒定 DC

最常见的原因有几个。首先是接收链路增益设置太低,AD9361 的 RX 增益可以从 0 到 70dB 左右调节,如果设成 0,天线口进来的微弱信号根本不被 ADC 检测到。用 IIO Oscilloscope 调增益时,先设一个中等增益如 30dB 再观察。

第二个原因是 RX 使能没打开。AD9361 的收发状态机(ENSM)如果没有正确进入接收模式,数据接口上不会有有效的 IQ 数据。在 No-OS 里要调用ad9361_set_en_state_machine设置工作模式并初始化。

第三个坑比较隐蔽,就是数据接口时序不对。CMOS 模式下,数据线在时钟上升沿还是下降沿采样,由数据手册里的一个寄存器位控制。如果改了逻辑没有同步改寄存器,就可能采到全是 0 或 0xFF 的数据。

6.2 IIO Oscilloscope 连接不上目标板

检查两点。第一,目标板是否运行了iiod守护进程,如果没有,libiio 的网络后端连不上端口 30431。第二,网络是否通,先ping一下目标板 IP。如果 ping 通但 iio 连接失败,可能是防火墙或 iiod 没起,执行iiod &看有没有报错。

如果用的是 USB 或者本地模式,要确保加载了libiio的本地后端,并且设备节点有访问权限。实际调试中,我发现不少问题出在用户权限上,iio_readdev命令前可以加sudo或者把用户加入iio组。

6.3 BPSK 解调星座图旋转

星座图旋转的本质是收发端的本振频率有偏差,或者采样时钟有偏差。先检查收发两端配置的 LO 频率是否完全一致,包括小数部分。AD9361 的 LO 分辨率是微赫兹级别的,配置脚本里如果用了浮点打印,容易出现肉眼不可见的微小差异,积累起来会造成星座图缓慢旋转。

如果频率配置没问题,就要关注参考时钟精度。晶振的 ppm 值会直接转化为载波频偏。比如收发各用一块板卡,参考时钟准确性都是 20ppm,2.4GHz 下频偏最坏可以到 96kHz,对 1MSPS 的 BPSK 来说影响很大。解决思路是在数字域做频偏估计和补偿,或者用外置高精度参考时钟。也可以用 Costas 环把残余频偏消掉,这更符合工程实际。

6.4 PetaLinux 启动时设备树编译报 duplicate unit address

这通常是因为复制节点时没有注意源文件里已经有同名节点,比如ad9361@0在外层 dtsi 里已经被定义过,你在system-user.dtsi里又定义了一次。正确做法是用&ad9361 { ... }这样的引用方式修改已有节点,而不是重新定义。如果旧工程里节点命名是ad9361@0,新工程里 SPI 子节点的地址可能是ad9361@0也可能是ad9361@1,要按 SPI 片选的实际编号来写,不要无脑照抄。

6.5 一套问题排查顺序清单

多年的调试经验总结下来,我遇到问题时一般按这个顺序排查:

  1. 先确认电源和参考时钟,这两个是基础。
  2. 再用 SPI 读寄存器验证通信,比如读 AD9361 的 Product ID 寄存器。
  3. 然后查 FPGA 数据接口是否有时钟信号,AD9361 的 DATA_CLK 引脚必须有脉冲。
  4. 再查 DMA 和中断链路,确认 CPU 能收到 DMA 中断。
  5. 最后才怀疑算法和业务逻辑问题。

这个顺序让我少走了非常多弯路。很多人一上来就怀疑算法,其实大部分问题都出在链路没有通。

一些实际操作里的小经验

最后分享几个和 AD9361 官方例程打了很多年交道之后攒下的琐碎经验。

第一,SPI 读写务必加超时机制。AD9361 的 SPI 通信偶尔会因为时序竞争或者电平不稳导致读取失败,如果代码里没有超时保护,一次卡死就会让整个初始化流程陷入死循环。No-OS 例程里部分版本对这方面处理不够完善,建议在自己的工程里统一封装一层,读不到预期寄存器值就重新初始化。

第二,多备几根 SMA 线缆。射频调试时,线缆损耗、接头松动都会让你误判链路好坏。最典型的例子是,接收端收不到信号,排查半天发现是 SMA 接头只拧了一半。这种问题和软件一点关系都没有,但确实能把人逼疯。

第三,官方例程里的滤波器配置表值得好好研究。AD9361 内置的 FIR 滤波器可以灵活配置,官方例程里已经为几种常用采样率生成了对应的系数。你不要一上来就自定义滤波器,先用官方配置跑通,有性能瓶颈再考虑优化。

第四,版本记录很重要。复现 AD9361 问题时,给别人报信息的时候,写明 HDL 的 commit、No-OS 的 commit、内核版本和硬件板卡型号。这四个信息缺一个,别人想帮你看问题都无从下手。

AD9361 的官方例程体系看着复杂,但它其实是一套设计很优雅的参考设计。把 HDL、No-OS、Linux 驱动和设备树这四层的关系理解了,把默认流程完整跑一遍,再去做自己需要的调制解调算法或者系统移植,就不会有那种“满屏代码却不知道从哪下手”的感觉。这套东西我用来用去,最终的体会就一句话:官方例程是给你打底的,不是给你抄的,理解每一层为什么这么设计,比背下来一百个 API 都有用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询