Jump to content
  • 0

Is there any function in Vitis that can directly read the values from the ZmodADC1410 registers?


Mamatchai

Question

Hello,

As title, I'm looking to address certain technical issues through this method, but I couldn't find relevant information online.
By the way, the location I want to read is ZMODADC1410_REG_ADDR_TRIG.
Thanks for helping me!!!

Link to comment
Share on other sites

13 answers to this question

Recommended Posts

  • 0

Hi @Mamatchai

Edit: There's no specific zmodlib API function for reading back the trigger value after it's written, you'd need to store data in the application calling setTrigger or one of the acquire functions. The rest of this comment is about how to work around this.

So these tips are mainly for baremetal. Linux user space virtual addresses probably complicate things, and could require modifications to zmodlib sources. A screenshot showing each is attached.

You can view values stored in memory-mapped registers in the debugger - though this feature has had a bug in some versions of Vitis where only the lowest byte of each word can be viewed (this can be seen in the screenshot).

You can also use a debugger expression to determine the value of the register at a particular point in the code, by dereferencing its address. Note that the debugger may not have access to macros coming in from headers like xparameters for this, so you may need to declare the pointer in the code.

If you're trying to modify the code to read the register value and then use it for something, you can use the above method of dereferencing a pointer that has been set to the register address.

Hope this helps,

Arthur

image.png

Link to comment
Share on other sites

  • 0
Posted (edited)
On 3/6/2024 at 9:18 AM, artvvb said:

Hi @Mamatchai

Edit: There's no specific zmodlib API function for reading back the trigger value after it's written, you'd need to store data in the application calling setTrigger or one of the acquire functions. The rest of this comment is about how to work around this.

So these tips are mainly for baremetal. Linux user space virtual addresses probably complicate things, and could require modifications to zmodlib sources. A screenshot showing each is attached.

You can view values stored in memory-mapped registers in the debugger - though this feature has had a bug in some versions of Vitis where only the lowest byte of each word can be viewed (this can be seen in the screenshot).

You can also use a debugger expression to determine the value of the register at a particular point in the code, by dereferencing its address. Note that the debugger may not have access to macros coming in from headers like xparameters for this, so you may need to declare the pointer in the code.

If you're trying to modify the code to read the register value and then use it for something, you can use the above method of dereferencing a pointer that has been set to the register address.

Hope this helps,

Arthur

image.png

Hello, about the solution you provided, it raises several questions for me.
1.Since it's the first time that I am using dereferencing, so I am not sure if I am doing it correctly. The code is all in the screenshot. The error code it gives is:
Exception: At col 12: Cannot read symbol data. Value of register pc not available in the selected frame
for this report issue, it prevents me from reading the data stored in the register I dereferenced. The code compiles without errors, so I suspect there might be something incorrect in my code causing this.

image.png

2.If my dereferencing method is correct, then I would like to inquire about the subsequent data packaging method. Our goal is to try if we can transmit the measurement signals received by our ADC through TCP. After receiving your guidance, we found that the problem indeed lies in the conflict of interrupt handlers. From : https://forum.digilent.com/topic/27431-is-it-possible-to-run-tcp-and-zmod_adc-on-eclypse-z7-at-the-same-time/#comment-83944). Thus, if we try to do something further, can you give us some advice or tell us what to do so that we can continue working on our project.

Edited by Mamatchai
Link to comment
Share on other sites

  • 0

ZMODADC1410_REG_ADDR_TRIG is an offset - there should be a corresponding BASEADDR (base address) macro in the "xparameters.h" file, add the offset to the base address, rather than just using the offset. In the screenshot in my previous comment, you can see the addresses are coming from "XPAR_AXI_DMA_0_BASEADDR + * + XAXIDMA_*_OFFSET". Ignore the middle value, it's specific to the DMA IP and doesn't apply to the Zmod ADC controller. You should have something like "XPAR_(Zmod ADC controller name)_BASEADDR + ZMODADC1410_REG_ADDR_TRIG".

I apologize if that's not super clear - if you have the Vivado project, you can also see where base addresses are assigned for different peripherals in the Address Editor, something like this is usually in the 0x40000000+ range (that DMA core in the screenshot is at address 0x40400000). You can also get this info in the hardware platform SPR file, though the xparameters.h header is where to get it for the code itself.

image.png

Link to comment
Share on other sites

  • 0

