news 2026/9/28 1:13:58

基于PetaLinux的AD9361设备树配置与IIO驱动完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于PetaLinux的AD9361设备树配置与IIO驱动完整实践

1. 项目背景与整体设计思路

做 SDR 的同学应该都知道 AD9361 这颗芯片的江湖地位:一颗从 70MHz 一路覆盖到 6GHz 的宽带收发器,内置 12 位 ADC/DAC,支持双收双发,还能做 TDD/FDD。可这颗芯片真正“劝退”新手的并不是射频前端,而是它那几十个寄存器、一堆校正流程和灵活的接口模式。你要是打算用 Zynq + PetaLinux 把它跑起来,核心问题只有一个:怎么让 Linux 正确识别它、初始化它、并把射频参数暴露给应用层。

我在这个项目里做的是一件很朴素的事:从零建一个 PetaLinux 工程,把 Xilinx 硬件导出的 XSA 导入进去,在内核里打开 AD9361 的 IIO 驱动,再通过设备树告诉内核“这颗 AD9361 挂在哪个 SPI 控制器下面、参考时钟是多少、用了几根线”。整个过程里真正花时间的不是敲命令,而是理解设备树里的每一个节点为什么这么写,以及驱动在初始化时到底做了哪些检查。

这篇文章就把整个过程完整拆开讲。你可能是刚接触 PetaLinux 的学生,也可能是第一次在板子上调 AD9361 的嵌入式工程师,只要照着文章里的思路走,基本能把工程搭起来。文章不会只贴命令,我会把“为什么要这么配置”“配置错了会看到什么现象”“应该怎么排查”都讲清楚,尤其是 0x247 一直读到 0x80、CP OVRG HIGH 被置位、RX PLL 不锁这几个常见问题,会让少走很多弯路。

1.1 AD9361 在 Linux 里的身份:一个 IIO 设备

先解决一个概念问题:AD9361 是什么?一颗射频收发器。它在 Linux 里是什么?一个 IIO 设备。

IIO 的全称是 Industrial I/O,是内核里专门管理传感器、ADC、DAC、射频收发器的一类子系统。你可以把它理解成“面向工业测量的通用输入输出框架”。AD9361 这种芯片本质上就是高速 ADC + DAC 再加一堆控制逻辑,所以放在 IIO 下面非常合适。

使用 IIO 驱动的好处是用户空间不用直接面对 SPI 寄存器。正常情况下,你会看到类似下面的路径:

/sys/bus/iio/devices/iio:device0/

这个目录下面有大量属性文件,比如in_voltage0_i_raw、in_voltage0_q_raw、out_voltage0_frequency、in_voltage_sampling_frequency等等。应用程序直接读写这些文件就能完成配置,不需要关心寄存器地址。这正是 IIO 设计的目的:把底层硬件的差异封装起来,把“配置射频参数”这件事变成“读写文件”。

AD9361 的驱动在 Linux 内核里的路径是drivers/iio/adc/ad9361.c,这个驱动在 ADI 维护的内核分支里做得比较完整,包含 SPI 初始化、时钟配置、射频前端校准、IIO 属性注册、触发器和 DMA 缓冲对接。PetaLinux 自带的内核不一定直接包含它,所以后面我会专门讲怎么确认这个驱动有没有被编译进去。

1.2 为什么选择 PetaLinux 做工程底座

在 Zynq 上跑 AD9361,常见方案就三种:裸机、自己交叉编译 Linux、用 PetaLinux。

裸机方案不是不行,很多产品验证阶段都在用裸机。问题是你得自己写 SPI 读写函数、自己实现芯片初始化脚本、自己处理中断和 DMA,更痛苦的是调试射频参数时没有一个像iio_info这么好用的工具。等你把代码写到一半,你会发现大量时间花在了“重复造轮子”上。

自己交叉编译 Linux 的好处是灵活,坏处是麻烦。内核版本、设备树、U-Boot、文件系统每一样都要手动维护,一个依赖对不上就够折腾一个星期。而且 Xilinx 的 Zynq 平台对 PetaLinux 的支持已经很成熟,从 Vivado 导出的 XSA 可以直接变成 PetaLinux 的硬件描述,整个流程是通的。

