米联客 ZYNQ/SOC精品教程 S02-CH03 XADC 实验

╄→尐↘猪︶ㄣ 提交于 2019-11-28 17:34:18

软件版本:VIVADO2017.4

操作系统:WIN10 64bit

硬件平台:适用米联客 ZYNQ系列开发板

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!

3.1 概述

       本课讲解了使用芯片内部XADC采集片上电压以及温度的方法。

       Xilinx 7系列的ADC是一个双12位分辨率的而且每秒一兆(MSPS, 1 Mega sample per second)采样速率的模数转换器,是一种通用的、高精度的模数转换器,双通道的ADC支持单极和差分输入工作模式,其最多可支持17路外部模拟输入通道。

      上图为XADC的一个内部实现框图,从图中可以看出ADC分为12位的A和B两个,故称为双12位。其中ADC A可以对供电电压进行采样,供电电压包括VCCINT、VCCAUX、VCCBRAM,其中Zynq-7000系列的芯片还支持对VCCPINT、VCCPAUX和VCCO_DDR的采样,还包括温度的采样和外部模拟输入的采样。ADC B只能对外部模拟输入进行采样转换。

       XADC内部包括16位的控制寄存器和状态寄存器,控制寄存器可以通过DRP(DynamicReconfiguration Port)进行读写操作,从而实现对XADC的初始化配置,而状态寄存器只能进行读取,ADC将采样转换后的值保存在对应的状态寄存器,通过DRP便可以将其读出,从而便可以用于FPGA外部的使用。

      模拟输入管脚XADC可以采样的外部模拟输入包括一对差分专用模拟输入信号端(Dedicated Analog)和16对差分辅助模拟输入信号端(Auxiliary Analog),其中16对模拟信号端在XADC中不被使用时,可以当做普通IO口来使用。辅助模拟输入信号在Vivado和ISE中使用是不同的:在ISE中辅助模拟输入不需要任何的用户定义约束(包括管脚位置约束和IO电平约束),而在Vivado里,辅助模拟输入管脚必须加管脚位置约束,而且必须选择合适的IO电平约束。有些辅助输入管脚对7系列的某些芯片是不支持的,可以通过原理图来查看哪些管脚可以使用,我们的开发板只有核心板引出了16对差分辅助模拟输入信号端。而在底板上没有专门引出,所以开发板,无法直接做16对差分辅助模拟输入信号端的ADC实验。所以本实验仅仅对FPGA内部的温度,电压做采集。

       在实际工程运用中,开发人员可以通过片上XDC测试芯片内部电压,判断系统是否正常工作或预警。

3.2 采集参数

使用ZYNQ的内嵌XADC采集ZYNQ内部的一些参数:

•VCCINT:内部PL核心电压

•VCCAUX:辅助PL电压

•VREFP:XADC正参考电压

•VREFN:XADC负参考电压

•VCCBram:PL  BRAM电压

•VCCPInt:PS内部核心电压

•VCCPAux:PS辅助电压

•VCCDdr:DDR RAM的工作电压

3.3 搭建FPGA BD工程

Step1:新建一个名为为Miz_sys的工程。

Step2:创建一个BD文件,并命名为system,添加并且配置好ZYNQ IP。读者需要根据自己的硬件类型配置好输入时钟频率、内存型号、串口,连接时钟等。新手不清楚这些内容个,请参考“CH01 HelloWold/DDR/网口测试及固化”这一节课。

Step3:添加IP按钮,输入xadc,添加XADC的IP到BD文件。

Step4:需对XADC IP进行配置

采样方式采用AXI4Lite,连续采样模式,Channel Sequencer模式,DCLK设置100M

Sequencer模式为连续模式

报警部分采用默认设置

勾选我们需要采集的参数

Step5:直接单击run connection automation,选择OK,完成整体电路设计,自动运行后,软件自动添加了Processor System Reset IP和AXI Interconnect IP。

Step6:单击窗口上的运行按钮,运行程序。

3.6 测试结果

系统运行结果如下图所示:

3.7 函数分析

3.7.1 XAdcPs_LookupConfig函数

ConfigPtr = XAdcPs_LookupConfig(XPAR_AXI_XADC_0_DEVICE_ID),这种库函数的调用都是xilinx SDK库函数标准套路,就是从系统中查下,是否有这个设备的定义。进入这个函数原型看下

XAdcPs_Config *XAdcPs_LookupConfig(u16 DeviceId)

