Jump to content
  • 0

XADC Example with Vivado block diagram?


engrpetero

Question

I've got a Zybo Z7-10 and am trying to use the JA port to monitor some 0-1V voltages (looking at the Zybo product page, it looks like I could monitor 8 single ended voltages?).

I've downloaded the XADC example project (which was made some time ago and doesn't contain a Vivado block diagram - or at least if it does, I'm not setting up the project correctly with my Vivado version) but I have looked at the project's constraints file.  FWIW - it doesn't look much different than the xdc 'base' file I download for the Zybo.

In my test project, I instantiate the XADC Wizard customize the component for my test purposes, including use of the channel sequencer and 2 of the aux channels (as well as the calibration and temperature channels).

image.thumb.png.a8684df2962c4b5ed1ceb921c70a1b12.png

I'm at a loss as to how to connect my external ports to the XADC IP though.

image.png.bb1ea3f0ab3db2e0122cf1d467a73f7b.png

In my constraints file...

image.thumb.png.e38c120f6eb80f7cdd57bb8d82aab0b3.png

Implementation produces errors...

image.thumb.png.79d5758aa0d52c2a1a56cac49fd3af2d.png

I continue to do online research to address the error but thought someone here might point me to a helpful article/link...

Link to comment
Share on other sites

19 answers to this question

Recommended Posts

  • 0

The Zybo's demo is HDL-only, so it's correct that you aren't seeing a block design. The Cora Z7's XADC demo (another Digilent Zynq board) involves setting up an XADC IP with an AXI interface to the processor in a block design: https://digilent.com/reference/programmable-logic/cora-z7/demos/xadc. Screenshots are attached. Specifics of the XADC configuration will differ, but it hopefully could help show you how to connect and constrain things. I'm not positive but suspect that you should connect the Vaux0 and Vaux1 interfaces externally, rather than the individual pins, and constrain both the _p and _n pins regardless of whether the input is single-ended or not. The warnings imply that the tool is trying to use an IBUF primitive, input buffer for a digital pin, rather than actually use the dedicated XADC input routing.

UG480 also states that location constraints do not need to be specified (quote below), so it could be unnecessary to include them (although the same lines are where the required IOSTANDARD is set): https://docs.amd.com/r/en-US/ug480_7Series_XADC/Analog-Inputs.

Quote

The auxiliary analog inputs do not require any user-specified constraints or pin locations. They do not require an I/O standard setting to be added to the UCF or in the PlanAhead pinout tool. In the Vivado design tools, an IOSTANDARD must be selected that is compatible for the bank even though the IOSTANDARD does not affect the input programming. All configuration is automatic when the analog inputs are connected to the top level of the design.

image.png

image.png

 

Link to comment
Share on other sites

  • 0
Posted (edited)

On second look, it appears that the Zybo XADC port (JA) if used for analog inputs must use the 4 differential pairs so that only 4 actual analog inputs can be digitized? So even if the analog signals are not truly differential (well, if all four share one common voltage), only 4 signals can be monitored, and not the 8 that *might* be available with a single-ended scheme.

(which by the way is fine, just trying to confirm my understanding)

image.png.2856eccb02487cc08909c9a5023de0d3.png

Edited by engrpetero
added clarifying peretheses statement
Link to comment
Share on other sites

  • 0

Yep, good catch. It's four differential inputs.

From the feature list in the Zybo Z7 reference manual:

image.png

From the Zybo Z7 XADC Demo description:

image.png

Note that the channel names in the table above correspond to the actual XADC channel numbers you should be using in the XADC settings. Channels 6, 7, 14, and 15 correspond to the Zybo's Pmod inputs. This configuration should also be seen in the sequencer settings for the Zybo XADC demo.

The Zybo schematics (screenshot from page 11) also show that the analog input circuitry is the same as the Cora's differential analog inputs:

image.png 

The Cora manual's description of diff. inputs: image.png

 

Link to comment
Share on other sites

  • 0
Posted (edited)

Well, I'm not getting the result I want.  Perhaps some additional help, please?

