ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Zynq+AD9361基于PetaLinux的IIO驱动调试与初始化故障排查

Zynq+AD9361基于PetaLinux的IIO驱动调试与初始化故障排查 做软件无线电的朋友十有八九绕不开AD9361这颗芯片。70MHz到6GHz的覆盖范围、最大56MHz的接收带宽混频、滤波、增益控制、双通道ADC/DAC统统集成在一颗芯片里在Zynq平台上通过PetaLinux构建Linux环境、用IIO驱动框架把射频链路跑通已经是目前SDR原型验证最主流的一条技术路线。但这套流程文档虽然多真上手之后会撞上各种刁钻问题驱动静默加载失败、SPI读回全F、初始化时0x247寄存器固定读出0x80、CP_OVRG被置位导致RX PLL不锁定任何一个都能卡住你好几天。这篇文章把我从创建PetaLinux工程、定制AD9361设备树到完整跑通IIO驱动的过程拆开讲重点分析那些卡过我脖子的初始化异常适合准备拿ZynqAD9361做原型验证或者板子已经在手边但驱动一直调不通的朋友参考。1. 项目整体设计思路与方案选型1.1 为什么是Zynq PetaLinux AD9361这个组合我第一次接触AD9361的时候也纠结过明明STM32也能通过SPI配置芯片寄存器为什么业界几乎清一色用Zynq原因有三层。第一AD9361是一个宽带零中频收发器它需要高带宽、低延迟的数据通路。芯片输出的双通道12位ADC数据速率为40MSPS时就接近百兆字节每秒普通MCU根本搬不动。Zynq的PL部分可以高速接管这个数据流做DDC、DUC、自定义调制解调然后再把处理结果交给PS端的Linux做上层协议。第二Zynq的PS端是一颗双核ARM Cortex-A9可以完整跑Linux。射频前端的控制和管理在Linux里非常方便网络接口、文件系统、调试工具链全是现成的。第三PetaLinux是Xilinx官方的嵌入式Linux构建工具。Zynq的启动过程很繁琐——FSBL、U-Boot、内核、设备树、根文件系统每个环节都有大量配置。手动交叉编译内核和U-Boot不是不行但每次改一个设备树都要重新走一遍流程效率很低。PetaLinux把这些步骤封装成了工程化管理方式硬件描述文件导进来整体构建镜像打包一条龙对搞射频的人来说确实省心。1.2 为什么选用IIO子系统而不是自写字符设备驱动Linux内核里给设备驱动分了门别类网络设备走netdev输入设备走input而AD9361这种数据采集数据转换器件最合适的就是IIOIndustrial I/O子系统。IIO的设计初衷就是为传感器、ADC、DAC这类设备提供统一抽象。它把设备的能力拆成“通道”和“属性”比如AD9361的两个接收通道、两个发射通道各自有频率、采样率、增益、带宽这些属性通过sysfs接口就能读写。用户层还有libiio库封装了更友好的API并且支持本地调用和网络调用两种方式。这意味着你可以在宿主机上通过网口直接操作目标板上的AD9361这对调试射频链路来说非常方便。如果自己写一个裸的字符设备驱动行不行当然行但你需要自己处理设备节点分配、IOCTL接口、并发访问、中断处理而且用户层还得专门写配套工具。IIO把这些基础设施都做好了ADI官方还提供了libiio、iio-oscilloscope这些现成工具没必要重复造轮子。1.3 AD9361 Linux驱动在软件栈中的位置AD9361的驱动代码并没有合入Linux内核主线而是由ADI维护在自己的内核分支中代码路径在drivers/iio/adc/ad9361.c。这颗驱动挂在SPI总线上通过SPI接口配置芯片寄存器的同时注册成IIO设备。它跟普通的SPI设备驱动还有个区别AD9361驱动内部还要管理两个PLL链路RX和TX、VCO校准、滤波器配置、增益表等初始化逻辑非常长而且对时序敏感。用户空间工具链则是libiio及其配套程序。当你运行iio_info时它会通过sysfs或网络访问IIO设备列出所有通道和属性。这套架构的好处是隔离性很好驱动层只负责“把硬件配置正确”用户层负责“怎么用硬件”。需要注意的是设备树下发的属性必须和驱动期望的属性严格匹配比如compatible adi,ad9361这句字符串就是驱动和设备树之间的约定。一旦写错或漏掉内核就找不到匹配的驱动probe函数根本不会执行。我见过很多“驱动没反应”的问题最后查下来都是设备树节点格式有问题而不是驱动代码真的挂了。2. PetaLinux工程配置与内核定制2.1 安装PetaLinux前的环境准备PetaLinux对宿主机环境要求挺严格版本之间差异也大。我用的是PetaLinux 2022.2搭配Ubuntu 20.04。如果你用的是Ubuntu 22.04或更新的系统建议优先选PetaLinux 2023.2以后的版本避免遇到shell兼容性问题。安装步骤本身不复杂但有几个细节必须提前处理安装编译依赖。PetaLinux安装器只是解压工具链和配置脚本真正编译内核时需要gcc、make、libncurses5-dev、libssl-dev、flex、bison等一堆工具。建议先把所有基础编译工具装上sudo apt install gcc make build-essential libncurses5-dev libssl-dev \ flex bison u-boot-tools dosfstools mtools python3 python3-pip下载安装器并执行chmod x petalinux-v2022.2-10141622-installer.run ./petalinux-v2022.2-10141622-installer.run /opt/pkg/petalinux安装路径强烈建议放在根目录下的非中文、无空格路径。我曾经把工具链装在一个带空格的目录里后面编译时一堆shell脚本报路径错误排查了很久才醒悟。每次开新终端使用前都要先source环境变量source /opt/pkg/petalinux/settings.sh2.2 创建工程并导入Vivado硬件描述PetaLinux的工程创建、配置、编译都在命令行下完成。先说创建工程petalinux-create --type project --template zynq --name ad9361_sdr cd ad9361_sdr模板参数要注意如果你的平台是Zynq UltraScale MPSoC模板就换zynqMP如果只是Zynq-7000用zynq。硬件信息来自Vivado工程导出的XSA文件。在Vivado里完成Block Design搭建后File - Export Hardware把XSA文件导出然后在PetaLinux里导入petalinux-config --get-hw-description./hw/执行后会自动解析XSA并打开配置界面。这里我建议把根文件系统暂时选为initramfs方便初期快速启动验证等驱动跑通后再切到SD卡方式。配置界面里Subsystem AUTO Hardware Settings - Flash settings是启动方式等参数也可以先不管。2.3 内核菜单里使能AD9361驱动这一步是重点。在PetaLinux工程目录下执行petalinux-config -c kernel进入内核配置菜单后按/搜索AD9361我用的PetaLinux版本里它位于Device Drivers - Industrial I/O support - Analog to digital converters - Analog Devices AD9361这里有个容易踩坑的地方如果内核源码不是从ADI仓库同步过来的菜单里可能根本找不到AD9361。PetaLinux自带的内核仓库一般会包含Xilinx平台补丁但AD9361驱动有时被打包为模块或者隐藏在别的菜单层级。为了稳妥我建议在配置界面搜索关键字确认CONFIG_AD9361是否变成了y或m。如果找不到说明这个内核源版本不包含ADI驱动后续需要手动将ADI内核分支的相关补丁合入这个后面可以单独写一篇这里先按下不表。同一时间也要检查SPI和IIO相关依赖是否开启。AD9361驱动依赖CONFIG_SPI和CONFIG_IIO这两个通常在Zynq平台默认开启但值得确认一下。配置完成后保存退出回到工程目录。2.4 用户空间libiio工具的集成方式AD9361只是驱动层调试时我们还需要libiio工具链。PetaLinux的rootfs配方里可以集成这些工具配置路径是petalinux-config在菜单中找到Image Packaging Configuration - Root filesystem type如果希望直接可用可以把libiio、iio-oscilloscope加到rootfs中。不过要注意这些包在PetaLinux的默认包源里不一定都有。我个人的做法是初期先保持最小rootfs启动后用交叉编译的方式把libiio和常用工具编译好直接拷到板子的/tmp目录。这样不影响rootfs镜像迭代起来更快。如果实在想碰运气在菜单里找libiio选项可以用/搜索libiio看到有相关条目就直接勾上能少编译一次就少一次。3. AD9361设备树定制详解3.1 设备树在Zynq平台的工作方式设备树用文本文件描述硬件拓扑告诉内核“这块板子上有什么设备、挂在哪个总线、需要哪些参数”。ARM平台不像x86那样可以通过PCIe或ACPI枚举设备所以几乎所有外设都必须先在设备树里声明驱动才能找到它。在PetaLinux工程中用户自定义的设备树内容通常写在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi这个文件会被最终的设备树编译流程包含进去。有一点要注意不要在system-user.dtsi里重新定义整个SPI控制器节点而应该通过spi0的方式引用并扩展它这样不会覆盖原有配置。3.2 AD9361节点完整配置逐项说明下面是我项目里一份经过验证可用的AD9361设备树节点放在system-user.dtsi中/include/ system-conf.dtsi spi0 { status okay; num-cs 1; is-decoded-cs 0; ad93610 { compatible adi,ad9361; reg 0; spi-max-frequency 10000000; clocks ad9361_clk; clock-names ad9361_ext_refclk; clock-frequency /bits/ 64 40000000; adi,rx-synthesizer-frequency-hz /bits/ 64 2400000000; adi,tx-synthesizer-frequency-hz /bits/ 64 2400000000; adi,rx-channel-bandwidth /bits/ 64 18432000; adi,tx-channel-bandwidth /bits/ 64 18432000; adi,rx-rf-port-input-select 0; /* RX1A_N */ adi,tx-rf-port-input-select 0; /* TX1A_N */ adi,rx-gain-type 1; /* slow attack AGC */ adi,tx-gain-type 0; /* manual gain */ adi,tdd-use-tx-rx-swap-enable; adi,tdd-use-rx-tx-swap-enable; }; }; clkc { fclk-enable 0xf; ps-clk-frequency 33333333; };这里逐条解释每个属性的含义和坑点。compatible adi,ad9361是驱动匹配的关键必须和ad9361.c中of_device_id表的字符串完全一致。如果这个字符串写错后面的一切都不会发生。reg 0表示它挂在SPI控制器的片选0上对应硬件连接。spi-max-frequency我设为10MHzAD9361的SPI接口支持更高频率但布线差的时候提高频率会导致时序不稳定建议前期先保守一点。clocks和clock-names指引用的参考时钟。AD9361需要外部提供一个参考时钟设备树里必须声明这个时钟源。clock-frequency /bits/ 64 40000000是参考时钟频率我用的是40MHz有源晶振。在设备树里AD9361很多频率属性是64位整数所以必须写上/bits/ 64前缀。这个语法非常容易踩坑漏掉后内核会按32位解析数值不对驱动初始化就可能计算出错误的本振频率。adi,rx-synthesizer-frequency-hz和adi,tx-synthesizer-frequency-hz是上电时默认的接收、发射本振频率。2.4GHz是我这个工程的目标频点。注意这只是“初始频率”运行后完全可以通过libiio动态改。adi,rx-channel-bandwidth和adi,tx-channel-bandwidth是模拟滤波器的带宽设置18.432MHz是我根据CDMA2000信号带宽选的一般要小于ADC采样率的一半。这里如果设置得太高带外信号会混叠进来设置得太低有用信号会被切掉。adi,rx-rf-port-input-select和adi,tx-rf-port-input-select选择RF端口。AD9361有RX1A/1B/2A/2B、TX1A/1B/2A/2B多组端口硬件上接的是哪一组这里就必须选哪一组。0一般对应A端口具体要查驱动源码里的宏定义。adi,rx-gain-type和adi,tx-gain-type是增益控制模式。接收端我选了slow attack AGC发射端用手动增益。如果接收端选manual但又是大动态信号很容易削波反过来如果选慢速AGC在快速跳频场景下增益调整又跟不上。这里没有绝对的对错看场景。3.3 参考时钟、BBPLL与LO频率的约束关系AD9361内部有三个重要的PLLBBPLL、RX PLL、TX PLL。BBPLL的输入就是外部参考时钟经过内部倍频后给数字电路用。驱动初始化时会检查BBPLL锁定状态如果参考时钟异常整个初始化流程就卡住了。设备树里clock-frequency必须和实际硬件参考时钟完全一致。比如晶振是40MHz设备树里写了40M没问题但如果晶振实际是38.4MHz设备树里写成40M驱动会按错误的值去计算倍频系数BBPLL最终的锁定频率就会偏最后表现就是你读0x247寄存器得到的值不正常。RX/TX PLL的关系更直接目标LO频率越低VCO分频比越大目标LO频率越高VCO分频比越小。AD9361的VCO工作范围大致在6GHz到8GHz之间具体看版本所以LO频率是1.5GHz时VCO频率是6GHz分频系数是4LO频率是2.4GHz时VCO可能是7.2GHz分频系数是3。如果在设备树里配置的LO频率超出了VCO范围PLL肯定锁不住。改LO频率的正确方式是在libiio里设置比如iio_attr -c ad9361-phy RX_LO 2000000000改完驱动会自动重新做VCO校准。这个过程一般在几十毫秒到几百毫秒之间如果靠下设备树再重启来换频率效率太低。3.4 多芯片同步场景下的设备树扩展如果你做相控阵或多通道系统需要多片AD9361同步工作。我最初也想用两片AD9361做分集接收但同步问题比想象中麻烦。设备树里需要额外配置adi,multichip-sync-en、同步引脚等属性同时硬件上要把SYNC、SYSREF这些信号连好。从实践经验看多片同步的难点不在设备树而在时钟源和延迟校准。设备树能做的只是把使能开关打开真正的同步算法要结合FPGA逻辑来实现。如果你现在只需要单片跑通建议先不碰这些属性减少变量。3.5 设备树编译与快速验证在PetaLinux里修改完system-user.dtsi之后不需要全量编译可以增量执行petalinux-build -c device-tree petalinux-build -c kernel编译完成后生成新的image.ub。把根文件系统做成initramfs的话整个运行环境都在镜像里烧写SD卡或通过tftp加载都很方便。有条件的建议把设备树编译成dtbo在U-Boot阶段用overlay方式动态加载。这样可以在不重启内核的情况下反复试验不同设备树参数。具体做法是在U-Boot环境变量里增加setenv fdtoverlays /boot/ad9361-overlay.dtbo saveenv然后手动编译overlaydtc - -I dts -O dtb -o ad9361-overlay.dtbo ad9361-overlay.dts我习惯先用dtc在宿主机上验证语法确认没有语法错误再放到板子上可以省掉很多半路重启的麻烦。4. 驱动运行验证与经典初始化故障排查4.1 正常工作时应该看到什么现象驱动成功probe之后启动日志里会看到类似这样的信息ad9361 spi0.0: ad9361_probe: AD9361 Rev. 3 successfully initialized可以用dmesg | grep ad9361确认。然后检查IIO设备节点ls /dev/iio:device* iio_infoiio_info会列出所有IIO设备和通道。AD9361 phy设备上能看到out_altvoltage0_RX_LO_frequency、out_altvoltage1_TX_LO_frequency、in_voltage0_raw等属性。用libiio自带的小工具直接读寄存器iio_reg ad9361-phy 0x002AD9361的device ID寄存器地址0x002正常返回0x03。这个检查非常重要它从侧面验证了SPI总线是通的。如果上述现象都正常再用iio_oscilloscope去看IQ波形就说明驱动链路完全通了。4.2 0x247寄存器一直读0x80的深层原因这个问题在热搜词里反复出现我调第一块板子时也撞上了。现象是驱动初始化卡在某个等待PLL锁定的循环里手动去读0x247寄存器无论怎么读都是0x80。0x247是AD9361内部时钟/PLL状态相关的寄存器0x80这个值对应的状态位表示PLL锁定检测异常可以理解为芯片认为自己的参考时钟链路没有达到正常锁定条件。它的直接原因通常是BBPLL参考时钟没起振或频率不对但背后可能有这几个具体场景第一外部参考时钟根本没有送进来。有时候硬件上参考时钟接到了FPGA的某个bankFPGA bitstream没加载时钟自然过不来。检查方法是用示波器直接量AD9361的EXT_REF_CLK引脚确认有稳定的时钟信号。第二参考时钟幅度不够。AD9361对参考时钟的输入幅度有要求一般来说需要保证一定的电平摆幅常见范围是0.8Vpp到1.6Vpp之间最大不能超过2Vpp最小不能低于0.4Vpp左右具体以手册为准。若时钟源驱动能力不足或串了过大电阻衰减芯片内部检测不到有效沿BBPLL就锁不定。我遇到的一个案例是时钟源输出直接接了一个1k电阻做阻抗匹配结果压降太大换成了50欧姆匹配后问题消失。第三SPI时序本身不对导致读到的0x80其实是总线上的毛刺。如果SPI时钟极性、相位配错寄存器读回的数据就是乱的。设备树里的spi-max-frequency如果设得太高加上飞线太长回读数据也容易出错。排查方式就是用逻辑分析仪抓SPI波形确认读写时序和寄存器地址是不是自己想要的。第四寄存器0x247前面某一步配置出错导致PLL进入了错误状态。AD9361的配置有严格的顺序依赖驱动已经处理了大部分但如果你用iio_reg手动改过某些寄存器可能会破坏状态机重启设备是最快的恢复方式。我的排查顺序建议是电源供电→参考时钟有无→参考时钟频率→SPI时序→寄存器值回读。这五步走完大部分0x247卡住的问题都能定位到。4.3 CP_OVRG被置位且RX PLL不锁定的组合故障CP_OVRG是电荷泵过压标志。如果这个位被置位说明电荷泵在VCO校准时找不到合适的调谐电压电压顶到了上限或下限PLL自然锁不住。这通常意味着VCO校准失败。常见原因之一是LO频率配置和VCO实际工作范围不匹配。AD9361的VCO覆盖范围会有多档切换如果设备树里初始频率设得极低比如几十MHzVCO会尝试用高频范围内分频来实现校准会在边界值附近反复试探最终触发CP_OVRG。试着把LO频率调到一个更常规的区间比如1GHz以上看问题是否消失。另一个常见原因是供电电压偏低。AD9361的数字和RF部分分别供电VCO部分对电压非常敏感。我之前做低功耗优化时把VCO供电从标准的1.3V调到了1.25V结果CP_OVRG频繁出现。把电压调回规格要求的1.3V后这个问题就消失了。如果你用的DC-DC电源纹波偏大在VCO供电引脚附近加一颗100nF和10uF去耦电容可以有效改善。还有一个容易被忽视的点接收通道的RF端口输入信号幅度过强或直流偏置异常会在校准时干扰VCO环路。排查时把RF前端断开只保留本振自检看是否仍然报CP_OVRG。这样可以区分问题在前端还是在芯片本身。处理这类问题的实用方法是逐步降速排查先把RX/TX LO频率都设在2.4GHz参考时钟40MHz带宽18MHz这个配置是社区里验证过最通用的组合。如果它无法工作优先怀疑硬件如果它能工作再逐步改成你的目标参数二分定位问题。4.4 常见初始化失败速查表现象可能原因推荐排查方式dmesg没有ad9361相关信息设备树compatible错误、SPI节点status禁用、驱动未编译进内核检查设备树节点、搜索CONFIG_AD9361SPI回读0x002返回全FSPI时序、片选、供电问题用逻辑分析仪抓SPI信号确认CS、CLK、MOSI电平0x247一直0x80参考时钟未起振或幅度不够、BBPLL锁定失败量EXT_REF_CLK信号、确认clock-frequency属性CP_OVRG被置位RX PLL未锁定VCO校准失败、供电偏低、LO频率超范围调整供电电压、改动LO频率、串电容隔离RF前端驱动probe成功但RX数据全零AGC配置错误、RF前端增益过低、数据接口未配置用iio_attr查看RX增益、检查FPGA数据通路iio_info找不到ad9361设备驱动模块未加载、设备树节点未生效检查dmesg、确认CONFIG_AD9361是y这张表我建议截图收藏每次初始化失败先对照排查一轮比自己瞎翻寄存器快得多。5. 实操经验与调试技巧汇总5.1 调试AD9361的必备工具组合工欲善其事必先利其器。我调试AD9361时最常用的工具是这几个iio_reg用它读写AD9361原始寄存器快速验证SPI通路。iio_attr读写IIO设备属性比如查看当前RX LO频率、增益。iio_oscilloscope图形化界面查看IQ波形、FFT频谱验证射频信号是否真正收到。dmesg内核日志是排查probe失败的第一现场。逻辑分析仪抓SPI时序检查寄存器读写是否正确。尤其在怀疑SPI速率或者设备树配置问题时一抓一个准。如果板子支持网络优先用libiio的network backend。先在板子上运行iiod然后在电脑上执行iio_info -u ip:192.168.1.100这样就能在电脑上直接操作板子上的AD9361不用每次都在串口终端敲命令调试体验会好很多。5.2 PetaLinux构建速度优化的两个习惯PetaLinux全量编译很慢动辄十几分钟。我培养了两个习惯来提速。一是尽量只做增量构建改设备树就只编译device-tree和kernel不要没事执行全量petalinux-build。二是在开发阶段把rootfs做成initramfs这样每次修改后只需要打包一个image.ub通过tftp启动不用反复插拔SD卡。虽然initramfs在内存中运行有大小限制但调试阶段完全够用。5.3 几个容易被忽略的坑工程路径不能有空格。PetaLinux的构建脚本里有大量路径拼接空格会导致各种诡异错误。设备树里64位数字必须用/bits/ 64。忘了写这个前缀值会被截断频率直接不对。SPI速率不要太激进。板子布线不好时10MHz可能都会出错8MHz更稳。驱动加载成功后可以再慢慢提高。改完设备树以后记住先查启动日志。Linux对设备树不匹配的报错往往隐藏在dmesg中比如OF: fsl,spi ...或者failed to get ad9361_clk这类。看到类似信息说明设备树节点没被正确解析需要回过去检查文件内容。最后说一点个人感受。AD9361这套系统调试起来确实痛苦寄存器数量庞大状态机复杂芯片手册上千页。但一旦你把PetaLinux工程、设备树、IIO驱动这套框架跑通了后面做频率规划、带宽调整、增益策略这些功能就都变成“改参数”的问题不会再到处碰壁。我自己也是从0x247读到0x80的懵圈状态一步步走过来的后来总结出一套排查顺序效率高了很多。如果你的板子也卡在类似的初始化状态参考上面第4章的步骤逐项排除大概率能定位到真因。AD9361本身很成熟代码没问题的时候绝大多数故障都出在电源、时钟、SPI这三个基本盘上。
返回列表