PetaLinux 本质上是一套基于 Yocto 的嵌入式 Linux 构建系统,它能把以下几样东西统一打包:

  • 第一级引导镜像 FSBL
  • 硬件比特流 FPGA bitstream
  • U-Boot
  • 内核 image
  • 设备树 DTB
  • 根文件系统 rootfs

这些组件最终会被打包成BOOT.BIN和image.ub两个文件,放到 SD 卡里就能启动。对于 AD9361 这种需要快速验证的项目来说,这套流程的效率非常高。后面所有配置,都是围绕这几个组件展开的。

1.3 整体工程结构预览

先看一下我实际使用的目录结构,让脑子里有个完整画面:

ad9361_linux/ ├── hardware/ │ └── system.xsa ├── software/ │ └── petalinux/ │ ├── project-spec/ │ ├── components/ │ └── images/ └── app/ └── test_iio.sh

system.xsa是 Vivado 里导出的硬件描述文件,里面包含了 Zynq 处理器配置、外设地址、时钟信息、管脚约束以及有没有加载比特流。PetaLinux 在构建时会读取这个文件,自动生成最基本的内核配置和设备树骨架。

project-spec是 PetaLinux 工程里最重要的目录,用户的自定义配置基本都集中在这里。设备树修改文件在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi,这是一个会被自动包含到最终 DTB 里的用户扩展文件,我们所有的 AD9361 节点都会写在这里。

app目录是我自己放测试脚本的地方,不属于 PetaLinux 标准结构,但建议留着,后面调试时会用到很多简单脚本。

2. PetaLinux 工程创建与基础配置

建工程这种事,第一次做会觉得命令多,第二次就会发现规律。PetaLinux 的常用流程可以概括成四步:安装环境、导入硬件、配置内核、编译打包。下面每一步我都会给出实际命令和容易踩的坑。

2.1 软硬件准备与 PetaLinux 安装

先说硬件。我手里的板子是 Zynq-7000 系列,外接了一块 AD-FMCOMMS2-EBZ 评估板,这是 ADI 官方的 AD9361 评估板,接口是 FMC。如果你用的是其他板卡,原理类似,重点确认三样东西:AD9361 的 SPI 接在哪个控制器上,参考时钟是哪个来源,GPIO 控制脚接到了哪个 MIO 或 EMIO。

软件方面,我用的是 Ubuntu 20.04,安装了 PetaLinux 2022.2。这里有一条非常重要的经验:PetaLinux 对主机系统的版本很挑剔,官方对每个版本都明确规定了支持的 Ubuntu 版本,不要拿最新的 Ubuntu 去安装老版本 PetaLinux,否则会在依赖检查那一步卡住。我后来总结了最好用的方式:

sudo mkdir -p /opt/Xilinx sudo chown $USER /opt/Xilinx ./petalinux-v2022.2-final-installer.run --dir /opt/Xilinx/PetaLinux/2022.2

安装完成后,每次打开终端都需要先 source 环境变量:

source /opt/Xilinx/PetaLinux/2022.2/settings.sh

这一步经常被忽略。如果你输入petalinux-create提示找不到命令,十有八九是没执行 source。

2.2 创建工程并导入硬件描述

准备好 XSA 文件后,创建工程很简单:

petalinux-create -t project --name ad9361_linux --template zynq cd ad9361_linux petalinux-config --get-hw-description ../hardware/system.xsa

这里有两个细节。

第一个细节:--template zynq不是必填的,但建议填。如果你用的是 Zynq UltraScale+,模板要写成zynqMP。模板的作用是提前帮你把处理器类型、默认启动方式等基础配置摆好,能省掉很多菜单里的人工选择。

第二个细节:老版本 PetaLinux 用的是.hdf文件,导入命令是petalinux-config --get-hw-description ../hardware/system.hdf。从 2020.1 之后 Xilinx 逐渐统一到.xsa,新工程基本都用 XSA。如果你在用老版本,注意区别。

执行petalinux-config后会出现一个 menuconfig 界面,这里可以配置设备树生成选项、U-Boot 选项、根文件系统位置等。第一次使用直接用默认配置启动就行,不需要在这里做太多改动。AD9361 相关的关键配置在内核和设备树里,不在这个界面里。

2.3 内核配置:确认 IIO 和 AD9361 驱动开启