{

XAdcPs_Config *CfgPtr = NULL;

u32 Index;

 

for (Index=0; Index < 1; Index++) {

if (XAdcPs_ConfigTable[Index].DeviceId == DeviceId) {

CfgPtr = &XAdcPs_ConfigTable[Index];

break;

}

}

return CfgPtr;

}

可以看到这个函数原型,就是查表看是否有相关的设备描述XAdcPs_ConfigTable的定义如下,可以看到具备这个设备的描述,以及这个设备在ZYNQ 地址空间中的地址,这样就可以把地址复制给CfgPtr设备指针了。记住xilinx的库函数都是这个套路。

XAdcPs_Config XAdcPs_ConfigTable[XPAR_XADCPS_NUM_INSTANCES] =

{

{

XPAR_PS7_XADC_0_DEVICE_ID,

XPAR_PS7_XADC_0_BASEADDR

}

};

3.7.2 XAdcPs_CfgInitialize函数

Status_ADC = XAdcPs_CfgInitialize(XADCInstPtr,ConfigPtr,ConfigPtr->BaseAddress),根据前面找到的设备地址空间,可以对设备的寄存进行配置,这个也是XILINX标准的套路。

int XAdcPs_CfgInitialize(XAdcPs *InstancePtr, XAdcPs_Config *ConfigPtr,

u32 EffectiveAddr)

{

u32 RegValue;

/*

 * Assert the input arguments.

 */

Xil_AssertNonvoid(InstancePtr != NULL);

Xil_AssertNonvoid(ConfigPtr != NULL);

/*

 * Set the values read from the device config and the base address.

 */

InstancePtr->Config.DeviceId = ConfigPtr->DeviceId;

InstancePtr->Config.BaseAddress = EffectiveAddr;

 

/* Write Unlock value to Device Config Unlock register */

XAdcPs_WriteReg(XPAR_XDCFG_0_BASEADDR,

XADCPS_UNLK_OFFSET, XADCPS_UNLK_VALUE);

 

/* Enable the PS access of xadc and set FIFO thresholds */

 

RegValue = XAdcPs_ReadReg((InstancePtr)->Config.BaseAddress,

XADCPS_CFG_OFFSET);

 

RegValue = RegValue | XADCPS_CFG_ENABLE_MASK |

XADCPS_CFG_CFIFOTH_MASK | XADCPS_CFG_DFIFOTH_MASK;

 

XAdcPs_WriteReg((InstancePtr)->Config.BaseAddress,

XADCPS_CFG_OFFSET, RegValue);

 

/* Release xadc from reset */

 

XAdcPs_WriteReg((InstancePtr)->Config.BaseAddress,

XADCPS_MCTL_OFFSET, 0x00);

/*

 * Indicate the instance is now ready to use and

 * initialized without error.

 */

InstancePtr->IsReady = XIL_COMPONENT_IS_READY;

 

return XST_SUCCESS;

}

以上函数,我们先看第一个

XAdcPs_WriteReg(XPAR_XDCFG_0_BASEADDR,XADCPS_UNLK_OFFSET, XADCPS_UNLK_VALUE);

这个函数的3个参数如下:

#define XPAR_XDCFG_0_BASEADDR 0xF8007000U

#define XADCPS_UNLK_OFFSET  0x034 /**< Unlock Register */

#define XADCPS_UNLK_VALUE  0x757BDF0D /**< Unlock Value */

       我们看下ug585这个寄存器的说明,大概意思是该寄存器用于保护DEVCI配置寄存器免受ROM代码损坏。说明这个寄存器配置还是很重要的,那么在代码启动完成后,需要UNLOCK。写入0X757BDF0D可以解锁。

接下来首先读取XADCIF_CFG的值, XADCIF_CFG的寄存器描述如下。

RegValue = XAdcPs_ReadReg((InstancePtr)->Config.BaseAddress,

XADCPS_CFG_OFFSET);

之后重新设置XADCIF_CFG寄存器。

#define XADCPS_CFG_ENABLE_MASK  0x80000000 /**< Enable access from PS mask */

#define XADCPS_CFG_CFIFOTH_MASK  0x00F00000 /**< Command FIFO Threshold mask */

#define XADCPS_CFG_DFIFOTH_MASK  0x000F0000 /**< Data FIFO Threshold mask */

RegValue = RegValue | XADCPS_CFG_ENABLE_MASK |XADCPS_CFG_CFIFOTH_MASK | XADCPS_CFG_DFIFOTH_MASK;