I do read a believable value on for the chip temperature (~45C) but not on any of the aux channels for the ADC.  The Vivado XADC items is setup for channel sequencing (continuous) with the appropriate channels (those shown in the first pic) in the channel sequence list.

I have a 10:1 voltage divider of the 3.3V power going to pin 2 of the JA header.  pin 7 of the JA header is connected to DC common (JA pin 11).  I physically measure 0.33V between pins 2-7 so I'm confident the voltage is present on the pin.

I am using the BSP driver software and have defined XADCPS_CH_BATTERY_CHARGE_CURRENT as XADCPS_CH_AUX_MIN + 7U.  The software always reports 0.790V on pin 1 (using the BSP driver XAdcPs_GetAdcData() and XAdcPs_RawToVoltage() functions).  

Five pictures below: 1) my Vivado block; 2) XADc basic setup; 3) the constraints file showing the Vaux_p and _n items; 4) The Zybo schematic portion showing the inputs; 5) my Vitis function that calls the BSP functions (and is based off the BSP example).

image.thumb.png.67a904032719c53a53fabfb751035339.png

image.png.404feacfa1b4978fe9e3a23733542f24.png

image.png.6d3cbb1f31cc1406d61c74cd5cbfd492.png

image.png.0c18b773e596f99414e06ee0680d011b.png

image.png.939ba53f4e4ab7fb6f587e93148efa4f.png

float GetBatteryChargeCurrent(XAdcPs *ptrXAdc)
{
	u32 rawData;
	float voltage;
	float resistance = 1.0f;	// the hardware circuit uses a 1.0 ohm current sense resistor
	float chargeCurrent;
	rawData = XAdcPs_GetAdcData(ptrXAdc, XADCPS_CH_BATTERY_CHARGE_CURRENT);
	voltage = XAdcPs_RawToVoltage(rawData);
	chargeCurrent = voltage / resistance;
	return chargeCurrent;
}

 

Edited by engrpetero
Link to comment
Share on other sites

  • 0

What raw data value does 0.790 V correspond to? If it's all zeros or some other notable bit pattern then something isn't being initialized.

It's been quite a while since I worked on the Cora demo and I don't recall if the XADC device actually needs to be initialized in software, but that demo does it here: https://github.com/Digilent/Cora-Z7-SW/blob/d35d836848fed00dec917ff0784f6232862220f5/src/Cora-Z7-10-XADC_SW/src/main.c#L58. It also uses an "xsysmon" driver instead of xadcps, which might be a renamed version of the same drivers... Building out a project for Zybo to try it out.

Link to comment
Share on other sites

  • 0
Posted (edited)

The values were not 0s.  I'm at a different site today and don't have my hardware but can determine exact values tomorrow.  I played around with many of the 'Set' functions the driver defines to understand how they work/what they are intended to do.  I can explicitly call all the Set functions shown in the linked Cora example to see if that has any effect then as well.

Edit to add... 