导入硬件后,接着要进内核配置界面:

petalinux-config -c kernel

这个命令本质上是打开 Linux 内核的make menuconfig,只是它读的是 PetaLinux 的 Yocto 配置。菜单路径大概是:

Device Drivers -> Industrial I/O support -> ...

你需要确认以下几项被打开:

  • CONFIG_IIO必须为y
  • CONFIG_AD9361必须为y或m
  • 如果需要 DMA 数据流功能,还要确认CONFIG_AD9361_IIO_RATE_CTRL之类的相关选项

这里要特别提醒一句:AD9361 驱动并不在标准主线内核里,它主要在 ADI 的linux仓库、或者 Xilinx 的linux-xlnx分支中维护。如果你在 menuconfig 里怎么都搜不到AD9361,不要慌,大概率是当前内核源码不包含这个驱动。

解决方法是把内核切换到 ADI 分支,或者在 PetaLinux 的meta-user里给内核增加补丁。最简单的验证方法是先下载 ADI 的 Kuiper Linux 源码看看drivers/iio/adc/目录下有没有ad9361.c。如果有,就把这份源码作为内核源码源,或者直接把驱动文件加到 Xilinx 内核里重新编译。

还有一个更轻量的办法:如果你只是想先把工程跑通,可以先不用 DMA 数据流功能,只用 SPI 控制通道。这样即使CONFIG_AD9361暂时编译不过,也能先验证设备树和 SPI 通路是否正常。不过大多数场景下我们还是希望完整初始化 AD9361,所以建议从一开始就把驱动编译进去。

配置完内核后,直接保存退出,然后先做一次全量编译:

petalinux-build

这一步耗时取决于机器性能,第一次编译因为要构建工具链和基础包,可能需要半小时到一小时。别急着中断,如果编译中途报错,大多数都是软件包版本或者网络下载问题,后面会专门讲排查。

2.4 文件系统工具:libiio 和 iiod

设备树和内核驱动只解决了“内核能识别 AD9361”,真正调试时你还需要用户空间工具。最常用的是 ADI 的 libiio 库和它的命令行工具,包括iio_info、iio_readdev、iio_writedev、iiod。

在 PetaLinux 里启用这些工具很简单:

petalinux-config -c rootfs

进入菜单后找到Filesystem Packages或者user packages,把libiio和iiod选上。不同版本 PetaLinux 的菜单位置略有区别,但关键词都是libiio。特别提醒,iiod是服务器守护进程,远程调试时很有用。如果你只是本地调试,至少要把libiio和iio-utils选上,否则后面iio_info命令都找不到。

3. 设备树定制详解

设备树是 PetaLinux 工程里和 AD9361 关系最密切的部分。很多人在这个环节崩溃,因为一旦写错,内核可能探测不到设备,甚至启动卡住。下面从设备树在 PetaLinux 里的生效机制开始讲。

3.1 设备树在 PetaLinux 里的生效路径

PetaLinux 在构建时会根据 XSA 文件自动生成一套基础设备树。这套设备树覆盖了 Zynq PS 的所有外设,包括 SPI、GPIO、UART、I2C 等。我们不需要去改这些自动生成的文件,正确的做法是在system-user.dtsi里做增量修改。

在工程目录下找到这个文件:

project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi

文件内容初始状态很干净,只有基本的 model 和 compatible 描述。我们在这里override或extend自动生成设备树中的某个节点。比如自动生成的文件里定义了spi0节点,但状态是disabled,我们就可以在system-user.dtsi里写:

&spi0 { status = "okay"; };

这就是设备树最核心的用法:修改节点属性,而不是重写整棵树。这种增量修改方式最大好处是不会破坏 PetaLinux 自动生成的内容,构建时也不会因为硬件导出变化而产生巨大冲突。

改完设备树文件后,只需要重新执行:

petalinux-build

如果只改了设备树,PetaLinux 会增量编译 DTB,速度很快。最后再打包镜像即可。我见过很多人改完system-user.dtsi后没有重新petalinux-build,只重新打包镜像,结果看到的还是旧设备树,这一点要特别注意。

3.2 最小可用的 AD9361 设备树节点

下面是一个最小可用的 AD9361 设备树配置,我把它拆成三个部分来看。

