ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA内嵌XADC实战:DRP与AXI4-Lite接口配置及避坑指南

FPGA内嵌XADC实战:DRP与AXI4-Lite接口配置及避坑指南 1. 为什么XADC值得单独拿出来讲但凡用过7系列FPGA的人大概率都在工程里见过一个叫XADC的模块。它不像FFT、FIR这些IP那样显眼也不像DDR控制器那样一挂上就占一大片资源但它在很多项目里扮演的角色其实相当关键——板级电压电流监控、上电时序检测、温度采集、外部模拟量采样这些活儿基本都得靠它。XADC全称是Xilinx Analog-to-Digital Converter是集成在7系列FPGA内部的一个双通道12位、1MSPS的模数转换器同时还能监测芯片自身的温度和供电电压。换句话说你手里那颗FPGA不只是一堆逻辑资源它还自带了一个小型的模拟采集系统。我最早接触XADC是在一个工业采集项目上当时需要实时监控四路外部传感器的模拟输出同时还要盯着FPGA核心温度和几路供电电压。一开始想的是外挂一颗ADC芯片后来发现板子空间紧张、成本也压得厉害才回头认真研究XADC。说实话第一次配的时候踩了不少坑——DRP接口时序对不上、通道选择搞混、报警阈值没配导致中断一直触发。后来把Vivado里XADC IP核的几种接口模式都摸了一遍才算真正把它用顺。这篇内容主要面向已经有一定Vivado使用基础、准备在项目里集成XADC的工程师或者正在调试XADC接口但遇到问题的朋友。我会从IP核的配置思路讲起把DRP和AXI4-Lite两种接口的实战细节拆开说再补充一些实际调试中总结出来的避坑经验。涉及到的参数计算、时序分析和代码片段都会给出来尽量做到看完就能上手。2. XADC IP核的整体设计与接口选型思路2.1 XADC内部到底有什么在动手配置IP核之前有必要先搞清楚XADC内部的结构不然配出来的东西大概率是“能跑但不知道为什么能跑”。XADC的核心是一个12位逐次逼近型ADC最高采样率1MSPS。它有两个模拟输入通道可以配置成单极性或双极性输入也可以组成差分对。除了外部模拟输入XADC还内部连接了芯片的温度传感器和几路供电电压监测点这些在IP核里叫做片上传感器on-chip sensors。XADC内部有两个独立的ADCADC-A和ADC-B它们可以并行工作。ADC-A通常负责外部模拟输入ADC-B负责片上传感器。每个ADC前面有一个模拟多路复用器可以在多个输入通道之间切换。整个采集过程由一套状态机控制包括通道选择、采样保持、逐次逼近转换、结果写入寄存器这几个阶段。转换结果存放在一个叫状态寄存器Status Register的表格里每个通道对应一个固定的地址偏移。这里有个容易搞混的点XADC的通道编号和IP核里看到的通道名称不是一一对应的。比如VP/VN是专用差分输入对VAUXP[0]/VAUXN[0]到VAUXP[15]/VAUXN[15]是16对辅助输入。在IP核配置界面里这些通道会以复选框的形式出现但底层寄存器地址是固定的。如果你用DRP接口直接读寄存器就必须对照UG480手册里的通道地址表来操作不能想当然。2.2 DRP接口和AXI4-Lite接口怎么选Vivado里的XADC IP核提供两种主要的用户接口DRPDynamic Reconfiguration Port和AXI4-Lite。这两种接口的适用场景差别很大选错了会给后续开发带来不必要的麻烦。DRP接口本质上是一个同步的读写端口直接映射到XADC内部的寄存器空间。它的优点是延迟低、资源占用少、控制灵活适合对时序要求严格或者需要频繁切换通道的场景。缺点是接口协议需要自己写状态机来驱动对新手不太友好。DRP的读写操作是分开的读的时候要给地址、拉高DEN、等DRDY写的时候类似但方向相反。整个时序必须严格对齐时钟否则读回来的数据就是错的。AXI4-Lite接口则是把XADC的寄存器空间封装成了标准的AXI从设备你可以用MicroBlaze或者Zynq的PS端通过AXI总线来访问。这种方式的好处是软件层面操作简单直接读写地址就行不需要关心底层时序。缺点是资源占用比DRP多而且访问延迟受总线影响。如果你的项目里有处理器系统或者需要软件灵活配置采集参数AXI4-Lite是更省心的选择。我个人的经验是纯FPGA逻辑项目、对采集实时性要求高、不需要软件动态调整参数的选DRP带MicroBlaze或Zynq PS的、需要软件层面灵活控制的选AXI4-Lite。当然XADC IP核也支持同时使能两种接口但一般没必要反而增加复杂度。2.3 时钟方案与采样率的关系XADC的时钟来源有两种一种是外部时钟输入另一种是内部生成的时钟。在IP核配置里你可以选择使用PCLK外部时钟或者让IP核自己从系统时钟分频产生。不管哪种方式最终送到XADC核心的时钟频率决定了采样率。这里有一个关键公式XADC的转换时钟频率和采样率之间的关系是固定的。ADC的转换过程需要多个时钟周期具体来说一次完整的转换需要26个时钟周期对于连续采样模式。所以如果你给XADC的时钟是26MHz那么实际采样率就是1MSPS。如果你给的是13MHz采样率就是500kSPS。这个关系在UG480里有详细说明但很多人配的时候只看IP核界面上的“采样率”选项忽略了底层时钟的实际频率。在实际项目中我一般建议XADC的时钟不要超过26MHz因为超过之后采样率虽然标称能到1MSPS以上但实际精度会下降。另外XADC的时钟质量直接影响转换结果的噪声水平如果时钟抖动大读回来的数据跳动也会很明显。所以如果条件允许尽量给XADC一个干净的时钟源不要和高速逻辑共用同一个时钟网络。3. XADC IP核配置细节与关键参数解析3.1 通道使能与输入模式配置打开Vivado的XADC IP核配置界面第一页就是通道选择。这里你会看到几个区域片上传感器温度、VCCINT、VCCAUX、VCCBRAM等、专用模拟输入VP/VN、辅助模拟输入VAUXP/N[0]到[15]。每个通道前面都有一个复选框勾上就表示使能该通道的采集。这里有一个实操中很容易忽略的点使能通道的数量直接影响转换序列的长度。XADC内部有一个通道序列器它会按照你使能的通道顺序依次采集。如果你使能了8个通道那么一轮采集需要8次转换每次26个时钟周期总共208个时钟周期。这意味着如果你用1MSPS的采样率每个通道的实际更新率只有125kSPS。所以不要盲目把所有通道都勾上只使能你真正需要的通道。输入模式方面XADC支持单极性和双极性两种。单极性模式下输入范围是0到1V或者0到0.5V取决于参考电压配置双极性模式下输入范围是-0.5V到0.5V。这个选择取决于你的外部模拟信号是什么样的。如果你的传感器输出是0到3.3V那需要外部加一个分压电路把电压降到XADC的输入范围内。这里要特别注意XADC的模拟输入绝对不能超过规定的电压范围否则会损坏器件。我在一个项目里就见过同事直接把12V信号往VAUXP上接结果那颗FPGA的XADC通道直接报废了。3.2 报警阈值与中断配置XADC的报警功能在实际项目中非常有用但配置起来也有讲究。在IP核配置界面里你可以为每个片上传感器通道设置上限和下限阈值。当采集值超过或低于阈值时XADC会触发报警信号。这个报警信号可以连接到FPGA逻辑也可以触发中断。配置阈值的时候要注意XADC的数值格式。温度传感器的输出是12位有符号数单位是摄氏度但有一个固定的偏移量。具体来说温度寄存器的值除以16再减去273.15才是实际摄氏度。比如读回来是0x0A00换算一下就是2560/16 - 273.15 160 - 273.15 -113.15摄氏度这显然不对说明我读错了寄存器或者格式搞混了。实际上温度寄存器的格式是高12位有效低4位是小数部分。正确换算方法是温度 (寄存器值 4) * 0.0625 * 16 - 273.15简化一下就是 (寄存器值 4) * 1 - 273.15。这个换算公式我在第一次用的时候搞了半个小时才弄对。电压通道的换算相对简单V (寄存器值 / 4096) * 3.0这里的3.0是内部参考电压。但要注意不同通道的满量程可能不一样具体要查UG480里的表格。报警阈值设置的时候建议留一定的余量。比如VCCINT的正常范围是1.0V±5%那上限可以设到1.08V下限设到0.92V。不要贴着边界设否则电源纹波稍微大一点就会频繁触发报警反而干扰正常逻辑。3.3 时序参数与DRP接口配置如果你选择DRP接口IP核配置界面里会出现几个时序相关的参数转换时钟频率、DRP时钟频率、通道序列器模式等。这些参数直接决定了DRP读写操作的时序。转换时钟频率就是前面说的ADC核心时钟一般设成26MHz对应1MSPS。DRP时钟频率是DRP接口的工作时钟可以和转换时钟不同。这里有一个关键约束DRP时钟必须至少是转换时钟的1/2否则读写操作可能来不及完成。我一般把DRP时钟设成和系统时钟一样比如50MHz或100MHz这样逻辑写起来比较顺手。通道序列器模式有两种连续模式和单次模式。连续模式下XADC会不停地按照使能的通道顺序循环采集单次模式下每触发一次采集一轮。大多数应用场景用连续模式就行只有需要精确控制采集时刻的场合才用单次模式。还有一个参数叫“DRP使能”这个必须勾上否则IP核不会生成DRP接口。如果你同时要用AXI4-Lite那就在另一个选项卡里使能AXI接口。两个接口可以共存但地址空间是分开的。4. DRP接口实战从时序到代码实现4.1 DRP读写时序详解DRP接口的时序其实不复杂但细节很多。先看读操作当DEN拉高时DADDR上的地址被锁存同时DOUT开始准备数据。经过一定的延迟通常是2个DRP时钟周期DRDY拉高表示数据有效此时DOUT上的数据可以被采样。读操作完成后DEN需要拉低否则会一直重复读同一个地址。写操作类似DEN拉高DADDR给出地址DI给出要写的数据DWE拉高表示写使能。经过同样的延迟后DRDY拉高表示写完成。写操作一般用于配置XADC的寄存器比如设置报警阈值、修改通道序列等。这里有一个很容易踩的坑DRP的读写操作不能同时进行而且每次操作之间需要间隔至少一个时钟周期。如果你连续不断地发起DRP请求XADC可能来不及响应导致DRDY一直不拉高或者数据错乱。我在调试的时候就遇到过这个问题后来在状态机里加了一个空闲周期问题就解决了。另外DRP的地址是7位的对应XADC内部寄存器的地址空间。常用的地址包括0x00是温度寄存器0x01是VCCINT0x02是VCCAUX0x03是VP/VN0x10到0x1F是VAUX[0]到VAUX[15]。这些地址在UG480的Table 3-1里有完整列表建议打印出来贴在显示器旁边。4.2 用Verilog实现一个DRP读状态机下面是一个我实际项目中用过的DRP读状态机简化版但功能完整。这个状态机每秒钟读一次温度和VCCINT读回来的数据通过一个简单的握手信号送给后续逻辑。module xadc_drp_reader ( input wire clk, input wire rst_n, output reg [6:0] daddr, output reg den, input wire [15:0] dout, input wire drdy, output reg [11:0] temp_data, output reg [11:0] vccint_data, output reg data_valid ); localparam IDLE 3d0; localparam READ_TEMP 3d1; localparam WAIT_TEMP 3d2; localparam READ_VCC 3d3; localparam WAIT_VCC 3d4; localparam DONE 3d5; reg [2:0] state, next_state; reg [15:0] div_cnt; reg trigger; // 1Hz 触发脉冲生成假设 clk 50MHz always (posedge clk or negedge rst_n) begin if (!rst_n) begin div_cnt 16d0; trigger 1b0; end else if (div_cnt 16d49_999_999) begin div_cnt 16d0; trigger 1b1; end else begin div_cnt div_cnt 1b1; trigger 1b0; end end // 状态机主逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next_state; end always (*) begin next_state state; case (state) IDLE: if (trigger) next_state READ_TEMP; READ_TEMP: next_state WAIT_TEMP; WAIT_TEMP: if (drdy) next_state READ_VCC; READ_VCC: next_state WAIT_VCC; WAIT_VCC: if (drdy) next_state DONE; DONE: next_state IDLE; default: next_state IDLE; endcase end // 输出逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) begin daddr 7d0; den 1b0; temp_data 12d0; vccint_data 12d0; data_valid 1b0; end else begin den 1b0; data_valid 1b0; case (state) READ_TEMP: begin daddr 7h00; // 温度寄存器地址 den 1b1; end WAIT_TEMP: begin if (drdy) begin temp_data dout[15:4]; end end READ_VCC: begin daddr 7h01; // VCCINT寄存器地址 den 1b1; end WAIT_VCC: begin if (drdy) begin vccint_data dout[15:4]; data_valid 1b1; end end default: ; endcase end end endmodule这段代码的核心思路是用一个简单的状态机控制DRP的读写时序。注意几个细节DEN信号只在READ状态下拉高一个时钟周期然后立即拉低这样可以避免重复读。DRDY的等待放在单独的WAIT状态里确保数据稳定后再采样。数据取的是dout的高12位因为XADC的转换结果是12位的低4位是无效的或者用于其他目的。4.3 数据换算与校准读回来的原始数据需要换算成实际的物理量。温度换算前面已经说过了这里再强调一下温度 (原始值 4) * 1 - 273.15。注意这里的原始值是16位的右移4位之后得到12位的整数部分小数部分被截断了。如果你需要更高的精度可以保留低4位换算公式变成温度 (原始值 / 16.0) - 273.15。电压换算V (原始值 / 4096) * 3.0。这里的3.0是内部参考电压实际值可能在2.95到3.05之间波动如果需要高精度建议先用一个已知电压校准一下。校准方法很简单给一个精确的1.000V输入读回原始值然后反推参考电压。我在一个项目里就是这么做的校准之后精度能到±2mV。对于外部模拟输入通道换算公式是一样的但要注意输入模式是单极性还是双极性。单极性模式下0对应0V4095对应满量程双极性模式下2048对应0V0对应负满量程4095对应正满量程。5. AXI4-Lite接口实战软件端访问与寄存器映射5.1 AXI4-Lite接口的地址空间当你选择AXI4-Lite接口时Vivado会自动生成一个AXI从设备把XADC的寄存器空间映射到AXI地址上。在Address Editor里你可以看到XADC的地址范围通常是4KB。这个4KB空间里前256字节对应XADC的DRP寄存器后面的空间可能用于其他控制寄存器。具体的地址映射关系是AXI地址的低8位对应DRP地址高几位用于片选。比如DRP地址0x00温度寄存器对应的AXI地址就是基地址0x00DRP地址0x01对应基地址0x04因为AXI是32位对齐的每个寄存器占4字节。这个映射关系在IP核的文档里有说明但很多人第一次用的时候会搞混以为DRP地址直接就是AXI地址。在Zynq平台上你可以通过PS端的M_AXI_GP接口访问XADC。在SDK或Vitis里直接对映射后的地址进行读写就行。比如#define XADC_BASEADDR 0x43C00000 #define XADC_TEMP_OFFSET 0x00 #define XADC_VCCINT_OFFSET 0x04 u32 read_xadc_reg(u32 offset) { return *(volatile u32 *)(XADC_BASEADDR offset); } float get_temperature() { u32 raw read_xadc_reg(XADC_TEMP_OFFSET); return (raw 4) * 1.0 - 273.15; }这段代码看起来简单但有一个坑XADC的寄存器是16位的而AXI总线是32位的。读回来的32位数据里低16位有效高16位可能是0或者未定义。所以取值的时候要记得掩码否则算出来的温度会莫名其妙地大很多。5.2 中断与报警处理AXI4-Lite接口的一个优势是可以方便地使用中断。XADC IP核有一个中断输出信号可以连接到Zynq的PL-PS中断端口。当报警条件满足时XADC会触发中断PS端进入中断服务程序处理。配置中断的步骤首先在IP核里使能报警功能并设置阈值然后把中断输出连接到Zynq的IRQ_F2P端口最后在Vitis里注册中断处理函数。中断处理函数里要读取报警状态寄存器判断是哪个通道触发了报警然后做相应处理。这里有一个实际经验报警状态寄存器是只读的读完之后会自动清除。但如果你在中断处理函数里没有及时读取报警信号会一直保持导致中断反复触发。我在一个项目里就遇到过这个问题后来在中断服务程序里加了一个循环把所有报警状态都读一遍问题就解决了。5.3 软件端动态配置采集参数用AXI4-Lite接口的另一个好处是可以在软件运行时动态修改采集参数。比如你可以通过写寄存器来改变通道序列、调整报警阈值、切换单次/连续模式。这些操作在DRP接口下需要修改FPGA逻辑并重新综合而AXI4-Lite只需要写几行代码。具体来说通道序列寄存器地址0x48到0x4F控制着采集顺序。每个寄存器可以配置两个通道通过写不同的值来改变序列。报警阈值寄存器地址0x50到0x5F用于设置上下限。这些寄存器的具体格式在UG480的Chapter 3里有详细说明。不过要注意动态修改配置的时候最好先停止采集改完再重新启动。否则可能会出现配置生效时机不确定的问题导致读回来的数据对应的是旧配置。我在调试的时候就遇到过这种情况改完阈值之后读回来的数据还是按旧阈值判断的后来加了一个软复位才解决。6. 常见问题与排查技巧实录6.1 DRP读回来的数据一直是0或者不变这是最常见的问题之一。可能的原因有几个第一DEN信号没有正确拉高或者拉高的时间不对。DRP的DEN必须在DADDR稳定之后拉高而且要保持到DRDY拉高。如果DEN只拉高一个周期就拉低XADC可能还没完成转换。第二地址搞错了。比如你想读温度结果写了0x10那读回来的就是VAUX[0]的数据。第三DRP时钟和转换时钟的相位关系不对。如果DRP时钟太快可能在DRDY还没稳定的时候就采样了数据。排查方法先用ILA抓一下DRP接口的波形看DEN、DADDR、DRDY、DOUT的时序关系。重点看DRDY拉高的时候DOUT是否稳定以及DEN的宽度是否足够。如果波形没问题但数据还是不对那就检查地址映射表确认你读的地址对应的是哪个通道。6.2 温度读数明显偏高或偏低温度读数不准通常是因为换算公式用错了。XADC的温度寄存器格式是高12位是整数部分低4位是小数部分。但很多人直接拿整个16位数去换算结果就偏了。正确的做法是先右移4位再减去273.15。另外XADC的温度传感器测量的是芯片结温不是环境温度所以读数通常会比环境温度高10到20度这是正常的。如果读数偏差特别大比如差了100度那可能是寄存器地址搞错了读到了其他通道的数据。比如VCCINT的原始值大概是0x555左右换算成温度就是(0x555 4) - 273.15 85 - 273.15 -188度这显然不对。所以遇到异常读数先确认地址。6.3 报警中断频繁触发报警中断频繁触发通常是因为阈值设置得太紧。比如VCCINT的正常范围是1.0V±5%如果你把上限设到1.01V那电源稍微波动一下就触发了。建议阈值留至少10%的余量。另外XADC的报警信号有滤波功能可以在IP核里设置滤波周期避免噪声导致的误触发。还有一个可能的原因是报警状态寄存器没有及时清除。前面说过报警状态寄存器读完后自动清除但如果你用的是中断模式必须在中断服务程序里读取状态寄存器否则中断会一直保持。我在一个项目里就是因为忘了读状态寄存器导致中断反复触发CPU占用率直接飙到100%。6.4 AXI4-Lite读写超时AXI4-Lite读写超时通常是因为地址映射不对或者XADC IP核的时钟没有正确连接。检查一下Address Editor里的地址范围是否和代码里的一致以及XADC的s_axi_aclk是否连接到了正确的时钟源。另外如果XADC的复位信号没有正确释放AXI接口也不会响应。还有一个容易被忽略的点XADC的AXI接口需要一定的初始化时间。上电之后XADC需要完成内部校准才能正常工作。如果你在初始化完成之前就发起AXI读写可能会超时。建议在软件里加一个延时或者轮询XADC的状态寄存器确认就绪后再操作。6.5 常见问题速查表问题现象可能原因排查方法解决方案DRP读回数据为0DEN时序不对ILA抓波形确保DEN在DADDR稳定后拉高保持到DRDY温度读数偏差大换算公式错误检查换算代码先右移4位再减273.15报警中断频繁触发阈值设置过紧查看报警状态寄存器阈值留10%余量使能滤波AXI读写超时地址映射错误检查Address Editor确认基地址和偏移量数据跳动大时钟抖动大测量时钟质量使用干净的时钟源避免与高速逻辑共用通道数据串扰序列配置错误检查通道使能顺序只使能需要的通道确认序列寄存器7. 几个实际项目中的经验补充7.1 关于外部模拟输入的保护XADC的模拟输入引脚非常脆弱绝对电压不能超过规定范围。在实际项目中我建议在外部模拟信号和XADC输入之间加一个电压跟随器或者分压电路。电压跟随器可以用运放搭建输入阻抗高、输出阻抗低能有效隔离外部信号和XADC。分压电路则适合信号幅度超过XADC量程的情况比如0到5V的信号可以用两个电阻分压到0到1V。另外XADC的模拟输入引脚附近要加去耦电容一般0.1uF和1uF并联就行。这个电容要尽量靠近引脚否则滤波效果会打折扣。我在一个项目里就是因为电容放得太远采集到的信号上叠加了明显的开关噪声后来把电容挪到引脚旁边就好了。7.2 关于采样率的实际限制虽然XADC标称能到1MSPS但在实际项目中我很少真的用到这么高的采样率。原因有两个第一高采样率下噪声会明显增大12位精度可能只能保证到10位左右第二高采样率意味着DRP接口的读写频率也要相应提高对逻辑时序的要求更严格。所以如果你的应用不需要1MSPS建议把采样率降到100kSPS到500kSPS之间这样精度和稳定性都会好很多。7.3 关于多通道采集的调度策略如果你需要采集多个通道不要简单地让XADC自动循环。更好的做法是用单次模式手动触发每个通道的采集这样可以精确控制每个通道的采样时刻避免通道之间的相互干扰。具体实现方法是配置通道序列寄存器只使能一个通道触发一次采集读回数据然后切换到下一个通道。虽然这样效率低一点但数据质量更有保障。7.4 关于XADC的复位与初始化XADC在上电之后需要一定的时间来完成内部校准这个时间大概是100微秒左右。如果你在FPGA配置完成后立即访问XADC可能会读到无效数据。建议在逻辑里加一个复位计数器等XADC稳定后再开始采集。另外XADC的复位信号要同步释放避免亚稳态问题。我在一个项目里就因为忽略了初始化时间导致上电后前几秒的温度读数一直是0后来加了一个200微秒的延时才正常。这个坑虽然小但如果不注意调试的时候会很困惑。7.5 关于XADC在低功耗场景下的使用XADC本身功耗不高但在一些低功耗应用中如果不需要实时监控可以间歇性地开启XADC采集完就关掉。XADC IP核支持通过寄存器控制使能和关闭具体方法是写配置寄存器的相应位。不过要注意每次开启后都需要重新等待校准完成所以间歇采集的频率不能太高否则校准时间会占很大比例。8. 写在最后的一些个人体会XADC这个IP核说简单也简单说复杂也复杂。简单在于它的功能很明确就是采集模拟量复杂在于它的接口时序和寄存器映射有很多细节稍不注意就会踩坑。我自己的经验是第一次用的时候不要贪多先把一个通道调通把DRP或者AXI接口的读写时序搞清楚然后再逐步增加通道和功能。另外UG480手册一定要放在手边遇到问题先查手册大部分疑问都能在里面找到答案。网上的教程虽然多但很多都是抄来抄去细节上不一定准确。我自己就吃过这个亏照着某篇博客配了半天结果发现博客里的地址表是错的白白浪费了一个下午。最后分享一个小技巧如果你用的是Zynq平台可以先用PS端的XADC也就是通过SysMon来读取温度电压不需要占用PL资源。等PS端的XADC用熟了再过渡到PL端的XADC IP核这样学习曲线会平缓很多。我在实际项目中就是这么做的先用PS端验证采集逻辑再迁移到PL端省了不少调试时间。
返回列表