Looks like I should have more carefully followed your initial advice and studied the Cora example more carefully.  Converting the functions in the Cora example init to the Zybo differential channels and otherwise only using the onboard temperature channel, It seems the init should include these function calls (where #define XADCPS_SEQ_CHANNELS    0x03030010    // temp channel, aux 6, 7, 14, 15 (all aux are differential)...

	// Disable the Channel Sequencer before configuring the Sequence registers.
	XAdcPs_SetSequencerMode(ptrXAdc, XADCPS_SEQ_MODE_SAFE);
	// Disable all alarms
	XAdcPs_SetAlarmEnables(ptrXAdc, 0x0);
	// Set averaging for all channels to 16 samples
	XAdcPs_SetAvg(ptrXAdc, XADCPS_AVG_16_SAMPLES);
	// Set differential input mode for channels AUX6, AUX7, AUX14, AUX15 and unipolar input mode for the rest
	XAdcPs_SetSeqInputMode(ptrXAdc, XADCPS_SEQ_CH_AUX06 | XADCPS_SEQ_CH_AUX07 | XADCPS_SEQ_CH_AUX14 | XADCPS_SEQ_CH_AUX15);
	// Set 6ADCCLK acquisition time in all channels
	XAdcPs_SetSeqAcqTime(ptrXAdc, XADCPS_SEQ_CHANNELS);
	// Disable averaging in all channels
	XAdcPs_SetSeqAvgEnables(ptrXAdc, XADCPS_SEQ_CHANNELS);
	// Enable all channels
	XAdcPs_SetSeqChEnables(ptrXAdc, XADCPS_SEQ_CHANNELS);
	// Set the ADCCLK frequency equal to 1/32 of System clock
	XAdcPs_SetAdcClkDivisor(ptrXAdc, 32);
	// Enable Calibration
	XAdcPs_SetCalibEnables(ptrXAdc, XADCPS_CFR1_CAL_PS_GAIN_OFFSET_MASK | XADCPS_CFR1_CAL_ADC_GAIN_OFFSET_MASK);
	// Enable the Channel Sequencer in continuous sequencer cycling mode
	XAdcPs_SetSequencerMode(ptrXAdc, XADCPS_SEQ_MODE_CONTINPASS);

 

Edited by engrpetero
Included additional info and code example
Link to comment
Share on other sites

  • 0

Any chance you got anywhere, @artvvb?  I did play around with this a bit more today and the raw numbers are non-zero (it's the 10 MSBs of the 16 bit returned number).  They don't seem to change (however, with nothing connected to the JA pins, the values I read are different when reading Aux6, Aux7, Aux14, and Aux15.  I'll be back on it tomorrow but was just wondering if you learned anything new.

Link to comment
Share on other sites

  • 0

Some progress, I can see changing values printed when running the attached code (a heavily modified version of the Cora XADC source), but am still figuring out how to interpret raw data readings and what the register configuration settings applied in the init function might need to be.

(Edited to remove code after posting a followup)

Link to comment
Share on other sites

  • 0

I suspect that the transfer function defined by XSysMon_RawToVoltage is not accurate for differential aux channels. The following comes from UG480 (https://docs.amd.com/r/en-US/ug480_7Series_XADC/ADC-Transfer-Functions), when it describes the bipolar mode transfer function.

Quote

 The LSB size in volts is equal to 1V/2^12 or 1V/4096 = 244 µV.

Whereas the power supply transfer function is "ADC Code / 4096 * 3V".

I think this means that the XSysMon_RawToVoltage macro ("((((float)(AdcData))* (3.0f))/65536.0f)") lines up with the supply conversion, not the aux input conversion, which doesn't seem to be defined in the drivers.
The following is working cleanly for me:

#include "xsysmon.h"
#include "xparameters.h"
#include "sleep.h"
#include "stdio.h"
#include "xil_types.h"

#define XADC_DEVICE_ID XPAR_XADC_WIZ_0_DEVICE_ID

typedef struct ChannelInfo {
	u8 Channel;
	u32 Mask;
	char *Name;
	u8 IsDiff; // 0 or 1
} ChannelInfo;

#define NUMBER_OF_CHANNELS 4
ChannelInfo Channels[NUMBER_OF_CHANNELS] = {
		{XSM_SEQ_CH_AUX_SHIFT + 14, XSM_SEQ_CH_AUX14, "AUX14", 1},
		{XSM_SEQ_CH_AUX_SHIFT + 7,  XSM_SEQ_CH_AUX07, "AUX07", 1},
		{XSM_SEQ_CH_AUX_SHIFT + 15, XSM_SEQ_CH_AUX15, "AUX15", 1},
		{XSM_SEQ_CH_AUX_SHIFT + 6,  XSM_SEQ_CH_AUX06, "AUX06", 1}
};

void Xadc_Init(XSysMon *InstancePtr, u32 DeviceId) {
	XSysMon_Config *ConfigPtr;
	u32 DiffChannels, EnabledChannels;
	u8 Index;

	ConfigPtr = XSysMon_LookupConfig(DeviceId);
	XSysMon_CfgInitialize(InstancePtr, ConfigPtr, ConfigPtr->BaseAddress);

	DiffChannels = 0;
	EnabledChannels = 0;
	for (Index = 0; Index < NUMBER_OF_CHANNELS; Index++) {
		EnabledChannels |= Channels[Index].Mask;
		if (Channels[Index].IsDiff) {
			DiffChannels |= Channels[Index].Mask;
		}
	}

	// Disable the Channel Sequencer before configuring the Sequence registers.
	XSysMon_SetSequencerMode(InstancePtr, XSM_SEQ_MODE_SAFE);
	// Disable all alarms
	XSysMon_SetAlarmEnables(InstancePtr, 0x0);
	// Set averaging for all channels to 16 samples
	XSysMon_SetAvg(InstancePtr, XSM_AVG_16_SAMPLES);
	// Set differential input mode for appropriate channels
	XSysMon_SetSeqInputMode(InstancePtr, DiffChannels);
	// Set 6ADCCLK acquisition time in all channels
	XSysMon_SetSeqAcqTime(InstancePtr, EnabledChannels);
	// Disable averaging in all channels
	XSysMon_SetSeqAvgEnables(InstancePtr, EnabledChannels);
	// Enable all channels
	XSysMon_SetSeqChEnables(InstancePtr, EnabledChannels);
	// Set the ADCCLK frequency equal to 1/32 of System clock
	XSysMon_SetAdcClkDivisor(InstancePtr, 32);
	// Enable Calibration
	XSysMon_SetCalibEnables(InstancePtr, XSM_CFR1_CAL_PS_GAIN_OFFSET_MASK | XSM_CFR1_CAL_ADC_GAIN_OFFSET_MASK);
	// Enable the Channel Sequencer in continuous sequencer cycling mode
	XSysMon_SetSequencerMode(InstancePtr, XSM_SEQ_MODE_CONTINPASS);
}

void Xadc_ReadData (XSysMon *InstancePtr)
{
	u8 Index;
	s16 RawData;
	float VoltageData;

	printf("Waiting for EOS...\r\n");

	// Clear the Status
	XSysMon_GetStatus(InstancePtr);
	// Wait until the End of Sequence occurs
	while ((XSysMon_GetStatus(InstancePtr) & XSM_SR_EOS_MASK) != XSM_SR_EOS_MASK);

	printf("Capturing XADC Data...\r\n");

	for (Index = 0; Index < NUMBER_OF_CHANNELS; Index++) {
		RawData = XSysMon_GetAdcData(InstancePtr, Channels[Index].Channel);
		VoltageData = ((float)(RawData) * (1.0f) / 65536.0f);
		printf("Capturing Data for Channel %s: ", Channels[Index].Name);
		printf("%04hx = ", RawData);
		printf("%.3fV\r\n", VoltageData);
	}
}

int main () {
	XSysMon Xadc;

	Xadc_Init(&Xadc, XADC_DEVICE_ID);
	printf("Zybo Z7 XADC Initialized!\r\n");

	while(1) {
		Xadc_ReadData(&Xadc);
		sleep(1);
	}
}

Results, with VAUX14P at 25 mV, VAUX14N at 100 mV, and the rest grounded:

image.png

Link to comment
Share on other sites

  • 0
Posted (edited)

Thanks for your hard work on this item.  I *should* be able to take what you've presented and get this working.  One question since I don't see these items defined either in the Cora app or in the XAdcPs V2.6 driver files.  Where are XSysMon_GetStatus() (or XAdcPs_GetStatus()) and XSM_SR_EOS_MASK (or XADCPS_SR_EOS_MASK) defined, please?

image.png.e9b68355206ade121488e3f47387e6a3.png

 

It also *seems* since the last in the setup function calls was to setup up for continuous sequencer cycling that there is no need to clear the status and then 'wait' for the status mask to be set (though I'll certainly do that if that's what it takes. :-)).

	// Enable the Channel Sequencer in continuous sequencer cycling mode
	XSysMon_SetSequencerMode(InstancePtr, XSM_SEQ_MODE_CONTINPASS);

And with these pins representing Aux 14, 7, 15, 6...

image.png.a355768071e8d85568572b42f7c2debc.png

Edited by engrpetero
added pinout of JA.
Link to comment
Share on other sites

  • 0

I'll need to check out the XAdcPs API. If you have an XADC instantiated in your block design, you ought to be able to use the XSysMon drivers by directly copying over the posted code.

Guessing, but I think that XAdcPs represents direct PS access to the XADC core, while XSysMon is only pulled in when an XADC is included in block design. I'm not sure what the differences between the APIs are, or if the PS gets a different level of access if the XADC isn't hung off of the AXI bus. Given the existence of the XAdcPs drivers, it seems that it should be possible to use XADC without instantiating the IP - which might make XSysMon completely redundant for Zynq parts - but I'll need to try it out.

Link to comment
Share on other sites

  • 0

In case I haven't mentioned, I'm working in Vitis 2023.1.

3 hours ago, engrpetero said:

Where are XSysMon_GetStatus() (or XAdcPs_GetStatus()) and XSM_SR_EOS_MASK (or XADCPS_SR_EOS_MASK) defined, please?

XAdcPs_GetStatus does not exist in that API.

XSysMon_GetStatus is defined in xsysmon.h and the mask macros are defined in xsysmon_hw.h. In general, if you have the code loaded into the IDE and the macro or function exists in scope, you can control-click on it to find declaration locations.

3 hours ago, engrpetero said:

It also *seems* since the last in the setup function calls was to setup up for continuous sequencer cycling that there is no need to clear the status and then 'wait' for the status mask to be set (though I'll certainly do that if that's what it takes. :-)).

I think this just prevents reading the same sample twice if the software is moving fast. Removing it works fine for me.

Link to comment
Share on other sites

  • 0

For some additional reading, UG480 (the XADC user guide) talks about the dedicated interface between PS and XADC: https://docs.amd.com/r/en-US/ug480_7Series_XADC/Zynq-7000-SoC-Processing-System-PS-to-XADC-Dedicated-Interface. As does the Zynq technical reference manual: https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM/Control-Interfaces.

The two control interfaces described here are the two pieces of hardware underlying the two drivers. XSysMon is used to control XADC when XADC is instantiated in PL. XAdcPs must be used when an XADC isn't instantiated and has a slightly different hardware interface, reflected by the difference in drivers. There are likely various tradeoffs for which interface to use - choosing whether to "waste" FPGA resources or to disallow hardware in PL from using alarm signals comes to mind - but they're likely all pretty irrelevant for just getting the thing working. This is all to say, if you have an XADC instantiated in the design, you should be able to run the code I posted previously as-is.

Link to comment
Share on other sites

  • 0

Wow, I totally missed this when looking at UG480.  in my design XADC is instantiated on the PL side but I was using the XAdcPs drivers (I should have suspected something with the 'Ps' in the name).  As always, your help greatly appreciated.  I'll review the SysMon v7.8 and go that route since that makes more sense with my design.

 

 

Link to comment
Share on other sites

  • 0
Posted (edited)

well, @artvvb - your application works on my hardware.  So I know the hardware is built correctly.  And thank you for a working example!  Unfortunately, my app (in which I *think* I'm doing the exact same thing as you are) does not.  Back to troubleshooting! 

Edit to add...

Hmmm, as expected, mine did not exactly match your example.  I was mistakenly using the bit-mask values intended for the channel sequencer masking as the actual ADC channels instead of the actual ADC channel number.

E.g., Aux14 is ADC channel 30 (XSM_SEQ_CH_AUX_SHIFT + 14) and NOT XSM_SEQ_CH_AUX14 (which is actually 0x40000000 and intended to be used as a bit mask).

image.thumb.png.811cf15b3e1b17546414ace5c49cd20d.png

Edited by engrpetero
Added additional info (to keep all in fewer posts)
Link to comment
Share on other sites

  • 0

One more thing to add...

I think the driver XSysMon_SetSeqInputMode() function description is incorrect.  This function is for setting the channels to unipolar or bipolar mode.  The function merely updates a single bit in the XSM_SEQ_04_OFFSET and XSM_SEQ05_OFFSET registers.  The function description mixes differential and unipolar (which are not really related).

For the 'system' channels (like the on-chip temp), it doesn't seem to matter whether the channel is defined as unipolar or bipolar (I imagine bipolar is just ignored).  For my application, all aux channels are unipolar so I am calling XSysMon_SetSeqInputMode() with an InputModeMask value of 0.

It seems the XSysMon_SetSeqChEnables() function is for setting the channels that should be used with the channel sequencer.  And the initialization (for unipolar aux channels) should be

	// Disable the Channel Sequencer before configuring the Sequence registers.
	XSysMon_SetSequencerMode(ptrXSysMon, XSM_SEQ_MODE_SAFE);
	// Disable all alarms
	XSysMon_SetAlarmEnables(ptrXSysMon, 0x0);
	// Set averaging for all channels to 16 samples
	XSysMon_SetAvg(ptrXSysMon, XSM_AVG_16_SAMPLES);
	// Set unipolar mode for all sequence channels
	XSysMon_SetSeqInputMode(ptrXSysMon, 0);
	// Set seq channel enables
	XSysMon_SetSeqChEnables(ptrXSysMon, XSM_SEQ_CH_AUX06 | XSM_SEQ_CH_AUX07 | XSM_SEQ_CH_AUX14 | XSM_SEQ_CH_AUX15);
	// Set 6ADCCLK acquisition time in all channels
	XSysMon_SetSeqAcqTime(ptrXSysMon, XSM_SEQ_CHANNELS_MASK);
	// Disable averaging in all channels
	XSysMon_SetSeqAvgEnables(ptrXSysMon, XSM_SEQ_CHANNELS_MASK);
	// Enable all channels
	XSysMon_SetSeqChEnables(ptrXSysMon, XSM_SEQ_CHANNELS_MASK);
	// Set the ADCCLK frequency equal to 1/32 of System clock
	XSysMon_SetAdcClkDivisor(ptrXSysMon, 32);
	// Enable Calibration
	XSysMon_SetCalibEnables(ptrXSysMon, XSM_CFR1_CAL_PS_GAIN_OFFSET_MASK | XSM_CFR1_CAL_ADC_GAIN_OFFSET_MASK);
	// Enable the Channel Sequencer in continuous sequencer cycling mode
	XSysMon_SetSequencerMode(ptrXSysMon, XSM_SEQ_MODE_CONTINPASS);

 

Link to comment
Share on other sites

  • 0
42 minutes ago, engrpetero said:

The function merely updates a single bit in the XSM_SEQ_04_OFFSET and XSM_SEQ05_OFFSET registers.

Looks like it updates a bit in SEQ04 and two bytes in SEQ05. This probably corresponds to the VP/VN channel and sixteen aux channels. The XADC documentation is a little bit frustrating, as it's typically talking about the DRP interface rather than AXI. Quote below is from the "Automatic Channel Sequencer" section of UG480.

Quote

ADC Channel Analog-Input Mode ( 4Ch and 4Dh )

These registers are used to configure an ADC channel as either unipolar or bipolar in the automatic sequence (see Analog Inputs, page 21 ). These registers also have the same bit assignments as the channel sequence registers listed in Table 4-1 and Table 4-2 . However, only external analog input channels, such as the dedicated input channels (V P and V N ) and the auxiliary analog inputs (VAUXP[15:0] and VAUXN[15:0]) can be configured in this way. Setting a bit to logic 1 enables a bipolar input mode for the associated channel. Setting a bit to logic 0 (default) enables a unipolar input mode. All internal sensors use a unipolar transfer function.

Offsets and addresses seem to be different for the AXI interface than DRP though, so those "4Ch and 4Dh" don't necessarily apply.

It looks like PG019 is the actual document to be referencing: https://docs.amd.com/v/u/en-US/pg019_axi_xadc. I'm still reading, but haven't spotted if it details exactly what is in the SEQ04/SEQ05 registers yet.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...