第一部分,定义参考时钟:

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

AD9361 必须有一个外部参考时钟,通常来自板载晶振或者 FPGA 的时钟输出。设备树里用一个fixed-clock节点描述它,clock-frequency必须和硬件实际提供的频率一致。驱动在初始化时会读取这个频率,如果频率不匹配,PLL 和后续校正都会出问题。

第二部分,在 SPI 控制器下增加 AD9361 节点:

&spi0 { status = "okay"; num-cs = <1>; ad9361@0 { compatible = "adi,ad9361"; reg = <0>; spi-max-frequency = <16000000>; clocks = <&ad9361_clk>; clock-names = "ad9361_ext_refclk"; adi,spi-3wire-enable; }; };

先看compatible = "adi,ad9361",这一行是驱动和设备树匹配的关键。内核里 AD9361 驱动的of_device_id表里必须有同样的字符串,两者才能绑到一起。如果你的驱动是 ADI 的标准驱动,这个值就是adi,ad9361,不要自己改。

reg = <0>表示这个 SPI 设备连接在控制器的第 0 个片选上。如果你的 AD9361 接在 CS1,就要写reg = <1>,同时要检查 SPI 控制器节点里的cs-gpios是否正确。

spi-max-frequency建议先用 16MHz 甚至更低的频率。虽然 AD9361 理论上支持更高 SPI 时钟,但实际板卡走线、长排线、FMC 连接器质量都会影响信号完整性。我一开始按照参考设计写 20MHz,结果读取寄存器不稳定,降到 16MHz 后一切正常。

第三部分,如果 AD9361 的 GPIO 控制脚不接在默认位置,还要在节点里补上。常见的有使能、复位、同步三个 GPIO:

ad9361@0 { compatible = "adi,ad9361"; reg = <0>; spi-max-frequency = <16000000>; clocks = <&ad9361_clk>; clock-names = "ad9361_ext_refclk"; adi,spi-3wire-enable; enable-gpios = <&gpio0 86 0>; reset-gpios = <&gpio0 87 0>; sync-gpios = <&gpio0 88 0>; };

注意,GPIO 编号必须是芯片手册里实际的引脚编号,不是该引脚上的管脚名称。如果写错,驱动在复位或使能时就会操作到错误的 GPIO,表现往往是 AD9361 无响应、寄存器一直读成同一个数。建议先把 GPIO 管脚在 Vivado 里的编号确认一遍,再写进设备树。

3.3 时钟、SPI 模式与接口映射的关键细节

设备树里 AD9361 节点最让人头疼的不是语法,而是“时钟”和“SPI 模式”这两个细节。

先讲时钟。clock-names = "ad9361_ext_refclk"不是随便写的,它是驱动内部通过devm_clk_get(dev, "ad9361_ext_refclk")来获取时钟的名字。内核会拿着这个名字去设备树节点里的clocks属性里去查。名字不匹配的话,clk_get会返回错误,驱动 probe 直接失败。很多人在 dmesg 里看到:

ad9361 spi0.0: failed to get clock

就是因为clock-names写错了,或者根本忘了写。

再讲 SPI 模式。AD9361 的 SPI 支持标准 4 线模式和 3 线半双工模式。如果你的硬件上 MISO 和 MOSI 是复用的,就必须在设备树里加上adi,spi-3wire-enable。如果硬件是 4 线,却加了这个属性,驱动会切换到半双工模式,读写时序全乱,表现出来就是寄存器读取值不对。反过来,如果是 3 线硬件却忘了加,驱动读不到有效数据。

如果你不确定自己的硬件是几线 SPI,最直接的办法是看原理图:AD9361 的 SDI 和 SDO 是否走同一条线。AD-FMCOMMS2 评估板默认是 3 线模式,所以参考设计里通常带着adi,spi-3wire-enable。

还有一个细节是 SPI 时钟极性。Xilinx 的 SPI 控制器在驱动 AD9361 时,一般通过驱动里的spi->mode设置。设备树层面通常不需要额外指定,但如果发现 SPI 时序不对,可以在设备树里加spi-cpol、spi-cpha或者在驱动里强制设置模式。我自己的经验是,优先相信驱动里的默认值,只有用示波器确认时序确实不对时才去改。