XAdcPs_WriteReg((InstancePtr)->Config.BaseAddress,ADCPS_CFG_OFFSET, RegValue);

最后复位XADC XAdcPs_WriteReg((InstancePtr)->Config.BaseAddress,XADCPS_MCTL_OFFSET, 0x00);

3.7.3 XAdcPs_SetSequencerMode函数

XAdcPs_SetSequencerMode(XADCInstPtr,XADCPS_SEQ_MODE_SINGCHAN)在这个函数中,首先读取XADCPS_CFR1_OFFSET读取寄存器的值,然后设置这个寄存器,让XADC停止顺序模式。

                 RegValue = XAdcPs_ReadInternalReg(InstancePtr,

                                     XADCPS_CFR1_OFFSET);

                RegValue &= (~ XADCPS_CFR1_SEQ_VALID_MASK);

                RegValue |= ((SequencerMode  << XADCPS_CFR1_SEQ_SHIFT) &

                                     XADCPS_CFR1_SEQ_VALID_MASK);

               XAdcPs_WriteInternalReg(InstancePtr, XADCPS_CFR1_OFFSET,

                                    RegValue);

/*

 * XADC Configuration Registers

 */

#define XADCPS_CFR0_OFFSET 0x40 /**< Configuration Register 0 */

#define XADCPS_CFR1_OFFSET 0x41 /**< Configuration Register 1 */

#define XADCPS_CFR2_OFFSET 0x42 /**< Configuration Register 2 */

 

在ug480中这里有三个寄存器需要介绍下

以上代码只涉及到了reg 0x41,如下表所示,可以看到关于报警、校准、通道模式,的设置都是这个寄存器控制的。

再看下XADCPS_CFR1_OFFSET函数展开后

u32 XAdcPs_ReadInternalReg(XAdcPs *InstancePtr, u32 RegOffset)

{

u32 RegData;

RegData = XAdcPs_FormatWriteData(RegOffset, 0x0, FALSE);

/* Read cmd to FIFO*/

XAdcPs_WriteFifo(InstancePtr, RegData);

/* Do a Dummy read */

RegData = XAdcPs_ReadFifo(InstancePtr);

/* Do a Dummy write to get the actual read */

XAdcPs_WriteFifo(InstancePtr, RegData);

/* Do the Actual read */

RegData = XAdcPs_ReadFifo(InstancePtr);

return RegData;

}

这个地方完全难以理解,实际上和下面的图有关系,为了读取到寄存器的值,首先需要通过命令FIFO把需要读取读取寄存器的命令发送到cmd FIFO ,然后在读取 Data FIFO里面读取到的数。这个操作起来感觉好麻烦,XILINX datasheet资料上也没写清楚,由于全部提供的库函数,我们使用者,也不需要了解那么多。

所以我们要读写XADC的寄存器值,都需要通过命令FIFO 和数据FIFO。感觉好麻烦,幸好这个库函数都帮我们做好了。

再来看看XAdcPs_WriteInternalReg

void XAdcPs_WriteInternalReg(XAdcPs *InstancePtr, u32 RegOffset, u32 Data)

{

u32 RegData;

/*

 * Write the Data into the FIFO Register.

 */

RegData = XAdcPs_FormatWriteData(RegOffset, Data, TRUE);

XAdcPs_WriteFifo(InstancePtr, RegData);

/* Read the Read FIFO after any write since for each write

 * one location of Read FIFO gets updated

 */

XAdcPs_ReadFifo(InstancePtr);

}

下面还有一张表格,是关于XADC寄存器的,理解了这些,我们下面分析函数就要简单一些了。

3.7.4 XAdcPs_SetAlarmEnables

XAdcPs_SetSeqInputMode(XADCInstPtr, XADCPS_SEQ_MODE_SAFE);在这个函数中,首先读取XADCPS_CFR1_OFFSET读取寄存器的值,然后设置这个寄存器,让XADC停止报警。

#define XADCPS_CFR1_ALM_ALL_MASK 0x0F0F /**< Mask for all alarms */

            RegValue = XAdcPs_ReadInternalReg(InstancePtr, XADCPS_CFR1_OFFSET);

            RegValue &= (u32)~XADCPS_CFR1_ALM_ALL_MASK;

            RegValue |= (~AlmEnableMask & XADCPS_CFR1_ALM_ALL_MASK);

           /*

            * Enable/disables the alarm enables for the specified alarm bits in the

            * Configuration Register 1.

            */

            XAdcPs_WriteInternalReg(InstancePtr, XADCPS_CFR1_OFFSET,

                            RegValue);