For interrupts, there might be a way to work around it by making sure that zmodlib and the constructors for its classes either don't reinitialize the xscugic driver, or to make sure the constructors get called before the ethernet code initializes the driver. A quick way to check it would be to comment out or remove code from the functions in Zmod/baremetal/intc.c (assuming you're using baremetal).

XStatus fnInitInterruptController(XScuGic *psIntc)
{
	return XST_SUCCESS;
}

void fnEnableInterrupts(XScuGic *psIntc, const ivt_t *prgsIvt, unsigned int csIVectors)
{
	return;
}

This would prevent zmodlib from reinitializing the interrupt controller and from registering any interrupts - which also means no interrupts that zmodlib uses would work anymore and you'd have to rely on the Polling acquisition functions. I'm not 100% sure whether there are other interrupts in zmodlib that are still used when using Polling functions, but I don't think so.

That said, either a substantial rewrite of the ethernet code (so that it uses zmodlib's intc API) or a rewrite of the zmodlib source so that it can be told to not reinitialize the interrupt controller would probably be the better long-term solution.

Hope this helps,

Arthur

Link to comment
Share on other sites

  • 0
Posted (edited)
On 3/8/2024 at 7:52 AM, artvvb said:

For interrupts, there might be a way to work around it by making sure that zmodlib and the constructors for its classes either don't reinitialize the xscugic driver, or to make sure the constructors get called before the ethernet code initializes the driver. A quick way to check it would be to comment out or remove code from the functions in Zmod/baremetal/intc.c (assuming you're using baremetal).

XStatus fnInitInterruptController(XScuGic *psIntc)
{
	return XST_SUCCESS;
}

void fnEnableInterrupts(XScuGic *psIntc, const ivt_t *prgsIvt, unsigned int csIVectors)
{
	return;
}

This would prevent zmodlib from reinitializing the interrupt controller and from registering any interrupts - which also means no interrupts that zmodlib uses would work anymore and you'd have to rely on the Polling acquisition functions. I'm not 100% sure whether there are other interrupts in zmodlib that are still used when using Polling functions, but I don't think so.

That said, either a substantial rewrite of the ethernet code (so that it uses zmodlib's intc API) or a rewrite of the zmodlib source so that it can be told to not reinitialize the interrupt controller would probably be the better long-term solution.

Hope this helps,

Arthur

Hello Arthur,

After I remove code from the functions in Zmod/baremetal/intc.c,the functionality of initializing ZmodADC has been consistently unusable.I've been trying to solve the issue of initializing the functionality of ZmodADC these past few days.This problem has been bothering me for quite some time.This is quite a strange question, I can't figure it out by myself, I feel like I still need some advice from you.Here's my main.c code.

//--------------------------------------------------
//          blog.csdn.net/FPGADesigner
//          copyright by CUIT Qi Liu
//      Zynq Lwip TCP Communication Test Program
//--------------------------------------------------

#include "timer_intr.h"
#include "sys_intr.h"
#include "user_tcp.h"
#include "lwip/netif.h"
#include "sleep.h"
//#define TRANSFER_LEN 0xB

#define TIMER_LOAD_VALUE    XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8 //0.25S

static  XScuGic Intc; //GIC
static  XScuTimer Timer;//timer
//extern volatile unsigned tcp_client_connected;
//extern int tcp_trans_cnt;
void start_application(void);


#include "./Zmod/zmod.h"
#include "./ZmodADC1410/zmodadc1410.h"

#define SEND_SIZE 1463

//extern struct netif server_netif;
static char val_formatted[15];
#define TRANSFER_LEN 0x5B7
// ZMOD ADC parameters
#define ZMOD_ADC_BASE_ADDR  XPAR_AXI_ZMODADC1410_0_S00_AXI_BASEADDR
#define DMA_ADC_BASE_ADDR  XPAR_AXI_DMA_ADC_BASEADDR
#define IIC_BASE_ADDR  XPAR_PS7_I2C_1_BASEADDR
#define FLASH_ADDR_ADC   0x30
#define ZMOD_ADC_IRQ  XPAR_FABRIC_AXI_ZMODADC1410_0_LIRQOUT_INTR
#define DMA_ADC_IRQ  XPAR_FABRIC_AXI_DMA_ADC_S2MM_INTROUT_INTR
//static char sendBuffer_main[TCP_SEND_BUFSIZE];

//--------------------------------------------------
//                中斷與定時器初始化
//--------------------------------------------------
void System_Init()
{
	Timer_init(&Timer,TIMER_LOAD_VALUE,TIMER_DEVICE_ID);
	Init_Intr_System(&Intc); // initial DMA interrupt system
	Setup_Intr_Exception(&Intc);
	Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR);
	Timer_start(&Timer);
	TcpTmrFlag = 0;
}