如果整套硬件里还包含 ADI 官方的 HDL 参考设计,比如axi-ad9361-rx和axi-ad9361-txIP,那还需要额外配置这两个 AXI 节点。它们负责把 AD9361 的数字数据接口接到 DDR,通过 DMA 搬运数据。节点里主要是注册地址和 DMA 通道引用:

axi_ad9361_rx: axi-ad9361-rx@79020000 { compatible = "adi,axi-ad9361-rx"; reg = <0x79020000 0x1000>; dmas = <&rx_dma 0>; }; axi_ad9361_tx: axi-ad9361-tx@79024000 { compatible = "adi,axi-ad9361-tx"; reg = <0x79024000 0x1000>; dmas = <&tx_dma 0>; };

如果这部分漏配,IRQ 或 DMA 请求通道会缺失,应用层用iio_readdev时能打开设备但读不到数据。这不是 AD9361 本身的问题,而是数据通路的问题。

3.4 设备树修改后如何验证和编译

设备树写完后,不要急着打包整机镜像。可以先用petalinux-build单独编译设备树,查看是否有语法错误。PetaLinux 的构建输出里会有 DTC 编译日志,如果设备树语法有问题,会提示类似:

Error: system-user.dtsi:39.1-5 syntax error

遇到这类错误,基本就是少了括号、漏了分号、或者引号没有闭合。设备树语法非常严格,一个分号错了整棵树都编不过。

验证编译成功后,还可以直接把生成的 DTB 拿到 Linux 环境下反编译检查:

dtc -I dtb -O dts -o output.dts system.dtb

这样能直观看到 PetaLinux 最终生成的设备树是什么样子,确认我们的节点有没有被包含进去。这个小技巧非常实用,能避免“我明明改了文件,系统里却不是这样”的尴尬。

4. 核心环节:AD9361 驱动与 IIO 属性映射

设备树配置正确、内核编译通过、镜像烧到 SD 卡启动之后,真正有趣的部分才开始:看驱动是否成功探测到 AD9361,以及怎样通过 IIO 接口控制它。

4.1 内核驱动的 probe 流程

AD9361 驱动在设备树匹配成功后,会执行ad9361_probe。这个函数做的事情很多,但核心逻辑可以概括成五步:

第一步,获取资源。驱动会从设备树中读取 SPI 控制器、时钟、GPIO 等信息。如果某个资源不存在,比如时钟节点丢了,probe 会直接返回错误。

第二步,复位芯片。驱动会把reset-gpios拉低再释放,保证 AD9361 处于一个确定的初始状态。很多“寄存器读出来一直不变”的问题,其实就是复位没做干净。

第三步,读取芯片版本。驱动会通过 SPI 读取 AD9361 的版本寄存器,确认芯片存在并且版本支持。这一步如果失败,通常说明 SPI 通路有问题,而不是驱动问题。

第四步,执行初始化。驱动会写大量默认寄存器,做 RF 前端和基带部分的初始化,包括滤波器配置、增益表配置、时钟分频等。这里的很多寄存器配置来自 ADI 提供的配置文件。

第五步,注册 IIO 设备。初始化成功后,驱动会调用iio_device_register,在/sys/bus/iio/devices/下面创建对应的设备节点。

所以你在 dmesg 里看到的信息,一般是这样的顺序:

[ 1.324165] ad9361 spi0.0: ad9361_probe: enter [ 1.328795] ad9361 spi0.0: ad9361_probe: AD9361 Rev. 2 [ 1.334503] ad9361 spi0.0: ad9361_init: start [ 1.340100] ad9361 spi0.0: ad9361_calib: start

如果 dmesg 里只看到 probe enter,后面没有下一步,就说明驱动卡在了某个环节。最常见的是 SPI 读取失败、时钟获取失败、或者 GPIO 复位没完成。

4.2 IIO 设备和属性文件怎么读

驱动注册成功后,我们可以在系统里查看 IIO 设备:

ls /sys/bus/iio/devices/

正常情况下应该能看到iio:device0之类的目录。如果你还想确认这个设备到底是谁,执行:

readlink /sys/bus/iio/devices/iio:device0

返回的路径里会包含驱动名称。如果是 AD9361 驱动,路径里会出现driver指向ad9361。

进入设备目录后,能看到大量文件。我随便列几个常见的:

in_voltage0_i_raw in_voltage0_q_raw in_voltage1_i_raw in_voltage1_q_raw out_voltage0_i_raw out_voltage0_q_raw in_voltage_sampling_frequency out_voltage_sampling_frequency in_voltage_rf_port_select out_voltage_rf_port_select

这些文件并不是普通寄存器,而是 IIO 抽象出来的属性。比如想要把接收频率调到 2.4GHz,可能操作的是:

echo 2400000000 > /sys/bus/iio/devices/iio:device0/in_voltage0_frequency

想要设置采样率:

echo 20000000 > /sys/bus/iio/devices/iio:device0/in_voltage_sampling_frequency

这个过程很像是在操作一个“虚拟设备的寄存器”:你写的是人话,驱动负责翻译成 AD9361 的真正寄存器操作,并处理所有依赖关系,比如修改本地振荡器频率后自动触发 VCO 校准。这正是 IIO 框架的巨大价值。

如果你装了 libiio,也可以直接用iio_info查看:

iio_info -a

这个命令会列出所有 IIO 属性,远比直接ls目录清晰。

4.3 通过 debugfs 直接访问寄存器

遇到驱动初始化问题,光看 IIO 属性是不够的,我们会想直接读写 AD9361 的寄存器。IIO 驱动通常会在 debugfs 里开放一个direct_reg_access接口。

挂载 debugfs:

mount -t debugfs none /sys/kernel/debug

然后查看:

cat /sys/kernel/debug/iio/iio:device0/direct_reg_access

读写格式通常是这样:

echo 0x247 0x00 > /sys/kernel/debug/iio/iio:device0/direct_reg_access

这里第一个数是寄存器地址,第二个数是要写入的数据。如果不写第二个数,只写寄存器地址,驱动会返回当前寄存器值。这个接口在调试时特别有用。比如你可以先读一下 0x247,看它是不是一直 0x80;然后再尝试写一些校准相关的寄存器,观察状态位有没有变化。

不过注意,direct_reg_access不是标准内核 API,不同版本驱动实现可能不同。如果你在 debugfs 下找不到这个文件,说明当前内核没有启用CONFIG_IIO_DEBUGFS,需要重新配置内核并开启它。

4.4 从寄存器到工程实践:一个简单调频脚本

当 IIO 设备正常工作后,你很容易就能写一个 shell 脚本快速验证 AD9361。比如:

#!/bin/bash DEV=/sys/bus/iio/devices/iio:device0 echo 2400000000 > $DEV/in_voltage0_frequency echo 20000000 > $DEV/in_voltage_sampling_frequency echo "RX_LO: $(cat $DEV/in_voltage0_frequency)" echo "Sample rate: $(cat $DEV/in_voltage_sampling_frequency)"

执行后如果能正常打印出设置的值,说明 AD9361 的 SPI、时钟、驱动三方已经协同工作了。此时再去做数据流采集,你会更有底气,因为问题不会出在最底层的控制通路上。

5. 常见问题与排查技巧实录

这部分是我最想写的。AD9361 + PetaLinux 真正的坑不在构建工程,而在启动之后那些“似懂非懂”的报错和异常现象。

5.1 0x247 寄存器一直读出 0x80,到底是谁的问题

这个话题在社区里被问过无数次。板上 AD9361 初始化,读到 0x247 这个寄存器,无论怎么操作都是 0x80。很多人第一反应是 SPI 读时序有问题,其实不完全是。

首先要明确 0x80 这个值的含义。二进制看是1000_0000,最高位是 1。在你的芯片数据手册里,0x247 属于状态寄存器组,它的 bit7 往往是一个“告警或状态标志位”。也就是说,0x80 不一定是 SPI 乱码,而可能是在告诉你某个 PLL 或校准环节不在正常状态。

那为什么一直读到 0x80?排查顺序我建议是:SPI 时序 -> 参考时钟 -> 供电电压 -> 复位序列。

SPI 时序是最容易排除的。用示波器或逻辑分析仪抓 SPI 读操作,看读命令发出后,MISO 上返回的数据是否稳定。如果你发现 MOSI 上的地址字节和你想读的地址不一样,那多半是 SPI 模式、3 线/4 线配置、或者spi-max-frequency太高导致的问题。临时把spi-max-frequency降到 2MHz 再试一次,如果 0x247 的值开始变化了,说明就是 SPI 信号完整性问题。