3.7.5 XAdcPs_SetSeqInputMode

XAdcPs_SetSeqInputMode(XADCInstPtr, XADCPS_SEQ_MODE_SAFE);在这个函数中,设置XADC的ADC通道是双极性或者单极性采样.这里设置的为0所以为单极性采样。

#define XADCPS_SEQ04_OFFSET 0x4C /**< Seq Reg 04 Adc Input Mode Select */

#define XADCPS_SEQ05_OFFSET 0x4D /**< Seq Reg 05 Adc Input Mode Select */

另外注意当使用外部模拟输入信号的收,这连个寄存器和0X48 X049具备一样的功能。这个具体可以查阅ug480 本课程没有连接到外部。

XAdcPs_WriteInternalReg(InstancePtr,XADCPS_SEQ04_OFFSET,

                            (InputModeChMask & XADCPS_SEQ04_CH_VALID_MASK));

XAdcPs_WriteInternalReg(InstancePtr,XADCPS_SEQ05_OFFSET,

                            (InputModeChMask >> XADCPS_SEQ_CH_AUX_SHIFT) &XADCPS_SEQ05_CH_VALID_MASK);

3.7.6 XAdcPs_SetSeqChEnables

XAdcPs_SetSeqChEnables(XADCInstPtr,XADCPS_CH_TEMP|XADCPS_CH_VCCINT|XADCPS_CH_VCCAUX|XADCPS_CH_VBRAM|XADCPS_CH_VCCPINT| XADCPS_CH_VCCPAUX|XADCPS_CH_VCCPDRO); 设置需要采样的端口,可以看到实际这个位置把2个XADC都设置了。

#define XADCPS_SEQ00_OFFSET 0x48 /**< Seq Reg 00 Adc Channel Selection */

#define XADCPS_SEQ01_OFFSET 0x49 /**< Seq Reg 01 Adc Channel Selection */

XAdcPs_WriteInternalReg(InstancePtr,

                          XADCPS_SEQ00_OFFSET,

                          (ChEnableMask & XADCPS_SEQ00_CH_VALID_MASK));

XAdcPs_WriteInternalReg(InstancePtr,

                          XADCPS_SEQ01_OFFSET,

                         (ChEnableMask >> XADCPS_SEQ_CH_AUX_SHIFT) &

                         XADCPS_SEQ01_CH_VALID_MASK);

具体对应的通道如下表所示

3.7.7 XAdcPs_GetAdcData

这个函数就是读取具体的ADC通道的数值。

TempRawData = XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_TEMP);

TempData = XAdcPs_RawToTemperature(TempRawData);

 

VccIntRawData= XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_VCCINT);

VccIntData = XAdcPs_RawToVoltage(VccIntRawData);

 

VccAuxRawData = XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_VCCAUX);

VccAuxData = XAdcPs_RawToVoltage(VccAuxRawData);

 

VBramRawData = XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_VBRAM);

VBramData = XAdcPs_RawToVoltage(VBramRawData);

 

VccPIntRawData = XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_VCCPINT);

VccPIntData = XAdcPs_RawToVoltage(VccPIntRawData);

 

VccPAuxRawData = XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_VCCPAUX);

VccPAuxData = XAdcPs_RawToVoltage(VccPAuxRawData);

 

VDDRRawData = XAdcPs_GetAdcData(XADCInstPtr, XADCPS_CH_VCCPDRO);

VDDRData = XAdcPs_RawToVoltage(VDDRRawData);

 

XAdcPs_GetAdcData函数读取通道的数据,函数原型如下

u16 XAdcPs_GetAdcData(XAdcPs *InstancePtr, u8 Channel)

{

u32 RegData;

/*

 * Assert the arguments.

 */

Xil_AssertNonvoid(InstancePtr != NULL);

Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

Xil_AssertNonvoid((Channel <= XADCPS_CH_VBRAM) ||

 ((Channel >= XADCPS_CH_VCCPINT) &&

 (Channel <= XADCPS_CH_AUX_MAX)));

RegData = XAdcPs_ReadInternalReg(InstancePtr,

(XADCPS_TEMP_OFFSET +

Channel));

return (u16) RegData;

}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!