//--------------------------------------------------
//                     主程序
//--------------------------------------------------
int main(void)
{
	xil_printf("Im here~~~\n");


	uint8_t channel=0;
	uint8_t gain=0;
	size_t length=TRANSFER_LEN;

	ZMODADC1410 adcZmod(ZMOD_ADC_BASE_ADDR, DMA_ADC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_ADC, ZMOD_ADC_IRQ, DMA_ADC_IRQ);

	uint32_t *acqBuffer;
	adcZmod.setGain(channel, gain);
	acqBuffer = adcZmod.allocChannelsBuffer(length);
	adcZmod.acquireImmediatePolling(acqBuffer, length);


	static char time_formatted[15];
	uint32_t valBuf;
	int16_t valCh;
	float val;
	int coo;

	xil_printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
	struct netif *netif, server_netif;   //用於lwIP網絡接口的通用數據結構
	struct ip4_addr ipaddr, netmask, gw;  //unsigned int

	//開發板的MAC地址
	unsigned char mac_ethernet_address[] = {
			0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
	System_Init();

	netif = &server_netif;  //0x50C004
	xil_printf("netif = %s\tnetif = 0x%p\t&server_netif = 0x%p\n", netif, netif, &server_netif);
	//將4byte結構的IP地址轉換爲unsigned int
	IP4_ADDR(&ipaddr,  192, 168,   1, 10);  //IP地址(開發板)
	IP4_ADDR(&netmask, 255, 255, 255,  0);  //網絡掩碼
	IP4_ADDR(&gw,      192, 168,   1,  1);  //網關

	lwip_init();    //初始化lwIP
	//將網絡接口添加到netif_list中
	if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR))
	{
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	}
	xil_printf("netif_final = %s\n", netif);  //NULL
	netif_set_default(netif);  //設置默認網絡接口
	netif_set_up(netif);       //啓動網絡接口
	tcp_send_init();           //初始化TCP PCB

	xil_printf("New acquisition ------------------------\r\n");
	xil_printf("Ch1\tRaw\tTime\t\r\n");
	//
	while(1)
	{
		if(TcpTmrFlag)
		{
			tcp_tmr();
			TcpTmrFlag = 0;
		}
		xemacif_input(netif);     //將MAC隊列中的packets傳輸到lwIP棧中

		if (tcp_client_connected)
		{  //連接成功則發送數據
			xil_printf("TCP Connected");
			/*
			for (int i = 0; i < 30; i++)//
			{
				valBuf = acqBuffer[i];
				valCh = adcZmod.signedChannelData(channel, valBuf);
				val = adcZmod.getVoltFromSignedRaw(valCh, gain);
				adcZmod.formatValue(val_formatted, 1000.0*val, "mV\r\n");

				//xil_printf("%d\t%s\t\r\n",i,val_formatted);
				strcat(sendBuffer,val_formatted);
			}
			*/

			//send_data();
			//xil_printf("tran_cnt:%d\r\n", tcp_trans_cnt);
			xil_printf("Ready to send");
			//strcpy(sendBuffer,"");
			//adcZmod.freeChannelsBuffer(acqBuffer, length);
		}
		if (tcp_client_connected==-1)
		{
			xil_printf("errorrrrr  exit\r\n");
			return 0;
		}
		//sleep(2);
	}
	return 0;
}

And if I removed the initiate part of ZmodADC 

	uint8_t channel=0;
	uint8_t gain=0;
	size_t length=TRANSFER_LEN;

	ZMODADC1410 adcZmod(ZMOD_ADC_BASE_ADDR, DMA_ADC_BASE_ADDR, IIC_BASE_ADDR, FLASH_ADDR_ADC, ZMOD_ADC_IRQ, DMA_ADC_IRQ);

	uint32_t *acqBuffer;
	adcZmod.setGain(channel, gain);
	acqBuffer = adcZmod.allocChannelsBuffer(length);
	adcZmod.acquireImmediatePolling(acqBuffer, length);


	static char time_formatted[15];
	uint32_t valBuf;
	int16_t valCh;
	float val;
	int coo;

the code of can run successfully.

In my opinion, the Eclypse Z7 may have a certain procedure to confirm the normal execution of interrupt processes. When there is any abnormality in the interrupt process, other actions will not be executed. Of course, this is my observation based on experiments, which may not be accurate.

