公司动态
AXI总线与AD9361工程实战:从IP核到Linux驱动的完整解析
简介本资源是一个基于Verilog实现的AXI总线驱动工程专为Analog Devices AD9361射频收发器设计面向FPGA开发工程师、SDR系统设计人员及嵌入式硬件开发者解决AD9361在Zynq等SoC平台上的AXI协议对接与数据通路控制问题。压缩包共21个文件含12个核心Verilog源文件如axi_ad9361_tx.v、rx_channel.v、tdd.v等、3个Tcl脚本用于IP封装与综合、2个XML配置文件、1个Makefile、1个XPR工程文件、1个XDC约束文件及1个WDF器件描述文件整体仅40KB轻量但结构完整。已有1265人学习下载。读者可直接导入Vivado工程复现AD9361的AXI-Lite寄存器配置与AXI4数据通道收发逻辑涵盖LVDS接口适配、TDD模式控制、PN序列监测等关键模块代码分层清晰支持仿真验证与硬件调试是理解高速RF芯片底层驱动与AXI协议协同设计的实用参考。 拿到axi_ad9361.rar这个压缩包的人多半是在做软件无线电或者高速数据采集相关的东西。这个文件名背后是一个很典型的组合Xilinx 的 AXI 总线协议 ADI 的 AD9361 射频收发器。我见过不少朋友从这个包开始入门结果被里面的 IP 核、设备树、DMA 通道搞得一头雾水。这篇博客就围绕这个压缩包里的核心内容展开聊一聊 AXI 总线在 AD9361 工程里到底扮演什么角色、驱动层是怎么一层层把射频数据搬进处理器的以及我实际调试中踩过的坑。先说清楚这个工程是干什么的。AD9361 是一颗高集成度的射频收发芯片它的配置接口是 SPI数据接口是并行的 CMOS 或 LVDS而 AXI 是 FPGA 内部和 ARM 处理器之间通信的总线标准。axi_ad9361这个 IP 核做的事情就是把 AD9361 的数据接口和 AXI 总线无缝对接起来让处理器可以通过 AXI-Lite 配置寄存器通过 AXI-Stream 接收和发送高速 IQ 数据。如果你拿到的是 Zynq 平台的版本那 PS 端多半跑着 Linux驱动层需要做的事情就更多了。整篇文章我会按这个思路展开先讲清楚系统架构是怎么从压缩包反推出来的再分别拆解 AXI 协议选型、AD9361 接口时序、Linux 驱动设计、常见问题排查这几个模块。无论你是 FPGA 工程师、嵌入式软件工程师还是刚接触 SDR 的学生应该都能从中找到能直接拿去用的东西。1. 从压缩包反推系统全貌axi_ad9361 到底在做什么很多人解压axi_ad9361.rar之后第一反应是找 README但往往官方文档并不全。我的做法是先看目录结构把文件按类型分一下.v/.vhd是 RTL 源码.xci是 Xilinx IP 核配置.tcl是构建脚本.dts/.dtsi是设备树文件.ko或者驱动源码就是 Linux 侧的东西。这个分类做完整个工程的轮廓就出来了。这类工程的核心思路可以用一句话概括ARM 处理器通过 AXI 总线控制 FPGA 逻辑FPGA 逻辑再通过 SPI 和并行数据接口驱动 AD9361。AD9361 负责把射频信号变成数字 IQ 数据FPGA 里的axi_ad9361IP 负责把这些数据打包成 AXI-Stream 协议最终通过 DMA 送进 DDR 内存。反过来发数据就是反方向走一遍。为什么要用 AXI因为 AD9361 的数据率不低。以 20 MHz 带宽、IQ 两路、16 bit 采样来算单通道数据率就是 40 MSPS 乘 32 bit大概 160 MB/s。如果用自定义总线或者 GPIO 去搬这个数据量CPU 直接被打满。AXI-Stream DMA 的组合可以把 CPU 从数据搬运中解放出来CPU 只负责配置和控制数据通路纯硬件搞定。这也是为什么几乎所有 AD9361 的 FPGA 参考设计都长一个样AXI-Lite 管配置AXI-Stream 管数据。从这个角度看axi_ad9361这个压缩包本质上是一个软硬件分界线的参考实现。搞清楚这条线在哪后续的驱动开发和调试就顺畅了。FPGA 工程师需要关注的是 AD9361 接口时序和 AXI 数据打包软件工程师需要关注的是寄存器和 DMA 描述符的交互两者在 AXI-Lite 寄存器映射处交汇。2. AXI 总线在 AD9361 工程里的三种角色AXI 不是一种总线而是一族协议。在 AD9361 这种工程里你至少会遇到三种 AXI 变体它们各司其职不能混用。2.1 AXI-Lite配置通道速度要求低但必须可靠AXI-Lite 是 AXI 的精简版没有突发传输burst每次只能读写一个寄存器的值。听起来很弱但配置寄存器场景根本不需要高带宽需要的是操作简单、时序清晰。AXI-Lite 单次读写延迟低、资源占用小所以 SPI 桥、GPIO 控制器、状态寄存器这类低速外设全挂在它下面。在axi_ad9361工程里AXI-Lite 主要承担两件事一是把 ARM 侧的寄存器读写转换成 AD9361 的 SPI 时序让软件可以直接用devmem或者驱动代码操作 AD9361 内部寄存器二是提供axi_ad9361IP 自身的控制寄存器比如软复位、数据通路使能、中断状态位。调试 AXIL 的时候有一个很容易忽略的点地址对齐。AXI-Lite 要求 32 bit 访问必须 4 字节对齐8 bit 访问必须 1 字节对齐。如果你在驱动里定义了一个结构体成员之间有 padding直接通过结构体指针访问寄存器就会解析错位。我遇到过不止一次软件工程师在 ARM 侧读到的寄存器值永远是 0x00最后发现是结构体没加__packed。2.2 AXI-Stream数据通道追求极致吞吐AXI-Stream 和 AXI-Lite 完全是两个思路。AXI-Lite 有地址AXI-Stream 没有地址它只关心数据的流向。每一个时钟周期数据在tdata线上流动tvalid和tready握手tlast标记一帧数据的结束tkeep标定哪些字节有效。这种协议设计的目标只有一个让数据以最高效率从源搬到目的地中间不经过任何寻址开销。在 AD9361 数据链路里RX 方向的做法大致是这样的AD9361 的数据引脚持续输出并行的 IQ 采样点axi_ad9361IP 把这些采样点按照预先设定的打包格式填充到tdata总线上同时拉高tvalid。下游的 AXI DMA IP 一旦收到tvalid就开始把数据往内存里写。TX 方向则是反过来DMA 从内存读出 IQ 数据通过 AXI-Stream 送给axi_ad9361IPIP 再把数据按 AD9361 要求的时序送到数据引脚上。这里有一个关键设计点tlast怎么拉。AD9361 的数据是连续流没有自然帧边界所以axi_ad9361通常会配置一个采样长度计数器攒够设定的采样点数就拉一次tlast。如果你的驱动和应用层是按帧处理数据的这个长度必须和 DMA 描述符的长度精确匹配否则就会出现半帧数据或者跨帧拼接的问题。2.3 AXI DMA 与 SmartConnect把数据搬进内存把多设备串起来光有数据流还不够数据最终得进 DDR让操作系统和应用层能访问。这个任务通常由 Xilinx 的 AXI DMA IP 完成。AXI DMA 内部维护一个描述符链表每个描述符指向一块内存缓冲区并指定传输长度。驱动的工作就是把描述符排队到 DMA 的寄存器里然后等中断。AXI SmartConnect 或 AXI Interconnect 在这个架构里承担总线交换机的角色。因为系统里不止一个 AXI 主设备——ARM 处理器是一个DMA 是一个——多个主设备要访问 DDR 或外设必须有仲裁机制。这就是热词里axi仲裁器和axi smartconnect出现的场景。实际操作中如果 DMA 带宽上不去先别急着怀疑代码看看 SmartConnect 的端口带宽配置和数据位宽是否匹配。默认配置可能把所有端口都设为 32 bit但你的 DMA 是 64 bit 的这里不匹配性能直接砍半。3. AD9361 与 FPGA 的数据接口先于 AXI 搞清楚很多人在 AXI 这边折腾半天最后发现问题在 AD9361 本身。AD9361 和 FPGA 之间的接口有一套独立的时序要求必须在 AXI 设计之前定义清楚。3.1 数据接口模式CMOS 还是 LVDSAD9361 的数据总线有两条DATA_CLK 是数据时钟P0 和 P1 两组数据线。不同模式下的引脚数量和数据率上限差别很大模式引脚位宽最高数据率适用场景CMOS单端P0 和 P1 各 6 或 12 bit约 160 MSPS带宽需求较低逻辑简单LVDS差分P0 和 P1 各 3 或 6 对约 640 MSPS高带宽长距离走线CMOS 模式的好处是 FPGA 引脚资源占用少、逻辑层直接处理单端信号坏处是对走线等长要求高。LVDS 模式抗干扰强、速率高但需要接差分终端电阻。axi_ad9361IP 在 RTL 层面对两种模式都有支持但你必须在例化 IP 之前就决定用哪种因为引脚约束文件完全不同。我的建议是第一版先跑 CMOS 模式把链路打通再说。等基本功能没问题再切 LVDS 打性能。不要一上来就挑战最高难度调试环境的复杂度会成倍增加。3.2 时钟关系是最容易埋雷的地方AD9361 的数据接口时钟有两种来源一种是 AD9361 自己根据参考时钟和采样率产生的 DATA_CLK另一种是 FPGA 提供时钟给 AD9361。无论哪种数据引脚的变化始终以 DATA_CLK 为基准。在axi_ad9361IP 里数据采样逻辑需要同步到 DATA_CLK 域。如果你的 FPGA 工程里有多时钟域这里必须加异步 FIFO 或者同步器不然时序收敛不了数据偶尔错位是最难查的现场问题之一。还有一个常见坑DATA_CLK 的频率必须和 AD9361 的配置准确匹配。比如你配置采样率为 40 MSPSCMOS 接口 2:1 模式下 DATA_CLK 可能是 20 MHz但实际因为参考时钟精度问题DATA_CLK 是 19.98 MHz短期看不出问题长时间采集数据就会丢帧。所以我在每个 AD9361 工程里都坚持做一件事把 DATA_CLK 引到一个可以测量的 GPIO 或者逻辑分析仪探头上。上电后第一件事就是测频率确认和预期一致再继续跑省去了大量排查时间。3.3 SPI 配置链路从工程启动到 AD9361 跑起来AD9361 的配置完全依赖 SPI寄存器数量超过 1000 个。axi_ad9361IP 把 AXI-Lite 的寄存器读写翻译成 SPI 时序翻译规则是固定的但配置内容由软件准备。通常参考设计里会提供一份 C 数组或者初始化脚本里面是一大堆地址-值对就是 AD9361 的完整配置。关键的配置内容包括采样率、射频带宽、本振频率、增益控制模式、滤波器系数、数据接口格式二进制补码还是偏移二进制CMOS 还是 LVDS单端口还是双端口。这些配置一旦出错AD9361 可能根本不输出数据或者输出的数据完全不对。驱动层做 SPI 配置时有一个细节值得注意SPI 的速率不要一次性拉太高。很多 AD9361 板上 SPI 走线比较长信号质量受限10 MHz 能正常配置20 MHz 就会丢位。先低速配置完确认寄存器回读没问题之后再考虑提速。这个顺序反了你会在错误的方向上浪费大量时间。4. 驱动层Linux 侧怎么把整条链路拉起来FPGA 侧搞定了软件侧的驱动才是真正把硬件变成可用工具的最后一步。axi_ad9361工程在 Linux 下的驱动通常不是一个人能写完的但理解它的框架并不难。4.1 老式 FPGA 驱动框架platform driver 全家桶一个典型的 AD9361 Linux 驱动会包含几个部分首先是一个platform_driver负责匹配设备树节点获取寄存器物理地址、中断号和 DMA 通道然后通过ioremap把寄存器的物理地址映射到虚拟地址空间用readl/writel操作寄存器最后注册一个字符设备或者网络设备向上层提供open/read/write/ioctl接口。regmap是内核里一个很实用的抽象层它把寄存器读写这个操作抽象成regmap_read/regmap_write底层可以接内存映射、I2C、SPI。对于axi_ad9361这种通过 AXI-Lite 内存映射访问的 IPregmap_config里指定reg_base就够了。好处是你的驱动逻辑不用关心寄存器到底在哪个地址也方便将来换平台。DMA 部分用内核的 DMA Engine 框架管理。驱动申请一个dma_chan把数据缓冲区地址和长度填进dma_slave_config然后用dmaengine_prep_slave_single准备描述符dmaengine_submit提交dma_async_issue_pending启动。传输完成后在中断回调里处理数据。这套 API 有点绕但只要理解了描述符→提交→执行→回调这个生命周期写起来就很顺手。4.2 设备树里的关键信息逐字段拆解设备树是 AXI 外设驱动的身份证。一个典型的axi_ad9361相关节点长这样axi_ad9361 { compatible adi,axi-ad9361; reg 0x79020000 0x1000; reg-names ad9361; clocks clk0_ad9528 1; clock-names ad9361_clk; interrupts 0 57 4; interrupt-parent gic; rx-dma-channel axi_dma_rx; tx-dma-channel axi_dma_tx; adi,rx-data-mode 1; adi,tx-data-mode 1; };reg字段告诉我们 AXI-Lite 寄存器基地址是 0x79020000长度 0x1000。驱动里所有寄存器操作都从这地址偏移。interrupts是中断号ARM GIC 中断号 57高电平触发。rx-dma-channel和tx-dma-channel指向 DMA 节点DMA 节点里则会有#dma-cells和具体通道配置。设备树写错了最常见的报错是compatible字符串不匹配。内核通过compatible找驱动如果驱动里填的字符串和设备树的差一个字符驱动加载就会直接失败。我排过最长的一次就是aei,axi-ad9361和adi,axi-ad9361的区别大小写加拼写盯了半天才发现。4.3 数据通路启动流程从 insmod 到拿到 IQ 数据驱动的运行流程其实比很多人想得简单。加载驱动后probe函数完成四件事解析设备树获取寄存器、中断、DMA 通道资源ioremap映射寄存器空间注册regmap通过 AXI-Lite 发起 AD9361 的 SPI 配置把 1000 多个寄存器写进去申请 DMA 通道注册中断处理函数创建设备节点应用层打开设备节点后通过ioctl下发开始采集命令。驱动接到命令后在 DMA 内核缓冲区里申请内存填充 DMA 描述符启动 DMA。DMA 每传输完一帧触发中断驱动的中断处理函数把数据拷贝到用户空间。这里的拷贝开销不小高性能方案是用 mmap 让用户态直接访问内核 DMA 缓冲区省掉copy_to_user。TX 方向就是反着来应用层把 IQ 数据写到设备节点驱动把数据填进 DMA 缓冲区DMA 把数据送到axi_ad9361IPIP 再把数据按 AD9361 接口时序发出去。4.4 新板卡调试串口工具、调试器驱动缺一不可每次拿到一块新板卡我最先做的事情不是编译内核而是确认调试链路通不通。AD9361 的板子通常需要串口做 Linux 控制台、J-Link 或 ST-Link 做 ARM 调试、选装 USB-UART 工具抓日志。这些东西的驱动安装本身也容易卡人。CP2102 和 CH340 是两款最常见的 USB-UART 芯片。CP2102 的驱动在 Linux 内核里已经内置CH340 内核也自带但 Windows 下就得装厂商出的cp210x或ch340驱动。FT232 系列则强烈建议用 FTDI 官方驱动不要用系统自带的旧版 VCP否则高波特率下会丢字符。这些纯属运气问题有一次我板子串口死活不出字符最后发现是用了山寨的 FT232芯片 ID 不对驱动直接拒绝加载。这种问题没有捷径只能换原装芯片或者禁用驱动校验。J-Link 和 ST-Link 的驱动大多数时候没问题但新板卡上有个陷阱默认 SWD 接口可能被复用成了别的功能。你连不上调试器不一定是驱动问题先查原理图确认 SWDIO 和 SWCLK 没有被外设占用。这个问题我遇到三次了每次都是查驱动查半天最后一看原理图全是泪。5. 踩坑实录AD9361 AXI Linux 的典型问题排查这部分我直接以表格形式把常见问题、可能原因和排查手段列出来都是我在多个项目里反复验证过的经验。现象可能原因排查手段设备树匹配失败驱动没加载compatible 字符串不一致设备树节点未包含dmesg查看内核日志核对设备树和驱动源码寄存器读回全是 0xFFSPI 链路不通AD9361 复位未释放参考时钟没起万用表测 AD9361 电源示波器测参考时钟检查复位 GPIO寄存器读回正常但无数据输出数据接口模式配置错误DATA_CLK 频率不对测量 DATA_CLK核对 AD9361 配置脚本里的接口模式字段DMA 中断触发但数据全部为 0AXI-Stream 握手异常IP 内部数据通路未使能ILA 抓 AXI-Stream 接口检查axi_ad9361控制寄存器数据有值但解出来是乱码IQ 数据格式不匹配二进制补码/偏移二进制采样位数不对核对配置脚本里的格式字段对照 AD9361 手册确认默认值DMA 传输卡死不再触发中断描述符不够用缓冲区未对齐DMA 通道没释放检查 DMA 状态寄存器增加描述符数量确认缓冲区地址以 32 字节对齐长时间运行后偶发丢帧DATA_CLK 有微小频偏中断处理时间过长用精确时钟源优化中断处理流程考虑多缓冲机制排查顺序也很重要。我的固定流程是先物理层再协议层最后才是驱动层。先查电源和时钟再查 SPI 配置然后用 ILA 看 AXI 时序最后才看 Linux 驱动。很多人一上来就在dmesg里找报错其实硬件信号一塌糊涂软件再排查都是白费。还有两个容易被忽略的小细节。第一个是 AD9361 的复位时序复位信号低电平保持时间至少需要 10 个参考时钟周期软件里gpio_set_value之后必须加足量的延迟不然芯片可能处于复位状态但你以为已经释放了。第二个是 Linux 内核的 DMA 缓冲区需要物理地址连续kmalloc小的缓冲区没问题但大数据量要用dma_alloc_coherent。如果你在驱动里发现 DMA 地址和虚拟地址乱七八遭八成是缓冲区分配方式用错了。6. 不用官方工程自己搭建最小验证系统的思路axi_ad9361.rar这种参考工程是很好的学习材料但它毕竟是别人按自己的平台调好的拿到你的板卡上不一定能直接跑。我个人的建议是参考工程可以帮你理解完整的系统结构但新板卡的调试应该从更小的系统开始。最小验证系统只需要三样东西一个 AXI-Lite 接口的寄存器读写模块一个 GPIO 控制的 AD9361 SPI 桥一个逻辑分析仪或 ILA 核。先用 AXI-Lite 读写自己的测试寄存器确认 ARM 和 FPGA 之间的通路是通的然后通过 SPI 桥读写 AD9361 的几个关键 ID 寄存器确认 AD9361 活着最后用 ILA 抓 AD9361 的 DATA_CLK 和数据引脚确认模拟前端在出数据。这三步做完才真正具备调试完整数据链路的条件。这一步最大的价值在于把问题空间切小了。如果 AXI-Lite 读写都不通后面的一切都是空中楼阁如果 AD9361 的 ID 寄存器都读不到说明芯片都没起来真没必要急着调 DMA。我自己每次做新板卡都严格按这个顺序来调试时间从以前的几周缩短到三到四天。等最小系统验证通过再往里面加 AXI-Stream、DMA、设备树和驱动每加一层就验证一层。这样做看起来慢但实际上比一次性把整个工程烧进去然后一个问题一个问题地拆要快得多因为每一层的问题都被限制在一个很小的范围内不需要跨层猜测。这个思路也适用于看别人的参考工程。不要拿到axi_ad9361.rar就直接全量编译烧写先把里面的 IP 和驱动拆出来对照最小系统的思路逐步验证。很多参考工程都有针对特定板卡的 hack直接套用会带来莫名其妙的问题。按照你自己的硬件设计一步一步来反而能更快地把系统跑起来。本文还有配套的精品资源点击获取