☰
axi_ad9361驱动开发:AXI总线挂载AD9361从解压到跑通
2026/10/1 6:19:22 网站建设 项目流程

简介:AXI_AD9361 是一套基于 Verilog 实现的硬件驱动工程,面向从事软件定义无线电、无线通信及嵌入式系统开发的工程师与学习者,用于解决 FPGA 与 AD9361 射频收发器之间的高速数据交互问题。工程通过 AXI 总线完成寄存器配置与数据收发,涵盖初始化、工作模式设置、收发通道管理及错误检测等核心逻辑,适合具备一定数字电路与协议基础的中高级开发者参考。压缩包共 21 个文件,约 40KB,以 12 个 Verilog 源文件为主体,辅以 tcl 脚本、xdc 约束、xpr 工程文件、Makefile 及组件描述文件,覆盖从 RTL 设计到工程构建的完整链路。目前已有 1268 人学习下载。读者可从中获取 AD9361 驱动在 AXI 总线下的接口实现思路、收发通道与 TDD 控制模块的代码组织方式,以及配套仿真与约束脚本,便于快速搭建验证环境并理解射频前端与 FPGA 的协同设计要点。

1. axi_ad9361 驱动:从压缩包名到跑通 AXI 总线上 AD9361 的完整路径

手里拿到一个叫axi_ad9361.rar的包,解压出来是一堆 Verilog、C 和头文件,很多人第一反应是「这玩意儿怎么跑起来」。它本质上是把 AD9361 这颗射频收发芯片挂到 AXI 总线上的驱动与逻辑集合,核心解决的是 FPGA 侧怎么通过 AXI 寄存器读写去配置 AD9361、怎么把收发数据搬进内存。适合正在做 SDR、无线收发、基带验证的 FPGA 和嵌入式工程师,尤其是手上已经有 Zynq 或 MicroBlaze 平台、需要把 AD9361 从「上电没反应」推到「能收能发」的人。标题里的 axi_ad9361、AXI、ad9361、驱动四个词,对应的是三层东西:AXI 是总线协议,ad9361 是射频前端,驱动是把两者接起来的软件层。这三层任何一层没对齐,现象都是「寄存器读出来不对、PLL 不锁、数据不动」。

2. 拆开 axi_ad9361 包:先分清 AXI 逻辑、SPI 配置和驱动代码

2.1 一个包里的三类文件,别混着看

axi_ad9361这类包通常不是单一工程,而是三块内容的集合。第一块是 AXI 外设逻辑,负责把 AXI 读写请求翻译成对 AD9361 内部寄存器的 SPI 时序;第二块是 AD9361 的初始化序列,也就是那一长串寄存器地址和值;第三块是驱动层,可能是 Linux 下的字符设备驱动,也可能是裸机下的 C 函数。很多人翻车是因为把这三块当成一个整体去编译,结果逻辑综合过了、驱动加载了,但 SPI 根本没通。

常见做法是先把包里的文件按后缀和目录分一下:.v/.sv是 FPGA 逻辑,.c/.h是驱动和初始化,.xdc/.sdc是约束,.tcl是脚本。分完之后先确认 AXI 外设的基地址和中断号,这两个值在后续驱动里必须一致,不一致的现象就是驱动 probe 成功但读写全返回 0xFFFFFFFF。

2.2 AXI 寄存器映射:驱动和逻辑的契约

AXI 侧对 AD9361 的访问,通常不是直接读写 AD9361 的 SPI 寄存器,而是通过一组 AXI 映射的间接寄存器。典型布局是:一个命令寄存器写地址和读写方向,一个数据寄存器放要写或读回的值,一个状态寄存器表示 SPI 忙闲。驱动要做的就是按这个契约去操作。

下面是一段裸机下常见的 AXI 间接读写函数,用来替代直接 SPI:

/* axi_ad9361 间接 SPI 访问:先写命令,再等状态,最后读写数据 */ #define AXI_AD9361_BASE 0x43C00000 #define REG_CMD (AXI_AD9361_BASE + 0x00) #define REG_DATA (AXI_AD9361_BASE + 0x04) #define REG_STATUS (AXI_AD9361_BASE + 0x08) #define CMD_WRITE (1 << 31) #define CMD_READ (0 << 31) #define CMD_ADDR_MASK 0x3FF static int axi_ad9361_spi_write(uint16_t addr, uint8_t val) { /* 命令寄存器:bit31 方向,bit30..16 地址,bit15..8 数据 */ *(volatile uint32_t *)REG_CMD = CMD_WRITE | ((addr & CMD_ADDR_MASK) << 16) | (val << 8); /* 等待 SPI 忙标志清零,超时返回错误 */ int timeout = 10000; while ((*(volatile uint32_t *)REG_STATUS & 0x1) && --timeout) ; return timeout ? 0 : -1; } static int axi_ad9361_spi_read(uint16_t addr, uint8_t *val) { *(volatile uint32_t *)REG_CMD = CMD_READ | ((addr & CMD_ADDR_MASK) << 16); int timeout = 10000; while ((*(volatile uint32_t *)REG_STATUS & 0x1) && --timeout) ; if (!timeout) return -1; *val = (*(volatile uint32_t *)REG_DATA) & 0xFF; return 0; }

逻辑说明:命令寄存器的高位决定方向,地址位宽要和 FPGA 逻辑里定义的位宽一致,数据位放在固定偏移。参数说明:AXI_AD9361_BASE必须和 Vivado 地址编辑器里分配的基地址一致;CMD_ADDR_MASK要和逻辑里地址字段的位宽匹配,写错会导致访问到错误寄存器。超时值 10000 是经验值,SPI 时钟慢的时候要加大。

2.3 初始化序列:0x247 读出 0x80 意味着什么

AD9361 上电后必须按手册给的顺序写一长串寄存器,其中 0x247 是很多人卡住的地方。如果初始化到某一步读 0x247 一直返回 0x80,通常不是芯片坏了,而是前面的时钟和电源配置没到位,或者 SPI 写根本没生效。常见排查顺序是:先读一个已知固定值的寄存器(比如产品 ID),确认 SPI 通路是通的;再检查初始化序列里有没有漏掉某个必须的延时;最后确认参考时钟频率和初始化序列里假设的一致。

初始化序列不要手抄,用 ADI 官方提供的脚本或工具生成,然后对照包里的序列逐条比对。如果包里的序列和官方生成的不一致,以官方为准,因为芯片手册的时序要求是硬性的。

3. 把 axi_ad9361 跑起来:从地址分配、SPI 打通到 PLL 锁定

3.1 地址分配与设备树:驱动 probe 前的必做项

在 Zynq 或 MicroBlaze 平台上,AXI 外设的基地址由 Vivado 地址编辑器分配。分配完之后,Linux 下要在设备树里写一个节点,裸机下要在头文件里定义基地址。设备树节点的关键是reg和compatible,reg要和地址编辑器一致,compatible要和驱动里的匹配表一致。

/* 设备树节点示例:axi_ad9361 挂在 AXI 总线上 */ axi_ad9361: axi-ad9361@43c00000 { compatible = "myvendor,axi-ad9361"; reg = <0x43c00000 0x10000>; interrupt-parent = <&intc>; interrupts = <0 29 4>; clocks = <&clk_100m>; clock-names = "s_axi_aclk"; };

逻辑说明:reg里的地址和长度必须覆盖逻辑里所有寄存器;interrupts要和逻辑里中断引脚连到的中断控制器编号一致。参数说明:0x10000是地址空间大小,按实际寄存器数量调整;中断号 29 是示例,实际以 Vivado 里中断连接为准。如果 probe 失败,先看dmesg里是地址冲突还是中断申请失败。

3.2 用 devmem 快速验证 AXI 读写是否通

在 Linux 下,驱动还没写好之前,可以用devmem直接读写 AXI 寄存器,快速判断逻辑是否正常。这是最省时间的验证手段,比反复编译驱动快得多。

# 读 AXI 状态寄存器,确认总线能访问 devmem 0x43C00008 32 # 写命令寄存器,发起一次 SPI 读 devmem 0x43C00000 32 0x00000100 # 再读状态,看忙标志是否清零 devmem 0x43C00008 32 # 读数据寄存器,看返回值 devmem 0x43C00004 32