参考时钟排在第二位。AD9361 的一切内部节点都和参考时钟有关。如果参考时钟没起振、频率不对、或者时钟幅度太小,VCO 不可能锁定,状态寄存器里就会一直挂着未锁定的标志。对这种状态位,0x80 就成了一种“永久默认值”。用示波器在时钟引脚上量一下,有波形、频率正确,这一步就过了。

供电电压也很关键。AD9361 是多电源域芯片,DVDD、AVDD、VDD_SNLS、VDD_DIG 等等。任何一路电源如果电压不够,内部模块就可能处于异常状态。我遇到过一种情况:AVDD 电压只有 1.29V,规格是 1.3V,结果校准一直失败,寄存器状态也是固定的异常值。把电源调准后,一切恢复正常。

最后是复位序列。驱动在 probe 时应该正确产生一个复位脉冲。如果复位的 GPIO 写错,芯片可能根本没有被真正复位,寄存器读出来的全是上电后的随机状态。你可以在驱动 probe 前后用示波器抓 reset 脚,确认有低脉冲出现。

5.2 初始化超时:CP OVRG HIGH 被置位,RX PLL 没有锁定

有人的现象比 0x247 更直接:驱动在初始化某个步骤时卡住,日志里出现类似cp ovrg high的提示,或者直接告诉你 RX PLL 没有锁定。

cp ovrg high本质上是电荷泵过载溢出的标志。你可以把它理解成一个限位报警:压控振荡器的调谐电压已经顶到边界,但 PLL 还是没能把频率锁到期望值。出现这个标志,常见原因有三个。

第一,参考时钟频率和寄存器配置不一致。比如硬件给的是 20MHz,但寄存器配置的是 40MHz,PLL 分频链路完全错位,怎么锁都锁不上。

第二,PLL 的电荷泵电流设置不合理。这通常不是设备树问题,而是驱动初始化脚本里的参数问题,或者你在配置工具里生成的初始化文件不对。AD9361 对电荷泵电流、环路滤波器参数非常敏感,建议使用 ADI 提供的 filter design 工具生成配置,而不是自己瞎调。

第三,射频输入或输出端硬件有问题。如果 VCO 输出匹配网络有问题,或者 ADC/DAC 时钟链路断开,也会导致 PLL 无法锁定。

排查这类问题我的经验是:先把配置回到官方参考工程。ADI 官方示例里通常会给一组“最保守”的初始化参数,比如固定 LO=2.4GHz、带宽=20MHz。先用这组参数跑,确认基本通路依赖是可用的,再逐步改成目标参数。很多“初始化超时”莫名其妙就消失了。

如果你在用非官方板卡,还要检查 AD9361 的 SW 控制脚、ENSMM 和 ENSMB 管脚。这些管脚决定 AD9361 处于 TDD 还是 FDD 模式,如果硬件上拉下拉和设备树或驱动配置不一致,芯片状态机也会卡住。

5.3 设备树相关高频问题速查

设备树写多了,你会发现大部分坑就集中在几个点。我把工作中遇到的典型问题整理成了一张表。

现象可能原因排查方法
驱动没有 probecompatible 拼写错误或节点挂在错误总线检查dmesg中是否有of_device_id匹配失败
probe 提示 failed to get clockclock-names不匹配或时钟节点缺失对照驱动源码里的devm_clk_get参数检查
SPI 读取值固定不变3 线/4 线模式配置错误,或片选信号没拉低抓 SPI 波形,确认每次传输都有有效 CS 有效脉冲
自定义节点没生效修改了自动生成的 dts 而不是system-user.dtsi用 dtc 反编译最终 DTB 查看
启动卡在 U-Boot 或内核早期GPIO 冲突、某个设备树节点中断配置错误打开 earlycon 串口日志,缩小到具体节点
RX/TX 设备节点没出现缺少axi-ad9361-rx、axi-ad9361-tx节点检查 AXI 地址和 DMA 通道是否正确