If you need any other information, please let me know. Thank you very much.

Edited by Mamatchai
Link to comment
Share on other sites

  • 0
On 3/7/2024 at 3:23 PM, artvvb said:

ZMODADC1410_REG_ADDR_TRIG is an offset - there should be a corresponding BASEADDR (base address) macro in the "xparameters.h" file, add the offset to the base address, rather than just using the offset. In the screenshot in my previous comment, you can see the addresses are coming from "XPAR_AXI_DMA_0_BASEADDR + * + XAXIDMA_*_OFFSET". Ignore the middle value, it's specific to the DMA IP and doesn't apply to the Zmod ADC controller. You should have something like "XPAR_(Zmod ADC controller name)_BASEADDR + ZMODADC1410_REG_ADDR_TRIG".

I apologize if that's not super clear - if you have the Vivado project, you can also see where base addresses are assigned for different peripherals in the Address Editor, something like this is usually in the 0x40000000+ range (that DMA core in the screenshot is at address 0x40400000). You can also get this info in the hardware platform SPR file, though the xparameters.h header is where to get it for the code itself.

image.png

 

Link to comment
Share on other sites

  • 0

Are these addresses constant for the Eclypse Z7 platform or do you need to look at this file each time when wanting to get data?

I was wondering how these came about, does building an application in Vitis automatically assign the values or properly route existing hardware values?

or other magic?

Thanks, 

 

Edited by Xband
more info
Link to comment
Share on other sites

  • 0

Mamatchai, I'll need a bit of time to reproduce and double-check whether there are other important interrupts.

4 hours ago, Xband said:

Are these addresses constant for the Eclypse Z7 platform or do you need to look at this file each time when wanting to get data?

I was wondering how these came about, does building an application in Vitis automatically assign the values or properly route existing hardware values?

or other magic?

Addresses for PL IP are different per project, they're assigned in the address editor in Vivado. You can either let Vivado automatically assign them (the default behavior) or manually set them. Either way, the address map is exported with the XSA file after generating a bitstream, along with various additional information about the hardware design (this is also how IP configuration information makes its way into the files used by *_LookupConfig functions). Including the xparameters file in your code and using the macros it defines is recommended in case the peripheral addresses are changed in Vivado.

image.png

Link to comment
Share on other sites

  • 0

