第一次接触PYNQ-Z2开发板时,最让我惊喜的不是它的可编程逻辑部分,而是那颗集成的XADC模块——不需要任何外部电路,就能实时监控芯片的健康状态。这就像给开发板装上了"生命体征监测仪",而我们要做的,就是学会如何读取这些关键数据。
XADC(Xilinx Analog-to-Digital Converter)是ZYNQ芯片中一个经常被初学者忽略的硬件宝藏。这个硬核模块包含两个12位1MSPS的ADC,能同时采样多个模拟信号源。与普通MCU内置ADC不同,XADC的特殊之处在于:
实际项目中,XADC数据对排查硬件问题特别有用。我曾通过它发现过一块开发板的3.3V电源实际输出只有3.0V,避免了后续的稳定性问题。
XADC的测量对象主要分为三类:
| 测量类型 | 通道编号 | 典型值范围 | 转换公式 |
|---|---|---|---|
| 结温 | 0 | 0-85°C | 503.975 - raw*0.12207 |
| VCCINT电压 | 1 | 0.95-1.05V | raw*3.0/4096 |
| VCCAUX电压 | 2 | 2.85-3.3V | raw*3.0/4096 |
| VCCBRAM电压 | 6 | 0.95-1.05V | raw*3.0/4096 |
使用Vivado 2023.1和PYNQ-Z2开发板,我们需要创建一个最简工程来验证XADC功能。不同于常规FPGA工程,这个实验完全在PS端运行,不需要生成bitstream文件。
硬件配置步骤:
tcl复制# 保存硬件设计后执行的Tcl命令
validate_bd_design
generate_target all [get_files ./project_1.srcs/sources_1/bd/design_1/design_1.bd]
make_wrapper -files [get_files ./project_1.srcs/sources_1/bd/design_1/design_1.bd] -top
关键点在于理解PS-XADC接口的访问机制:
在Vitis IDE中新建Application Project,选择刚才导出的硬件平台。XADC的SDK驱动已经包含在BSP中,我们只需关注应用层开发。
核心API解析:
c复制#include "xadcps.h"
// 初始化序列器模式
XAdcPs_SetSequencerMode(&XAdcInst, XADCPS_SEQ_MODE_CONTINPASS);
// 读取温度原始值
u32 raw_temp = XAdcPs_GetAdcData(&XAdcInst, XADCPS_CH_TEMP);
// 转换为实际温度值
float actual_temp = XAdcPs_RawToTemperature(raw_temp);
完整的数据采集函数应该包含错误处理和单位转换:
c复制void read_system_monitor(XAdcPs *InstancePtr) {
u32 raw_data;
float converted;
// 温度采集
raw_data = XAdcPs_GetAdcData(InstancePtr, XADCPS_CH_TEMP);
if (raw_data == XADCPS_MAX_DATA) {
xil_printf("Temperature sensor error!\r\n");
} else {
converted = XAdcPs_RawToTemperature(raw_data);
printf("Core Temp: %.2f C\t", converted);
}
// VCCINT电压采集
raw_data = XAdcPs_GetAdcData(InstancePtr, XADCPS_CH_VCCPINT);
converted = XAdcPs_RawToVoltage(raw_data);
printf("VCCINT: %.3f V\r\n", converted);
}
实际开发中容易遇到的三个坑:
XAdcPs_SetSeqInputMode()配置基础功能实现后,我们可以进一步开发更实用的监控系统。以下是几种典型应用场景的实现方法:
实时报警系统:
c复制// 设置温度阈值(85°C)
#define OVERHEAT_THRESHOLD 0x1A0 // 对应约85°C
void setup_alarms(XAdcPs *InstancePtr) {
XAdcPs_SetAlarmEnables(InstancePtr, XADCPS_ALM_TEMP_MASK);
XAdcPs_SetThreshold(InstancePtr, XADCPS_OT_UPPER, OVERHEAT_THRESHOLD);
}
// 在中断服务程序中检查报警状态
void XADC_IRQHandler(void) {
u32 status = XAdcPs_GetStatus(&XAdcInst);
if (status & XADCPS_ALM_TEMP_MASK) {
printf("[ALERT] Chip overheat detected!\r\n");
// 触发保护措施...
}
}
低功耗定时采样方案:
c复制// 配置单次采样模式
XAdcPs_SetSequencerMode(&XAdcInst, XADCPS_SEQ_MODE_SINGCHAN);
while(1) {
// 启动单次转换
XAdcPs_SetSequencerMode(&XAdcInst, XADCPS_SEQ_MODE_SINGCHAN);
usleep(100000); // 等待转换完成
// 读取数据
float temp = XAdcPs_RawToTemperature(
XAdcPs_GetAdcData(&XAdcInst, XADCPS_CH_TEMP));
printf("One-shot reading: %.2f C\r\n", temp);
sleep(10); // 每10秒采样一次
}
对于需要更高精度的场合,可以考虑以下优化手段:
单纯的串口输出不便于长期监测,我们可以用Python构建一个简单的远程监控系统:
PYNQ端代码(通过PS端UART):
python复制from pynq import Overlay
import serial
import time
ser = serial.Serial('/dev/ttyPS0', 115200)
while True:
data = ser.readline().decode().strip()
if 'Temp' in data:
with open('/tmp/xadc_log.csv', 'a') as f:
f.write(f"{time.time()},{data.split(':')[-1]}\n")
PC端可视化脚本(matplotlib):
python复制import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def update_graph(i):
try:
df = pd.read_csv('xadc_log.csv', names=['time','temp'])
plt.cla()
plt.plot(df['time'], df['temp'], 'r-')
plt.ylabel('Temperature (°C)')
plt.tight_layout()
except:
pass
ani = FuncAnimation(plt.gcf(), update_graph, interval=1000)
plt.show()
这种架构的扩展性很强,后续可以:
结合前面所学,我们构建一个完整的温度调控系统。当芯片温度超过阈值时,自动调整PL端PWM风扇转速。
硬件连接:
软件架构:
c复制// PID控制器简化实现
float pid_control(float current, float target) {
static float integral = 0;
static float last_error = 0;
float error = target - current;
integral += error * 0.1; // 积分项
float derivative = (error - last_error) / 0.1; // 微分项
last_error = error;
return error*0.5 + integral*0.01 + derivative*0.05; // PID系数
}
void fan_control() {
float temp = get_current_temp();
float duty = pid_control(temp, 60.0); // 目标温度60°C
// 限制PWM占空比范围
duty = (duty < 0) ? 0 : (duty > 100) ? 100 : duty;
set_pwm_duty(duty);
printf("Temp: %.1fC -> Fan: %.0f%%\r\n", temp, duty);
}
在PYNQ-Z2上实测发现,这套系统可以将芯片温度稳定控制在±2°C范围内,比固定转速方案节能30%以上。