ARTICLE DETAIL

资讯详情

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

ZYNQ 7010 PS直驱XADC:xadcps驱动实现温度电压监控

ZYNQ 7010 PS直驱XADC:xadcps驱动实现温度电压监控 简介ZYNQ 7010的PS端XADC驱动开发是嵌入式开发中常见的底层驱动需求这份配套工程包面向使用Zynq SoC且有模拟量采集需求的软硬件开发者系统演示了在SDK环境下构建PS_XADC驱动并读取温度、电压等信号的方法可应用于电源监测、温度采集与工业控制等场景。包内共530个文件以h头文件、c源文件、o目标文件、so与dll动态库等编译产物为主辅以tcl脚本、makefile、elf可执行文件方便工程重建、编译与调试压缩包仅7.58MB整体目录覆盖SDK工程、设备树配置及驱动源码。内容预览中包含Xilinx标准库、PS初始化代码以及XADC驱动源文件等关键组成部分说明内含一套可直接参考的XADC驱动库实现既有寄存器级底层配置也有AXI总线访问以及中断、轮询等数据读取方式通过分析这些代码还能理解XADC单通道、双通道、并行模式的选择以及采样数据的转换处理流程。截至目前已有393人学习对需要快速上手PS_XADC或系统梳理Xilinx SDK驱动框架的工程师来说是一份实用性很强的工程样本尤其适合希望从设备树到应用层完整理解XADC驱动链路的初学者。1. PS_XADC 驱动ZYNQ 7010 上最容易被低估的片内仪表调试 ZYNQ 7010 板卡我习惯第一步先跑一遍 ps_xadc 驱动——片子温度、VCCINT、VCCAUX 全都能读出来比拿万用表去量 BGA 背面快得多。这份 zip 里打包的是 SDK 工程基于 xadcps 库实现 PS 端对 XADC 硬核的直接访问不需要在 PL 里例化 xadc_wizard IP也不占 LUT。做电源监控、热保护或上电自检的人或者被 XADC 初始化玄学卡过的人这套工程可以直接导入 SDK 复现。下面把这套驱动从硬件配置到换算公式全部拆开写。2. 选 PS 直驱还是 PL 例化xadcps 库与 xadc_wizard 的两条路子2.1 XADC 的寄存器结构与两条访问路径XADC 是 7 系列 FPGA 里焊死在硅片上的 12 位、1MSPS 模数转换器对 ZYNQ 7010 来说它的测量对象包括芯片结温、PS 和 PL 各路电源电压以及最多 17 个外部模拟输入通道。ZYNQ 里这个硬核同时挂了两个控制器接口这是整件事的根源一条是 PL 侧通过 xadc_wizard IP 包出来的 AXI4-Lite 接口另一条是 PS 侧的系统监视器寄存器组基地址固定在 0xF8007000。SDK 里的 xadcps 驱动就是对着这组寄存器做的封装。两条路径物理上访问的是同一块 ADC 硬核所以它们天然互斥。PL 例化了 xadc_wizard 并跑起来PS 侧去读 0xF8007000 往往拿到全零或者初始化失败反过来 PS 通过 xadcps 驱动接管了采样序列PL 侧再发控制命令也没有响应。互斥这个性质决定了选型必须先想清楚而不是两边都写上代码再慢慢调。我见过不少人在 PL 里放着 xadc_wizard又在 SDK 里初始化 XAdcPs结果两边数据互相打架排查半天才发现是同一个硬核被两个控制器抢。2.2 为什么我选 PS 直驱我用 ZYNQ 7010 做的大部分板卡PL 侧的 LUT 和 FF 是要留给业务逻辑的比如千兆网、图像采集或者自定义协议处理。xadc_wizard 虽然只占几百个 LUT但它不是零成本你要给它配 AXI Interconnect、分配地址空间、做引脚约束还得花心思处理它和 PS 侧同时上电时的仲裁关系。PS 直驱则完全避开这些Block Design 里不添加任何 XADC 相关 IP导出硬件后 SDK 的 BSP 把 xadcps 驱动挂上几行代码就能开始读数。从调试角度讲PS 直驱还有一个隐性优势它不依赖 PL 的 bit 流。zynq 烧写 bit 流失败、或者 PL 侧被临时改成别的功能时PS 里这段 XADC 代码照样能跑温度监控不会跟着 PL 一起罢工。做电源时序调试、板子热稳定性测试时这个特性非常实用我之前就靠它在一批 PL 逻辑反复烧写测试的板子上持续盯着芯片温度完全不受影响。对于纯监控类需求PS 直驱的 CPU 占用也很低XADC 自己在片内按安全序列循环转换PS 只在要读数时花几十个周期去寄存器里取结果。我用它做 100ms 间隔的温度轮询CPU 占用率几乎可以忽略。对比项PS 直驱xadcpsPL 例化xadc_wizardPL 资源占用0 LUT数百 LUT BRAM总线接口PS 寄存器映射AXI4-Lite时钟来源片内时钟外部引脚或 PS 提供启动顺序PS 启动即可用依赖 PL bit 流加载适用场景电源监控、热保护外部通道采集、PL 逻辑联动2.3 SDK 工程需求清单与 API 分类开始写代码之前先把 SDK 工程需要的东西理清楚。硬件平台文件Vivado 导出的 hdf/xsa里必须带 PS_XADC 资源BSP 的 Peripherals 设置里要勾上 xadcps 库。工程里真正用到的宏只有两个XPAR_XADCPS_0_DEVICE_ID 和 XPAR_XADCPS_0_BASEADDR前者是设备 ID后者是寄存器基地址 0xF8007000。这两个宏由 BSP 根据硬件平台自动生成手写地址反而容易出问题。xadcps 库的 API 大致分成四类初始化类包括 XAdcPs_LookupConfig、XAdcPs_CfgInitialize、XAdcPs_SelfTest序列配置类包括 XAdcPs_SetSeqChMode、XAdcPs_SetSeqInputMode、XAdcPs_SetSamplingRate数据读取类包括 XAdcPs_GetTemperature、XAdcPs_GetVccint、XAdcPs_GetVccaux、XAdcPs_GetADCData报警类包括 XAdcPs_SetAlarmEnables、XAdcPs_SetAlarmThreshold。实际监控项目里最常用的就是前两类加温度读取把这几个函数吃透整个驱动就掌握了一半。下面从工程搭建开始一步一步走。3. 从 Vivado 到 SDK导出 hdf、勾选 xadcps 并初始化3.1 只含 PS 的最小 Block Design第一步在 Vivado 里新建 RTL 工程器件选 XC7Z010。然后添加 Zynq PS 核双击进入配置按你板子的实际型号配好 DDR比如 DDR3L 或 DDR4取决于硬件颗粒、UART1串口打印要用和 MIO 分配。这里有个原则Block Design 里不要添加任何 PL IP特别是 xadc_wizard。XADC 硬核在没有任何 PL 外设占用时默认采样序列归 PS 侧控制这正是我们想要的干净状态。配置完成后先 Validate Design再 File Export Hardware。如果你不需要 PL 侧的 bit 流这里可以不勾 Include bitstream导出的 hdf 文件已经带有 PS_XADC 资源信息。SDK 会拿这个 hdf 去生成 BSP所以这一步导出的文件一定要是最新的。很多人后面 BSP 里找不到 xadcps往回倒多半就倒在这里——Vivado 里改过 Block Design 但是没有重新 Export Hardware老 hdf 里压根没有 PS_XADC 节点。3.2 在 SDK 里搭 BSP 并确认 xadcps 驱动打开 SDK 后新建 Application Project输入工程名Board Support Package 选择 New。创建完成后先别急着写 main.c到 Board Support Package Settings 里看 Peripherals 列表把 xadcps 勾上然后点 Re-generate BSP Sources让驱动库真正落进工程。确认驱动安装成功有两个办法。一个是看 BSP 工程下的 system.mss里面能找到 xadcps 字样另一个是在 BSP 的 include 路径下找到 xadcps.h。这一步我一般会花 30 秒做掉因为后面编译报错了再回来排查你根本分不清是 BSP 没生成还是代码写错。如果用的是 Vitis 新版本流程类似在 Vivado 里导出的 xsa 文件关联到 Vitis 平台工程BSP 里同样会列出 xadcps 驱动选项。3.3 初始化 XADC 并登记采样序列初始化代码是固定的三段式#include xadcps.h #include xparameters.h #define XADC_DEVICE_ID XPAR_XADCPS_0_DEVICE_ID static XAdcPs XAdcInst; XAdcPs_Config *Config; int Status; Config XAdcPs_LookupConfig(XADC_DEVICE_ID); if (Config NULL) { return XST_FAILURE; } Status XAdcPs_CfgInitialize(XAdcInst, Config, Config-BaseAddress); if (Status ! XST_SUCCESS) { return XST_FAILURE; } XAdcPs_SetSeqChMode(XAdcInst, XADCPS_SEQ_CH_SAFE_MODE); XAdcPs_SetSeqInputMode(XAdcInst, XADCPS_SEQ_INPUT_MODE_SAFE);XAdcPs_LookupConfig 拿的是 BSP 根据硬件平台自动生成的配置结构体里面已经填好基地址和输入时钟频率不需要自己手写 0xF8007000。CfgInitialize 把配置结构体关联到驱动实例上并把 XADC 的控制寄存器复位到默认状态。初始化成功后再设采样序列安全模式表示 XADC 按固定顺序自动采集内部传感器——温度、VCCINT、VCCAUX、VCCBRAM、VCCPINT、VCCPAUX——不需要我们手动去维护通道轮询。SetSeqInputMode 设置输入类型安全模式下对应内部通道的默认输入配置。这里有个参数要留意XAdcPs_SetSeqChMode 的第二参数如果设成 XADCPS_SEQ_CH_CONTINUOUS_MODEXADC 会按你配置的通道列表顺序连续采集适合外部模拟输入。内部监控用安全模式就够了省心也不容易把序列配错。3.4 初始化后的状态自检初始化返回 XST_SUCCESS 不代表数据一定是准的我习惯再补一步自检调用 XAdcPs_SelfTest 做寄存器读写回环检查然后读取状态寄存器里的 EOC转换结束标志。安全模式下片内转换是自动持续的程序启动后等上一次转换完成即可没必要人为加固定延时。Status XAdcPs_SelfTest(XAdcInst); if (Status ! XST_SUCCESS) { xil_printf(XADC self test failed\r\n); return XST_FAILURE; }SelfTest 只验证寄存器读写路径不能代替数据正确性验证。真正的数据验证要等温度值读出来之后和万用表测的供电电压对比误差在合理范围才算通过。这步放在下一章讲换算的时候一起处理。4. 读温度与片内电压12 位转换码怎么换算成 °C 和 V4.1 温度换算为什么不是直接除以 4096XADC 是 12 位转换器输出范围 0~4095。如果把原始值直接拿来用你会得到温度值从 0 到 4095 的“大数”没有任何物理意义。温度的实际换算公式是T (code × 503.975) / 4096 - 273.82这个公式在 Xilinx 的 XADC 文档里有我一般记成先按 4096 满量程归一化乘 503.975 对应 ADC 输入范围的温度系数减 273.82 是把开尔文换回摄氏度。实测精度在 ±2°C 左右做热保护和板级健康监控完全够用。代码层面有个特别容易翻车的点code 的类型。XAdcPs_GetTemperature 返回的是 u32必须强转成 float 再算避免整数截断把小数点后的度数丢掉。我遇到过有人写成 code / 4096 * 503.975 - 273.82因为整数除法里 code / 4096 直接等于 0温度算出来永远是 -273.82还以为是硬件坏了。4.2 VCCINT / VCCAUX 电压换算与校准电压部分要区分两组通道。片内电源传感器测的是 PS/PL 内部供电轨XADC 内部做了归一化处理满量程对应 3V换算公式统一为V code × 3.0 / 4096对应 VCCINT、VCCAUX、VCCBRAM、VCCPINT、VCCPAUX、VCCO_DDR 这些通道。比如 VCCINT 是 1.0V 供电轨读出来的 raw_value 通常在 1365 附近因为 1365 × 3 / 4096 ≈ 1.0V。下面这张表是 7010 上最常检查的通道通道读取函数典型测量值芯片结温XAdcPs_GetTemperature30~60°CVCCINTXAdcPs_GetVccint1.0VVCCAUXXAdcPs_GetVccaux1.8V 或 2.0VVCCBRAMXAdcPs_GetVccbram1.0V外部模拟输入通道则是另一套换算满量程取决于你配的模拟输入模式和 PGA 增益不是这个 3V 公式。用 XAdcPs 驱动读外部通道时需要先用 XAdcPs_SetSeqChMode 把序列切到连续模式然后逐个通道读 XAdcPs_GetADCData再按该通道的增益系数换算。如果只是做板卡内部电源监控3V 公式就够用外部通道的校准可以等真正用到时再做不要在前期被它带偏。4.3 完整 main.c轮询采集并串口打印把上面的碎片拼成一个可以跑起来的 main#include xadcps.h #include xparameters.h #include xil_printf.h #include sleep.h #define XADC_DEVICE_ID XPAR_XADCPS_0_DEVICE_ID static XAdcPs XAdcInst; static float adc_temp_to_celsius(u32 code) { return (code * 503.975f) / 4096.0f - 273.82f; } static float adc_volt(u32 code) { return (code * 3.0f) / 4096.0f; } int main(void) { XAdcPs_Config *Config; int Status; Config XAdcPs_LookupConfig(XADC_DEVICE_ID); if (Config NULL) return -1; Status XAdcPs_CfgInitialize(XAdcInst, Config, Config-BaseAddress); if (Status ! XST_SUCCESS) return -1; XAdcPs_SetSeqChMode(XAdcInst, XADCPS_SEQ_CH_SAFE_MODE); XAdcPs_SetSeqInputMode(XAdcInst, XADCPS_SEQ_INPUT_MODE_SAFE); while (1) { u32 raw_t XAdcPs_GetTemperature(XAdcInst); u32 raw_vi XAdcPs_GetVccint(XAdcInst); u32 raw_va XAdcPs_GetVccaux(XAdcInst); xil_printf(T%.2f C VCCINT%.3f V VCCAUX%.3f V\r\n, adc_temp_to_celsius(raw_t), adc_volt(raw_vi), adc_volt(raw_va)); usleep(500000); } return 0; }几个说明。xil_printf 在 Standalone BSP 里默认输出到 stdout而 stdout 在 BSP 里已经绑定到你在 Vivado 里配置的 UART连上板子的串口终端就能看到输出。如果串口没有任何打印先检查 BSP 的 stdout 配置和你的调试串口是否一致。usleep(500000) 是 500ms 一次轮询XADC 安全模式下内部转换一直在跑所以每次读取拿到的都是最近一轮的结果这里延时只是控制打印节奏。两个换算函数都显式用了 float 运算raw 值先乘再除保证小数部分不丢。4.4 提高采集稳定性均值滤波与报警阈值XADC 原始数据在 12 位下多少会有点抖动尤其是电源轨附近有开关噪声的时候。xadcps 库提供了均值功能XAdcPs_SetAvgMode 可以设置平均次数比如 XADCPS_AVG_16_SAMPLES让硬件层面做 16 次累加平均。开启均值后读数更稳定代价是理论转换速率降低但内部传感器的采样速率本来就用不完平均之后反而更符合监控场景。XAdcPs_SetAvgMode(XAdcInst, XADCPS_AVG_16_SAMPLES);温度报警阈值也经常在这里一起配。XAdcPs_SetAlarmThreshold 可以设置高温上限XAdcPs_SetAlarmEnables 打开对应的报警输出。要注意报警中断默认是不使能的开了阈值还要在 GIC 里配中断对纯轮询场景可以直接在循环里比较换算后的温度值超过阈值就点亮一个 GPIO 或打印告警比折腾中断简单得多。5. 避坑XADC 读不到数据时的 5 种排查路径5.1 现象XAdcPs_CfgInitialize 返回 XST_FAILURE程序跑起来后卡在 Status 判断那里返回值不是 XST_SUCCESS。这种失败绝大多数不是代码问题而是驱动和硬件资源对不上BSP 侧没有生成 xadcps 库LookupConfig 拿不到配置或者 PL 侧例化了 xadc_wizard 抢占 XADC 控制权。解决方式按顺序排查先在 BSP 工程右键 Re-generate BSP Sources 并确认 system.mss 里出现 XAdcPs 条目再回到 Vivado 的 Block Design 删掉 xadc_wizard重新导出 hdf 后同步更新 SDK 工程。两步做完90% 的初始化失败都能收掉。5.2 现象温度值固定为 0x000 或 0xFFF温度读出来要么是 0要么是 4095换算结果要么是 -273°C 要么是几百上千度完全不在合理范围。原因通常是采样序列被改成了只采外部通道XADC 根本没有运到内部传感器另一类原因是时钟没起来安全模式下如果没有可用时钟转换结果就是无效满码。解决在读取前强制调用 XAdcPs_SetSeqChMode 和 XAdcPs_SetSeqInputMode 切回安全模式不要依赖上电默认值时钟问题要回头确认 PS 配置里是否意外禁用了 XADC 相关资源纯 PS 直驱模式用的是片内时钟一般不会涉及外部引脚但如果你在 PL 侧动过时钟约束就要多留个心眼。5.3 现象电压读数为真实值的一半VCCINT 原本 1.0V读出来只有 0.5V第一反应是板子供电坏了。其实更常见的是读错了通道XAdcPs_GetVccint 返回的是内部电源通道的转换结果这个通道在片内已经按 3V 满量程归一化。如果代码里绕过驱动直接读外部通道寄存器或者采样序列里根本没包含 VCCINT 通道拿到的当然是别的数据。解决确认用的是 xadcps 库提供的 XAdcPs_GetVccint / GetVccaux 系列函数并且采样序列保持在安全模式换算时用统一的 3V 公式单位不要搞错。5.4 现象BSP 里找不到 xadcps 驱动BSP 配置页 Peripherals 列表里 xadcps 是灰的或者根本没有这一项。原因是导出的硬件平台文件里没有携带 XADCPS 资源信息最常见的是 Vivado 工程 Block Design 改过但没有 Validate Design 和重新 Export HardwareSDK 拿到的还是老版本 hdf。解决回到 Vivado先在 Address Editor 里确认 PS 系统监视器地址段存在0xF8007000 附近然后重新 Export Hardware 覆盖旧文件SDK 侧对 BSP 工程右键 Re-generate BSP Sources再检查 system.mss。这步做完如果还没有去 Zynq PS 配置页面里看有没有把 XADC 相关的选项误关掉了。5.5 现象和 PL 例化的 xadc_wizard 同时使用时数据互相干扰PS 直驱和 PL 例化 xadc_wizard 在同一套逻辑里共存表现为 PS 侧读温度偶尔跳变PL 侧采集结果也不稳定。原因就是共用 XADC 硬核时没有仲裁两个控制器各写各的寄存器配置序列被反复覆盖。解决要么拆除其中一个要么在 XADC 配置寄存器里明确设置仲裁优先级。SDK 的 xadcps 驱动默认不处理双控制器模式需要你自己操作 XADC 控制寄存器里的仲裁位。对绝大多数 7010 项目二选一是最省心的做法别让两个控制器同时碰这个硬核。6. 把 XADC 数据收进帧串口协议与连续打印技巧6.1 串口帧结构轮询结果直接打印给人看没问题但给上位机解析就有点难受了。我一般会把温度和电压放大 1000 倍转成整数按固定帧格式发出去上位机收到再还原物理量省得在串口里传浮点字符串。帧结构简单可靠即可字节内容0帧头 0xAA1帧头 0x552温度高字节3温度低字节4VCCINT 高字节5VCCINT 低字节6VCCAUX 高字节7VCCAUX 低字节8累加校验和6.2 发送代码与验证void send_xadc_frame(u32 raw_t, u32 raw_vi, u32 raw_va) { u8 buf[9]; u8 sum 0; buf[0] 0xAA; buf[1] 0x55; buf[2] (raw_t 8) 0xFF; buf[3] raw_t 0xFF; buf[4] (raw_vi 8) 0xFF; buf[5] raw_vi 0xFF; buf[6] (raw_va 8) 0xFF; buf[7] raw_va 0xFF; for (int i 0; i 8; i) sum buf[i]; buf[8] sum; for (int i 0; i 9; i) XUartPs_Send(UartInst, buf[i], 1); }XUartPs_Send 是 BSP 里 UART 驱动提供的发送接口UartInst 换成你工程里初始化好的实例即可。注意 UART 波特率不需要太高115200 在 7010 上足够稳定数据量本身也不大。上位机按帧头 0xAA 0x55 做同步校验和不匹配直接丢帧这种简单协议比打印字符串好解析得多后续要扩展外部通道采集结果往帧里加字节就行。从那以后我每次拿到一块新的 ZYNQ 7010 板子只要 PS 能启动第一件事就是先跑一遍这套 XADC 驱动确认温度在合理范围、各路供电电压正常再往下做业务逻辑。硬件问题在早期暴露出来后面能省出大把排障时间。这套流程对最小系统验证、热设计评估和电源完整性粗查都很实用希望帮到你。本文还有配套的精品资源点击获取
返回列表