很多第一次拿到 Zynq UltraScale MPSoC 开发板的工程师开机第一件事往往是点个灯、跑个 Hello World然后就不知道该干点什么了。其实这块片子最值钱的内设之一是它自带的一套片上监控系统——SYSMON。它干什么用一句话实时告诉你芯片的结对温度是多少、VCCINT/VCCAUX/PS 各电源轨电压稳不稳外部模拟通道还能挂几路传感器。做工业控制、车载、医疗、通信板卡的工程师迟早会跟它打交道。这套系统不是锦上添花而是可靠性设计的底座。7nm 工艺器件的热密度很高十几路电源轨一路一路地分压供电任何一路掉链子轻则跑飞重则烧毁。SYSMON 的价值就是让你在芯片内部直接读到结温和电源电压不用外挂一大排 ADC也不用在 PCB 上拉一堆采样线。这篇文章我就按自己实际调板的经验把 Zynq UltraScale MPSoC 的 SYSMON 从原理到落地讲一遍怎么选通路、怎么读数据、怎么换算、怎么设报警以及常见的坑。1. 为什么一片SoC要内置一套“体检系统”先想一个问题为什么不在板子上放一个外部 ADC再用 I2C 去读温度传感器成本倒还在其次关键是精度和响应。外部传感器贴在 PCB 上、封装外面离真正的热源——芯片内部晶体管——隔了一层封装和 PCB温度差个 10°C 都是正常的。SYSMON 把温度传感器直接做在芯片内部靠近最热的核心区域测到的是结温Junction Temperature这才是真正决定器件寿命和稳定性的物理量。电压也一样外部用万用表量到的只是 PCB 上的电压而芯片内部核心电压因为 IR drop 可能已经掉了几十毫伏。1.1 SYSMON不是ADC是片上健康管理单元很多人把 SYSMON 理解成“一颗内置 ADC”这个理解太窄了。它实际上是一个完整的监测子系统包含几块东西一是温度传感器分布在硅片关键位置二是电压采样通道通过片内分压网络把各路电源引到 ADC 输入三是一颗 12 位 SAR ADC负责把模拟量换成数字量四是一组报警比较器可以设置高低温、高低压阈值超过阈值直接产生中断五是寄存器接口让 PL、PS、JTAG 三条通路都能访问。我习惯把它类比成人的体检设备温度通道是体温计电压通道是血压计报警逻辑就是异常告警的护士。平常你不用盯着它自己在那儿周期性地量一旦超标它主动喊你。这个“主动喊”的能力在工程上特别重要因为很多故障要等到系统已经挂了你才反应过来而 SYSMON 可以在故障发生早期就给你中断。1.2 都哪些场景在靠它“续命”Zynq UltraScale MPSoC 被用在哪些场景我自己接触过和看到的典型应用包括工业机器视觉与运动控制、医疗影像设备、汽车智驾域控制器、5G 小站和回传设备、数据中心智能网卡、以及一些仿真加速平台。这些场景有个共同点器件长时间高负载运行环境温度可能很恶劣——工业现场 60°C 的环境温度不稀奇车载舱内更是如此。这时候如果没有结温监控你根本不敢把器件推到标称性能。另外一个高频问题就是 DDR4。很多人搜“zynq ddr4参数详细参数”把地址映射、数据位宽、时序参数调得很仔细却忽略了 DDR4 供电轨的稳定性。PS 侧 DDR 控制器对电源纹波和压降极其敏感高温下 1.2V 的 VCC_PSDDR 一旦掉到阀值以下就会出现随机 ECC 错误或者系统死机。用 SYSMON 的对应电压通道去监控这个轨是排查这类问题最直接的手段。所以别把 SYSMON 当成调试期的玩具量产设备里它是必不可少的“看门人”。2. 架构与关键参数动手前必须先弄懂的几件事2.1 12位SAR ADC精度到底够不够SYSMON 内部这颗 ADC 是 12 位逐次逼近型SAR采样率最高 1MSPS。第一次看手册的人都会问监控温度电压而已为什么不用 16 位甚至 24 位的道理很简单监控和测量是两码事。测量仪器追求绝对精度监控系统追求的是趋势判断和阈值比较。12 位分辨率下温度通道的量化步进大约是 0.123°C电压通道的分辨率取决于全量程Full Scale RangeFSR一般每 LSB 对应不到 1mV。这个精度做报警和趋势分析绰绰有余。而且 SAR ADC 结构简单、转换延迟低、功耗小适合持续采样。当然12 位是原始分辨率实际有效位数ENOB会因为噪声、参考电压抖动而略低。所以读出来的数据不要过度解读小数点后面你要关注的是变化趋势和越限行为而不是追求绝对精确到 0.01°C。2.2 内部电源通道、温度通道和外部模拟通道怎么分工SYSMON 的通道可以分成三类。第一类是温度通道直接测量芯片结温第二类是内部电源通道包括 VCCINT、VCCAUX、VCCBRAM以及 PS 侧的 VCC_PSINTLP、VCC_PSINTFP、VCC_PSAUX、VCC_PSDDR 等第三类是外部模拟输入通道通常叫 VAUXP/VAUXN是一组差分输入引脚可以接外部传感器或分压后的被测电压。内部电源通道的测量范围是出厂校准好的你只需要读原始码再按公式换算。外部通道则要注意输入电压不能超过引脚允许范围超过就要用电阻分压。关于内部通道不同器件、不同封装的全量程值会略有差异一定要以 UG580UltraScale Architecture System Monitor 用户指南里面的表格为准不要拿着 7 系列 XADC 的手册数据硬套。通道类型测量对象说明温度芯片结温传感器在片内直接读VCCINTPL 核心逻辑电源片内分压后采样VCCAUX辅助电源同上VCCBRAMBlock RAM 电源同上PS 电源轨VCC_PSINTLP/VCC_PSINTFP/VCC_PSAUX/VCC_PSDDR由 PS 侧 SYSMON 实例监控VAUX 外部通道用户自定义模拟量差分输入注意量程2.3 PL与PS两套监控链路千万别搞混Zynq UltraScale MPSoC 里其实有不止一个 SYSMON 实例。PL 侧有一个负责监控 FPGA 逻辑相关的电源轨和温度PS 侧也有一个挂在 PMUPlatform Management Unit下面负责监控 PS 电源轨、PS 温度以及部分片上传感器。这两个实例彼此独立寄存器空间也不同。这个区别在实际开发中特别容易踩坑。你如果在 Linux 下读到了温度那大概率是 PS 侧 SYSMON 的数据而 PL 侧的温度要么通过 PS 去访问 DRP 寄存器要么在 PL 里例化 System Management Wizard 用 AXI 去读。两边都有温度通道但读数可能不一样——PL 和 PS 的发热源不同硅片位置也不同。所以做监控面板时要标注清楚你读的是哪个实例的数据不要混在一起做成一条曲线。3. 三种通路实测Linux、裸机与PL侧IP3.1 准备一块能跑的基础平台与烧写流程我以最常见的 ZCU104 或者自研的 Zynq UltraScale 板卡为例。硬件上你需要一块能正常启动的板子、一条 JTAG 下载器、一张 SD 卡如果走 SD 启动。软件上建议用 Vivado Vitis 2021.1 以后的版本内核用 Xilinx 官方 BSP 或者你自己编译的 Linux。启动镜像的老问题——也就是网上总有人搜“zynq烧写”——流程通常是这样的先做一个 BOOT.BIN里面包含 FSBL、PMUFW、ATF、U-Boot然后再加上内核镜像和设备树做成 SD 卡启动或者烧到 QSPI Flash。这里提醒一句很多人在 Vitis 里改完裸机程序发现板上跑的还是旧程序原因就是新 ELF 没有打进 BOOT.BIN或者 BOOT.BIN 虽然更新了但 Flash 没擦干净。做 SYSMON 实验也一样确认你调试的监控程序确实被加载了再开始看数据。3.2 Linux方案一条cat命令看到结温如果目标板跑的是 Linux读 PS 侧 SYSMON 最简单。Xilinx 内核里有一个 IIO 驱动设备树里一般会有一个 SYSMON 节点。/ { sysmon: sysmon { compatible xlnx,zynqmp-sysmon-1.0; }; };实际 BSP 里节点位置和 compatible 可能随版本不同以你的内核设备树为准。驱动加载成功后会在/sys/bus/iio/devices/下生成设备节点然后直接读ls /sys/bus/iio/devices/ cat /sys/bus/iio/devices/iio:device0/in_temp0_input cat /sys/bus/iio/devices/iio:device0/in_voltage0_rawin_temp 开头的通道一般返回的是已经处理过的温度值单位是毫摄氏度。也就是说45250表示 45.25°C。电压通道可能同时提供 raw 和 processed 两种属性raw 是 ADC 原始码processed 是直接换算好的电压值。用 cat 跑一下就能看到非常直观。在生产环境里我更推荐用脚本周期性地把这些值拉出来写进日志或者推给监控平台。比如 5 秒读一次温度、10 秒记录一次电压连续跑几个小时基本就能画出这块板子的热特性曲线。3.3 BareMetal方案在FSBL或独立应用里拿数据Linux 还没起来或者系统不允许跑 Linux 的时候就要用裸机驱动。Xilinx 的 BSP 里提供 PS SYSMON 驱动在 Vitis 里创建 standalone 工程后可以在 BSP 里勾选xsysmonps然后这样初始化并读取#include xsysmonps.h static XSysmonPs SysMonInst; int SysMon_Init(void) { XSysmonPs_Config *CfgPtr; CfgPtr XSysmonPs_LookupConfig(XPAR_XSYSMONPS_0_DEVICE_ID); if (CfgPtr NULL) { return XST_FAILURE; } XSysmonPs_CfgInitialize(SysMonInst, CfgPtr); return XST_SUCCESS; } float ReadTemp(void) { return XSysmonPs_GetTemperature(SysMonInst); } float ReadVccInt(void) { return XSysmonPs_GetVoltage(SysMonInst, XSYSMONPS_CH_VCCINT); }注意不同 Vitis 版本的 BSP 里枚举名和函数名可能会有细微差别编译报错时打开xsysmonps.h对照一下就行。裸机方式的优点是可以放在 FSBL 阶段就跑起来在 Linux 或你的应用程序启动之前先完成一轮自检温度是否过高、关键电源轨是否正常。不过任何不通过就不继续启动后面的步骤。这个逻辑在车载和工业设备上是硬性要求。3.4 PL方案System Management Wizard与AXI读取要在 PL 侧做监控最常见的是在 Vivado 里例化 System Management Wizard 这个 IP。配置的时候有几个关键点一是选择你要启用的通道内部电源通道按需勾选外部通道如果用过要单独 Enable二是采样模式建议选 continuous让 ADC 自己一直循环采样而不是靠软件逐个触发三是如果你要接外部传感器差分输入引脚要在约束文件里分配好。IP 例化后会自动生成一个 AXI 接口你也可以用 DRP 接口让 PS 来读。假设你把 IP 挂到某个 AXI 从端口基地址是 0xA0000000那么在裸机或 Linux 驱动里就可以这样读#include xil_io.h #define SYSMON_BASEADDR 0xA0000000 #define SYSMON_REG_TEMP 0x00 #define SYSMON_REG_VCCINT 0x04 static u32 SysMon_ReadChannel(u32 Offset) { u32 val Xil_In32(SYSMON_BASEADDR Offset); return (val 0xFFF0) 4; } float SysMon_ReadTempC(void) { u32 raw SysMon_ReadChannel(SYSMON_REG_TEMP); return (raw * 503.975f) / 4096.0f - 273.15f; } float SysMon_ReadVccInt(void) { u32 raw SysMon_ReadChannel(SYSMON_REG_VCCINT); return (raw * 1.0f) / 4096.0f; }注意上面代码里的 12 位数据是左对齐放在寄存器的 [15:4] 位所以要先右移四位这个对齐方式在多数 XADC/System Monitor 移植版本上是一致的但保险起见还是要看你 Vivado 生成的 IP 手册里的寄存器说明。VCCINT 的换算我在代码里用了 1.0V 做示例实际全量程值要查 UG580我后面会再说。PL 侧方案适合做 FPGA 逻辑自己的健康信息上报比如把温度、电压打包进 PCIe 的配置空间或者通过 UART 直接输出到调试串口。它不依赖 PS 操作系统PL 上电配置完成后就能工作。3.5 报警阈值与中断联动SYSMON 的报警功能是它区别于普通 ADC 的核心竞争力。报警的思路是预设一组上限和下限比如温度上限 100°C、下限 -10°CVCCINT 上限 0.95V、下限 0.70V。采样结果不断和这组阈值比较一旦越限就置位报警寄存器并产生中断。以 PS 侧裸机为例设置报警阈值通常是类似这样的调用XSysmonPs_SetThreshold(SysMonInst, XSYSMONPS_ALARM_TEMP_HIGH, 100.0f); XSysmonPs_EnableAlarm(SysMonInst, XSYSMONPS_ALARM_TEMP_HIGH);然后在中断处理函数里去读报警状态寄存器判断是哪个通道、哪一类报警再执行风扇加速、频率降级或者直接整机断电。我建议报警阈值在开发阶段不要设得太激进。比如室温 25°C 时结温可能在 40°C 左右你如果设 45°C 报警那整个调试期间就一直在响。先记录一段时间正常运行的温度峰值再在这个峰值上加 20~30°C 的裕量作为正式阈值这个做法比较稳妥。4. 换算、标定与精度实战4.1 温度换算公式与一次实测核对SYSMON 温度通道的原始码和温度之间遵循 PTAT 传感器的一阶线性关系。官方手册给出的换算公式是[ T(°C) \frac{Raw \times 503.975}{4096} - 273.15 ]举个例子我某次在室温 26°C 的实验室里跑一个视频编解码负载从寄存器读到 Raw 2500。代入公式[ \frac{2500 \times 503.975}{4096} - 273.15 307.60 - 273.15 34.45°C ]这个 34.45°C 是结温比板载测温电阻贴在散热器上读到的 29°C 高出 5°C 左右完全合理。所以不要拿 SYSMON 的结温去和外壳温度做严格对比它们的物理含义不同。你要做的是建立一条“结温-负载-环境温度”的对应关系后续通过结温来推断系统热状态。4.2 电压换算与外部通道分压设计电压通道的换算比温度简单就是比例关系[ V \frac{Raw \times FSR}{4096} ]难点在于 FSR 到底是多大。UG580 里有一张表列出了每个内部通道的全量程电压。我强烈建议你打开手册找到你的具体型号那一行把每个通道的 FSR 抄下来写进头文件而不是凭感觉填。拿 VCCINT 举例如果 FSR 是 1.0V某次读到 Raw 3446那么电压就是 3446 × 1.0 / 4096 ≈ 0.841V和标称 0.85V 的差异来自电源纹波和 ADC 量化属于正常。外部通道的设计要更小心。VAUX 差分输入的量程通常较小被测信号如果来自 5V 或 12V 的分压网络必须仔细算电阻比例。我的习惯是先留足余量让被测电压在加到引脚前被限制到量程的 80% 以内再在 ADC 引脚前加一个 RC 低通滤波截止频率不要太高几十 kHz 就够用来滤掉电源开关噪声最后在输入端串联一个几百欧姆的保护电阻防止带电插拔时损伤引脚。4.3 出厂标定的几个土办法SYSMON 出厂时内部通道已经校准过但对精度要求高的应用我还是建议做一次板级标定。方法不复杂拿一块已经上电的板子用高精度万用表去量对应电源轨的测试点电压同时读 SYSMON 的原始码算出实际 FSR 和手册标称值的偏差。如果偏差是一个固定的比例系数就可以在软件里做增益修正如果偏差随温度漂移那就需要在高温和低温两个点分别标定做两点校正。外部通道更要标定。因为分压电阻本身有 1% 甚至 5% 的误差电阻温漂也会带来读数偏移。我的做法是在 PCIe 板卡或设备整机测试阶段用 CNC 或手动模式加载一个已知电压记录读数生成一张“实测-读数”对照表最后在固件里做线性校正。别嫌麻烦批量生产的板子电阻批次一换不标定的话电压读数能偏出 2% 以上这在某些电源监控场景是不可接受的。5. 现场问题排查与经验备忘5.1 高频问题速查表下面这些是我在实际调板过程中遇到过的、以及在社区里看到高频出现的问题整理成速查表方便你现场对照。现象可能原因处理办法温度读数一直是 0xFFF 或满码采样通道未使能或转换未启动检查 IP 配置是否开启 channel检查连续采样模式温度读数跳变剧烈波动几度ADC 转换结果未做平均软件里做多次采样取平均或开启过采样电压读数比万用表低几十毫伏板上走线压降或 FSR 配置错误核对 UG580 的 FSR用高精度表标定Linux 下找不到 IIO 设备节点设备树节点缺失或驱动未加载检查内核是否启用 xlnx-zynqmp-sysmon 驱动PS 和 PL 温度读数不一致两个不同实例测量位置不同区分两个实例的数据不要混用外部通道悬空时读满码引脚浮空导致采样输入不确定外部通道不用时禁用使用时接好差分对报警在启动阶段误触发阈值寄存器默认值或者上电瞬间电源未稳启动完成后延迟一段时间再使能报警5.2 我踩过的几个坑第一个坑在开发板的 PMU 固件里使能了温度报警但阈值设得太低。结果板子一加载大负载PMU 直接触发报警系统重启。当时排查了半天最后发现是报警阈值设置的是 60°C而这个板子满载时结温轻松到 75°C。所以报警阈值必须基于实测数据来定不能拍脑袋。第二个坑外部通道没有用的引脚我没有在 IP 里禁用导致它悬空。悬空引脚读出来的电压码是 0xFFF车里系统监控面板上多了一路 1.8V 的假报警。后来把所有不用的外部通道全部禁用问题消失。这个细节非常容易被人忽略。第三个坑跟“zynq ddr4参数详细参数”相关。调试一块自研板时DDR4 在常温下完全正常一跑高温环境就出 ECC 错误。用 SYSMON 量 VCC_PSDDR发现温度上来后该轨电压下降了接近 80mV已经低于 DDR4 规范允许的纹波范围。问题根源在供电模块的高温降额而不是 DDR 控制器参数。后来换了更大电流的 LDO 并调整 SYSMON 报警阈值问题解决。通过这件事我也意识到DDR4 的详细参数调试不能只看控制器侧还要看供电侧的实时监控。第四个坑烧写镜像后监控程序不生效。有一次我在 Vitis 里重新编译了一个包含物理层监控的应用准备通过“zynq烧写”流程把它烧到 QSPI Flash。结果重新启动后跑的还是旧程序读出来的温度和电压一直是初始化默认值。后来发现烧写工具只更新了应用分区而 BOOT.BIN 里的 FSBL 和 PMUFW 没有重新打包。PS 侧很多功能——包括 SYSMON 的初始化——依赖 PMUFW 的配合PMUFW 不更新监控初始化路径就不对。改完重新打包烧写一次通过。5.3 监控数据还能怎么用温度和电压数据读出来了别只停留在“看一眼”的层面。我做过几个比较实用的扩展第一是风扇调速用结温做闭环输入替代固定转速温度低时安静温度高时全速运转对设备噪音和寿命都有好处第二是动态降频在温度接近报警阈值时通过 Linux 的 CPUFreq 或 PS 的 DVFS 接口把频率降一档让系统在不过热的前提下继续运行而不是直接关机第三是寿命预测连续记录结温和时间做一个简单的 Arrhenius 寿命估算模型配合 RTC 时间戳可以预估器件在设备中的老化速度。数据上报那块也值得一提。如果你做的是远程运维设备把 SYSMON 数据通过 UART、以太网或者 PCIe 传到上位机或云端就可以在设备还没彻底挂掉之前提前预警。我见过不少设备故障日志里最后一条就是 SYSMON 报出的电压越限记录这一条数据往往能省下好几天的排查时间。我在实际做这套东西的时候最大的体会是SYSMON 的数据不复杂难的是把它当成一个系统来用。你先得确认自己读的是 PL 还是 PS 的实例再花半天时间把每个通道的 FSR、换算公式、报警阈值全部记录成一张表最后把这些值和中断、风扇、降频、日志联动起来。这样一套下来你的板子才算真正有了“自我感知”的能力。下次调板子遇到高温死机或者电源异常的疑难问题不妨先看一眼 SYSMON——很多时候答案早就写在寄存器里了。