ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ADRV9009 no-OS工程移植与Xilinx SDK调试实战指南

ADRV9009 no-OS工程移植与Xilinx SDK调试实战指南 做射频板的这几年我经手的项目里有一大半离不开ADRV9009这枚收发芯片。说它是整个链路里最难啃的部分一点不夸张内部寄存器上千个、初始化流程十几步、不同版本的API还互相不兼容一旦调试环境搭不对光是“把官方参考工程跑起来”这件事就能磨掉一整天。后来我把整个工程移植和调试流程理顺之后发现真正核心的东西其实没那么多关键就三件事版本选对、工程结构读懂、SDK里知道该看哪几个寄存器。这篇文章就是我自己的实操记录从no-OS工程移植到Xilinx SDK调试把每一步怎么走、为什么会踩坑、怎么避开这些坑全都摊开讲。适合正在用Zynq平台做ADRV9009驱动开发、或者刚拿到评估板准备跑通第一版代码的工程师参考。1. ADRV9009与no-OS先搞清楚你到底在玩什么1.1 这块芯片比你想象的要复杂得多ADRV9009是ADI出的一款双通道宽带收发器内部集成了接收链路、发射链路、观测接收机甚至还有片上处理器。听起来功能大而全但代价就是控制复杂度极高。你没法像控AD9361那样简单SPI写几个寄存器就能完成配置它需要一整套初始化流程包括加载固件镜像、初始化时钟、校准收发链路每个阶段都要等待状态寄存器返回正确标志。实测下来一次完整的初始化在SPI时钟跑10MHz的时候大概要持续几十毫秒到百毫秒级期间你要不断轮询状态稍有一点时序错位就卡在某个地方出不来。第一次在SDK里Debug ADRV9009的时候我盯着内存监视窗口看寄存器值完全看不懂为什么某个寄存器就是写不进预期的值。后来把SPI总线的波形抓出来才发现是片选信号拉的时机不对导致读写顺序错位了半拍。所以说ADRV9009的调试本质上是“硬件时序 寄存器状态机 软件配置顺序”三者一起配合光在软件层面瞎试是试不出来的。1.2 为什么选no-OS而不是Linux很多人会觉得既然Zynq平台上跑Linux天经地义那直接用Linux下的ADRV9009驱动不就好了确实方便但如果你的项目需要精确控制初始化时序、想要极低的中断延迟或者干脆只是在做原型验证no-OS反而更合适。no-OS其实就是ADI提供的一套不依赖操作系统的裸机驱动库直接通过SPI操作寄存器把API暴露给用户。我自己的经验是Linux驱动虽然代码量大、封装更完整但在排查问题的时候会多一层抽象。裸机世界里的调用栈很短应用代码 - API - SPI读写 - 寄存器任何一个环节出错都能快速定位。尤其做射频模块调试你在SDK里打断点、单步跑能清清楚楚看到每个配置字的写入顺序这种透明感是Linux驱动给不了的。当然如果你的产品最终要跑复杂协议栈那还是老老实实用Linuxno-OS只适合前期验证和时序敏感的场合。1.3 官方工程的整体构成三个仓库缺一不可ADI针对这块芯片的参考工程主要由hdl仓库和no-OS仓库组成。hdl仓库提供FPGA侧的比特流工程例如针对ZCU102或ZC706开发板的板级设计no-OS仓库则提供ARM侧的裸机驱动和示例应用。需要严格注意这两个仓库的版本必须匹配否则FPGA侧的AXI接口地址和驱动里的地址对不上程序跑飞了都不知道为什么。no-OS仓库里的目录结构是这样的drivers目录存放各个器件驱动rt-transceiver/adrv9009就是我们要的芯片驱动platform目录存放抽象层xilinx子目录就是针对Xilinx平台的SPI和GPIO实现project/adrv9009目录是示例应用工程里面包含了main函数和初始化流程。你移植的时候通常只需要改platform层和project层驱动本体基本不用动。2. 五分钟工程移植先跑通官方demo再谈定制2.1 选对版本是一切的基础标题说五分钟搞定因为我这里指的正是“基于官方设计文件在目标开发板上把参考工程跑起来”这件事。前提是你已经安装好Vivado环境和对应的SDK而且手上的板卡跟官方支持列表完全一致。例如ZCU102和ZC706的工程不同不能拿A板卡工程的bit流烧到B板卡上资源映射完全不同硬烧大概率起不来。强烈建议直接使用release tag不要用main分支。主要原因是main分支持续迭代可能今天的API和明天就不同。我踩过最大的坑就是clone了main分支代码结果ADRV9009相关头文件里的初始化结构体成员多了一个字段编译不通过查了半天才发现是版本不匹配。后面改用release tag就再没出过这种问题。下面列出了我曾经使用过的一组稳定组合仅供参考Vivado/SDK版本no-OS releasehdl release备注2019.22019_r2hdl_2019_r2最稳的一套组合资料最多2020.22020_r2hdl_2020_r2支持更新的Zynq UltraScale2022.12022_r1hdl_2022_r1API有较大调整需注意怎么查看当前仓库处于哪个tagcd进目录后执行git describe --tags --abbrev0就能看到。官方Release分支的代码相对成熟驱动API和FPGA设计是配套验证过的新手千万别自己去凑版本。2.2 工程目录里到底藏了哪些关键文件把no-OS仓库clone下来后不要急着打开IDE先把目录结构摸清楚。我着重讲三个地方第一是projects/adrv9009目录这里是应用入口。main.c里会调用adi_adrv9009_Initialize等一系列接口完成芯片初始化然后进入某个业务逻辑比如TDD模式下的收发切换循环。这个目录下还有makefile定义了交叉编译参数后面编译时要重点关注。第二是drivers/rf-transceiver/adrv9009目录这是芯片驱动的核心源码头文件有一大堆。里面的api目录存放不同模块的接口例如adrv9009_arm.h、adrv9009_rx.h等。一般而言驱动代码不需要改动直接拿来编译即可。第三是platform/xilinx目录里面是硬件抽象层实现包括SPI、GPIO、中断和DMA映射。这里才是移植的重点你要检查SPI的基地址、GPIO的管脚编号是否跟你的FPGA设计一致如果有不一致的地方改这个目录下的宏定义即可。ADI官方代码写得很清晰宏基本都集中在include目录中的头文件里例如XPAR_XXXX的宏这是Xilinx SDK自动生成的外设地址定义一般能自动匹配。2.3 移植三步走获取、修改、编译我的操作习惯是三步走第一步获取代码。先把hdl和no-OS仓库分别clone到本地然后切换到上面选定的release tag。再打开Vivado用hdl仓库里工程的.tcl脚本生成并编译出bit流。编译这个过程在Vivado里大概要跑二十到三十分钟取决于电脑性能。第二步修改平台文件。如果是完全官方的开发板其实不用改任何东西直接用就好。但如果你用的是非官方板卡比如第三方做的ADRV9009模块配Zynq主控那就得对着原理图打开platform/xilinx里的头文件把SPI、GPIO、RESET对应的管脚宏逐一修改。注意GPIO的极性也要看原理图低有效和高有效不要搞反。第三步编译no-OS应用。在no-OS根目录下直接执行make。可以指定交叉编译工具链例如ARCHarm CROSS_COMPILEarm-linux-gnueabihf-。make成功后会生成elf文件。到这里官方demo的编译算完成了。剩下的事基本上都是SDK里的事情把编译好的elf和bit流加载到板子上或者更常见的做法是直接从SDK里新建工程导入源码重新编译这样调试起来更方便。3. SDK调试技巧把代码跑起来并且看到它在干什么3.1 创建SDK工程与BSP配置的几个关键动作很多人习惯在Vivado里Export Hardware后直接Launch SDK然后创建一个Application Project再把源码全部拖进去编译。这么做是可以的但有几个细节一旦漏掉后面调试会特别痛苦。第一件是BSP配置。创建完工程后系统会为硬件平台生成一个板级支持包里面有standalone库的配置。你需要打开BSP的Settings在extra_compiler_flags这一项里确认一下标准栈配置。不要小看这个参数ADRV9009初始化过程中API内部会使用较大数组默认栈空间只有1KB或2KB一旦调用层次深一点就爆栈表现出来就是程序跑飞或跑到某个变量值被莫名改写。我把Stack Size直接设到8MB彻底告别闪退问题。第二件是串口重定向。SDK的printf默认走的是标准库的底层输出函数需要修改standalone里stdin和stdout设备。在BSP设置中找到stdin和stdout两个配置项改成你的串口外设名字比如ps7_uart1。这样printf输出就能在SDK的Terminal里看到了。如果你在硬件无报错但printf没输出九成是这里没配置。第三件是链接脚本。SDK会自动生成lscript.ld规定了代码段放哪些内存。默认情况下可能把堆(heap)和栈(stack)放在较小的OCM里建议改成DDR空间大不怕超。我这里踩过的坑是BSP设置里改了stack size但链接脚本里把栈指定到了OCM结果依然爆栈把两处都改了才彻底解决。3.2 编译、烧录与运行的完整流程在SDK里调试ADRV9009我建议的流程如下先在SDK的Xilinx菜单里选择Program FPGA加载之前Vivado导出的hdf文件里的bit流。加载成功后Zynq的PL侧就运行起来了PS侧还在等着代码加载。然后右键你的应用工程选择Debug As - Launch on Hardware (System Debugger)。SDK会自动下载elf并停在main函数入口处。到这里板子上的PS和PL都已经跑起来了接下来就是调试环节。烧录后第一件事永远是打开串口终端检查有没有经printf输出多行初始化日志。如果没有任何输出先排查终端是否连对了UART以及波特率是否与BSP里匹配常见是115200或9600。如果能看到初始化日志那就说明SPI链路没问题基本成功了一半。3.3 几个能救命的高级调试技巧在SDK里设置断点、单步执行这些基础操作就不多讲了重点说几个我的独门心得第一个是看SPI寄存器交互。调试ADRV9009经常有“初始化中断在那个阶段却不知道是哪一步”的情况。建议在adrv9009驱动的SPI读写函数入口打断点每次读写的总线地址和值都会停在调用栈里。配合watch窗口你能直接看到当前正在配置的寄存器地址再跟寄存器手册对照很快就能锁定问题出在哪个模块。这样做虽然慢一点但排查力度极强。第二个是紧盯状态机标志。ADRV9009初始化的每一步几乎都要等某个状态位变成OK。例如触发的ARM固件启动、TX QEC校准完成等都有自己的状态地址。我习惯在状态判断处的while循环条件上打断点或者加一些临时变量缓存状态值然后强制跑完循环体看状态值是否如期变化。如果状态一直不变说明链路往下根本没通根本原因是前面的配置有误不可能等到后面才暴露。第三个是缓存一致性问题。Zynq的ARM核有数据缓存做DMA接收时如果CPU读完缓存而DMA写入的是DDR两边看到的可能是不同数据。这个是Zynq裸机调试最经典的问题。SDK调试时如果你发现收到的数据总是不对检查代码里是否在DMA传输完成后执行了Xil_DCacheInvalidateRange。忘了刷缓存这件事我当时折腾了一下午才反应过来。4. 常见问题与排查技巧实录4.1 高频问题速查表下面是我自己整理的一份高频问题速查表几乎每次给客户支持或新同事交接时都会发一遍现象可能原因快速排查方法SDK连不上目标板调试器报错JTAG链未识别/板卡电源异常检查Vivado Hardware Manager能否检测到器件确认电源灯printf无输出BSP的stdin/stdout未指向UART打开BSP设置确认设备名检查波特率程序启动即Hard Fault栈溢出或外设基地址错误调整堆栈大小核对外设地址宏初始化卡在等待状态位SPI读写异常或时序错误在SPI入口打断点抓总线波形检查复位时序DMA收不到数据缓存一致性问题加Xil_DCacheInvalidateRange或关数据缓存编译器报结构体字段不存在驱动版本与示例工程不匹配全部改用同一个release tag初始化慢到像死机固件加载阶段耗时正常加大打印看进度在ARM加载接口处打时间戳发现没绝大多数问题都不是ADRV9009芯片本身的问题而是工程配置、版本匹配和缓存一致性这类通用问题。所以遇到问题别先怀疑芯片先怀疑你的工程环境这个意识特别重要。4.2 独家避坑经验与调试心得最后分享几条我自己的调试心得都是实打实用时间换来的教训第一条尽量先在官方参考板上跑通整套流程。第一次接触ADRV9009时我直接拿到一块第三方板卡就开始调结果一个下午全耗在SPI和GPIO映射上。后来换回官方评估板demo十分钟就跑了。官方板能验证你的工具链、流程和思路是否正确排除硬件差异后才好去做定制移植。第二条源码里多留打印信息。ADI的示例工程本身就打印不少日志但如果你改动了配置务必加上自己的标记打印。例如某一步配置完成后print一个“Step 3 done”。当程序卡住时看最后一条日志就能快速判断卡在哪个阶段比在调试器里翻调用栈快得多。第三条别迷信IDE界面的“正确性”。SDK里BSP设置、链接脚本和硬件设计之间实际是相互关联的。例如修改了硬件设计里的UART地址SDK里旧BSP可能依然保留旧地址这就导致编译自动生成的宏是旧的跑起来根本不对。每次硬件设计改动后记得重新Export Hardware并重建BSP不要偷懒复用旧工程我就是吃过这个亏才老老实实的。第四条遇到寄存器值写不进去时先用逻辑分析仪抓SPI总线。很多情况下你以为的寄存器错误其实只是片选或时钟相位不满足芯片要求。仪器一抓问题一目了然比盲调强太多。根据我个人经验ADRV9009的no-OS工程移植做到后面其实很套路每次新项目过来我基本就是复制自己上一份能跑的工程改改平台文件编译烧录再在SDK里断点确认初始化流程没问题。把前面这些版本、目录、缓存和栈的问题都处理好之后剩下的大部分时间就花在射频性能调优上了比如校准参数、频点切换延迟、DPD效果这些。说实话把基础环境搭稳、调试手法练熟比纠结某一个寄存器要重要得多。后面如果有时间我再整理一版关于ADRV9009 TDD模式下多板卡同步的方案那是另一个让人抓狂的话题了。
返回列表