Regarding the approach of commenting out the interrupt functionality, as an experiment, I cloned a fresh instance of the Zmod Scope baremetal demo (here: https://digilent.com/reference/programmable-logic/eclypse-z7/demos/zmod-scope), and ran it in 2019.1 with and without the contents of the interrupt initialization functions. It worked fine with the interrupt functions, but when the sources were commented out, there was no response. Debugging the demo, it seems that the DMA API doesn't expose the polled version of the fnIsDMATransferComplete function, and the system stalls while waiting for data to be transferred from PL.

To fix this, I added the second line below to zmodlib/Zmod/dma.h:

uint8_t fnIsDMATransferComplete(uintptr_t addr);
uint8_t fnIsDMATransferCompletePoll(uintptr_t addr);

Modified the following line in zmodlib/ZmodADC1410/zmodadc1410.cpp's ZMODADC1410::acquirePolling function:

    // Wait for DMA to Complete transfer
    while(!isDMATransferCompletePoll()) {}

And added the following function to zmodlib/Zmod/zmod.cpp and the corresponding header:

/**
 * Check if the DMA transfer previously started has completed.
 *
 * @return true if the DMA transfer completed, false if it is still running
 */
bool ZMOD::isDMATransferCompletePoll() {
	return fnIsDMATransferCompletePoll(dmaAddr);
}

The zmodlib/Zmod/zmod.h header:

	bool isDMATransferComplete();
	bool isDMATransferCompletePoll();

I also needed to adjust the fnIsDMATransferCompletePoll function in zmodlib/Zmod/baremetal/dma.c as follows, since the status register for PL->PS transfer is at address 0x34 instead of 4 (https://docs.amd.com/r/en-US/pg021_axi_dma/S2MM_DMASR-S2MM-DMA-Status-Register-Offset-34h):

uint8_t fnIsDMATransferCompletePoll(uintptr_t addr) {
	DMAEnv *dmaEnv = (DMAEnv *)addr;
	if (!dmaEnv)
		return 0;
	uint32_t offset = (dmaEnv->direction == DMA_DIRECTION_RX) ? 0x34 : 0x04;
	uint8_t val = readDMAReg(dmaEnv->base_addr, offset);
	return (val & 2);
}

 

Link to comment
Share on other sites

  • 0

Another potential way to fix the issues would be to make sure that your source code uses the same instance of the XScuGic handler as the zmodlib sources. In zmodlib, this is declared in zmodlib/Zmod/zmod.cpp and is externed into other files where it's relevant. Options are to either modify the zmodlib sources so you can pass in a handler to an instance that you declare in code outside of zmodlib, or use the API defined in intc.h when setting up handlers for the ethernet and timer sources. I'm not sure of the details of either option.

@Xband I realized you weren't necessarily just asking about the base addresses. Register offsets that specify where a particular register is located relative to a base address are commonly defined in both peripheral drivers and in IP documentation. The section of the AXI DMA user guide I linked in the previous comment is an example of the latter. As an example of where you might find register offsets in drivers, xaxidma_hw.h (found in BSP sources or by clicking through various other AXI DMA driver files) includes them for the DMA core. That receive status register offset is defined by the two following lines:

#define XAXIDMA_RX_OFFSET	0x00000030 /**< RX channel registers base
					     * offset */
#define XAXIDMA_SR_OFFSET	 0x00000004   /**< Status */

 

Link to comment
Share on other sites

  • 0
Posted (edited)
On 3/30/2024 at 7:36 AM, artvvb said:

Regarding the approach of commenting out the interrupt functionality, as an experiment, I cloned a fresh instance of the Zmod Scope baremetal demo (here: https://digilent.com/reference/programmable-logic/eclypse-z7/demos/zmod-scope), and ran it in 2019.1 with and without the contents of the interrupt initialization functions. It worked fine with the interrupt functions, but when the sources were commented out, there was no response. Debugging the demo, it seems that the DMA API doesn't expose the polled version of the fnIsDMATransferComplete function, and the system stalls while waiting for data to be transferred from PL.

To fix this, I added the second line below to zmodlib/Zmod/dma.h:

uint8_t fnIsDMATransferComplete(uintptr_t addr);
uint8_t fnIsDMATransferCompletePoll(uintptr_t addr);

Modified the following line in zmodlib/ZmodADC1410/zmodadc1410.cpp's ZMODADC1410::acquirePolling function:

    // Wait for DMA to Complete transfer
    while(!isDMATransferCompletePoll()) {}

And added the following function to zmodlib/Zmod/zmod.cpp and the corresponding header:

/**
 * Check if the DMA transfer previously started has completed.
 *
 * @return true if the DMA transfer completed, false if it is still running
 */
bool ZMOD::isDMATransferCompletePoll() {
	return fnIsDMATransferCompletePoll(dmaAddr);
}

The zmodlib/Zmod/zmod.h header:

	bool isDMATransferComplete();
	bool isDMATransferCompletePoll();

I also needed to adjust the fnIsDMATransferCompletePoll function in zmodlib/Zmod/baremetal/dma.c as follows, since the status register for PL->PS transfer is at address 0x34 instead of 4 (https://docs.amd.com/r/en-US/pg021_axi_dma/S2MM_DMASR-S2MM-DMA-Status-Register-Offset-34h):

uint8_t fnIsDMATransferCompletePoll(uintptr_t addr) {
	DMAEnv *dmaEnv = (DMAEnv *)addr;
	if (!dmaEnv)
		return 0;
	uint32_t offset = (dmaEnv->direction == DMA_DIRECTION_RX) ? 0x34 : 0x04;
	uint8_t val = readDMAReg(dmaEnv->base_addr, offset);
	return (val & 2);
}

 

Edited by Mamatchai
Link to comment
Share on other sites

  • 0
Posted (edited)

 

Recently an expert suggested to me that there might be a conflict occurring during function calls due to the similarity in the names of TCP and ZmodADC interrupt flags(IF). However, I haven't confirmed this yet.

Edited by Mamatchai
Link to comment
Share on other sites

  • 0

Apologies, I haven't had a chance to take a look at your project yet, and may not tomorrow either.

11 hours ago, Mamatchai said:

Recently an expert suggested to me that there might be a conflict occurring during function calls due to the similarity in the names of TCP and ZmodADC interrupt flags(IF). However, I haven't confirmed this yet.

This is interesting, I would expect to see some kind of "multiply defined" error indicating that the same definition occurs more than once. It's still possible that there could be issues like this that don't generate that specific error though.

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...