
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 bitstreamU-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.shsystem.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必须为yCONFIG_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; ad93610 { 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 控制脚不接在默认位置还要在节点里补上。常见的有使能、复位、同步三个 GPIOad93610 { 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-rx79020000 { compatible adi,axi-ad9361-rx; reg 0x79020000 0x1000; dmas rx_dma 0; }; axi_ad9361_tx: axi-ad9361-tx79024000 { 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接口。挂载 debugfsmount -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但寄存器配置的是 40MHzPLL 分频链路完全错位怎么锁都锁不上。第二PLL 的电荷泵电流设置不合理。这通常不是设备树问题而是驱动初始化脚本里的参数问题或者你在配置工具里生成的初始化文件不对。AD9361 对电荷泵电流、环路滤波器参数非常敏感建议使用 ADI 提供的 filter design 工具生成配置而不是自己瞎调。第三射频输入或输出端硬件有问题。如果 VCO 输出匹配网络有问题或者 ADC/DAC 时钟链路断开也会导致 PLL 无法锁定。排查这类问题我的经验是先把配置回到官方参考工程。ADI 官方示例里通常会给一组“最保守”的初始化参数比如固定 LO2.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/spie0006000/ad93610/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 工程配置和设备树定制这条路走顺少熬夜少烧香。