尤其要注意compatible字符串。这个字符串必须和驱动源码里的完全一致,差一个字符都不行。我曾见过有人写成adi,AD9361,大写字母和驱动不匹配,结果驱动完全没有被加载。这种问题在 dmesg 里不会有明显的错误,只有当你用ls /sys/bus/spi/devices/时发现设备没绑到驱动,才会意识到是字符串大小写问题。

另一个常见坑是多个 SPI 设备共用总线。如果板子上还有其他 SPI 设备,比如 ADC 或 DAC,检查reg片选号是否冲突。两个设备如果都是reg = <0>,它们会在同一个 CS 上打架,读取数据互相干扰。解决方法是分配独立片选,或者在硬件上保证每次只选中一个。

5.4 调试工具组合拳

遇到问题不要盲猜,我常用的排查链路是下面这套。

第一步,看内核日志:

dmesg | grep -i ad9361 dmesg | grep -i iio dmesg | grep -i spi

第二步,看设备树实际生效内容:

cat /proc/device-tree/axi/spi@e0006000/ad9361@0/compatible

第三步,看 SPI 设备是否绑定:

ls -l /sys/bus/spi/devices/ cat /sys/bus/spi/devices/spi0.0/driver_override

第四步,用 debugfs 直接读写寄存器:

mount -t debugfs none /sys/kernel/debug echo 0x247 > /sys/kernel/debug/iio/iio:device0/direct_reg_access cat /sys/kernel/debug/iio/iio:device0/direct_reg_access

第五步,用 iio 工具检查属性:

iio_info -a iio_attr -a -d

这套组合拳基本能把问题定位到三层:驱动层、设备树层、硬件层。多数情况下,问题都能在第三步和第四步显现出来。

最后再分享一个调试顺序

文章写到这里,其实已经覆盖了从 PetaLinux 工程创建到 AD9361 驱动上线的完整流程。最后我想分享自己的一个调试习惯:永远先验证“最基础的链路”,再去想复杂的问题。

在我这个项目里,所谓最基础链路就是“SPI 能稳定读写一个已知寄存器”。在把 AD9361 驱动跑起来之前,我会先用驱动里的 debugfs 接口或者一个极简 SPI 应用去读 AD9361 的版本寄存器。如果这个寄存器能读到预期值,说明 SPI 硬件、时钟、设备树节点、内核 SPI 驱动全部正常,后续所有初始化问题都可以放心地往 AD9361 配置参数和电源方面查。如果这个寄存器都读不对,就别急着折腾校准和 PLL,问题一定在底层。

第二个经验是,设备树修改不要一步登天。我见过很多人在第一次就把全套参考设备树拷贝过来,结果启动后一堆报错,完全不知道从哪查起。正确做法是,先只加最小节点,让驱动能 probe 成功,再逐步增加 GPIO、DMA、AXI 数据流节点。每加一个节点,启动一次,验证一次。虽然过程慢一点,但每次出现新问题,都能明确知道是“刚加的这部分”引起的,排查范围会小很多。

AD9361 这颗芯片功能很强,但它的脾气也很大。给它一个稳定的时钟、一组正确的 SPI 时序、一个干净的复位,它就会老老实实地按 IIO 属性工作。反过来,任何一环疏忽,都会在 0x247 这类状态寄存器里显露出来。希望这篇文章能帮你把 PetaLinux 工程配置和设备树定制这条路走顺,少熬夜,少烧香。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 1:13:23

FAST-LIO在ROS2中的实战部署:从算法原理到参数调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:13:04

威尔金森功分器设计实战:1.1~1.6GHz频段从理论到实测调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:12:32

无感BLDC六步方波驱动实战:反电动势过零检测与换相优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:12:30

I2C多主机仲裁与时钟延展:从开漏输出到实战调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 1:11:54

Linux下用mlabel高效管理FAT卷标:U盘/SD卡改名实战

说到Linux下的磁盘管理&#xff0c;lsblk、fdisk、df这些命令大家都很熟&#xff0c;但今天要聊的mlabel&#xff0c;是个很容易被忽略、可又特别实用的冷门工具。它是mtools工具集的一员&#xff0c;专门用来查看或修改FAT文件系统&#xff08;FAT12/16/32&#xff09;的卷标&…

作者头像 李华
网站建设 2026/9/28 1:11:52

LM Studio与Ollama深度对比:本地大模型部署工具怎么选

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华