逻辑说明:第一条命令读状态,如果返回 0xFFFFFFFF,说明地址不对或 AXI 逻辑没综合进去;第二条发起一次读操作;第三条确认忙标志清零;第四条读回数据。参数说明:地址要和设备树里一致,位宽写 32。如果状态一直不清零,检查 SPI 时钟是否使能、逻辑里状态位定义是否和驱动假设一致。

3.3 PLL 锁定与 CP OVRG HIGH:初始化到 RX PLL 没锁怎么查

初始化到某一步发现 RX PLL 没锁定,同时 CP OVRG HIGH 被置起,这是 AD9361 初始化里很典型的现象。CP OVRG 是电荷泵过压标志,置起说明 PLL 环路参数或参考时钟有问题。常见原因是参考时钟频率不对、初始化序列里 PLL 相关寄存器写错、或者电源纹波太大。

排查步骤:先用示波器或频率计确认参考时钟频率和初始化序列里假设的一致;再检查 PLL 寄存器(0x230 到 0x24F 附近)是否按手册要求写入;最后确认 CP OVRG 标志是否可以通过写清除寄存器清掉。如果清不掉,说明 PLL 确实没锁,不要强行继续后面的初始化。

4. axi_ad9361 避坑:SPI 不通、PLL 不锁、数据不动的排查记录

4.1 现象:驱动 probe 成功但读写全返回 0xFFFFFFFF

原因:AXI 基地址和设备树或头文件里定义的不一致,或者 AXI 逻辑根本没综合进 bitstream。解决:先用devmem读一个已知寄存器,如果返回 0xFFFFFFFF,检查地址分配和 bitstream 是否包含该外设。

4.2 现象:0x247 寄存器一直读出 0x80

原因:SPI 写没生效,或者初始化序列里前面的时钟配置没到位。解决:先读产品 ID 寄存器确认 SPI 通路,再逐条比对初始化序列和官方生成的是否一致,重点检查延时和参考时钟配置。

4.3 现象:RX PLL 没锁定,CP OVRG HIGH 置起

原因:参考时钟频率不对、PLL 寄存器写错、电源纹波大。解决:确认参考时钟频率,检查 PLL 相关寄存器,用示波器看电源纹波,必要时在电源脚加去耦电容。

4.4 现象:PLL 锁了但收发数据不动

原因:数据路径的 AXI Stream 没连对,或者 DMA 没启动。解决:检查 AXI Stream 的 tvalid/tready 握手,确认 DMA 配置和中断,用 ILA 抓一下数据路径上的信号。

4.5 现象:初始化序列写到一半就卡住

原因:某个寄存器写入后需要延时,但代码里没加。解决:对照手册在需要延时的地方加udelay或mdelay,延时值按手册要求,不要凭感觉。

5. 进阶:用 ILA 抓 AXI 事务和 SPI 时序,把黑匣子打开

当驱动和逻辑都「看起来对」但就是不工作时,最有效的手段是用 Vivado 的 ILA 抓 AXI 事务和 SPI 时序。把 ILA 挂在 AXI 接口和 SPI 接口上,触发条件设成一次写操作,看地址、数据、握手信号是否和预期一致。这一步能把「玄学」变成「看得见」。

具体做法:在 Vivado 里给 AXI 接口加 ILA IP,采样深度设成 1024 或 2048,触发条件设成awvalid && awready。抓一次写操作,看awaddr是不是你期望的地址,wdata是不是你写下去的值。如果地址不对,说明驱动里的偏移算错了;如果握手没发生,说明逻辑里 ready 信号没拉高。

SPI 侧同理,把 ILA 挂在 SPI 的 sclk、mosi、cs 上,触发条件设成 cs 下降沿,看时钟极性和相位是否和 AD9361 要求的一致。AD9361 的 SPI 模式在手册里有明确要求,模式不对的现象就是写进去读出来全是 0 或全是 0xFF。

我自己的习惯是:每次改完驱动或逻辑,先用 ILA 抓一次最小事务,确认地址、数据、握手都对,再去跑完整初始化。这样能把问题范围缩到最小,避免在几百条初始化序列里大海捞针。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询