Jump to content
  • 0

Multiple PS Uart Interrupts not seen


engrpetero

Question

I'd just about give my left arm at this point for a working example of using the Xilinx XUartPs stuff with interrupts in a non-loopback, continuously receive data example.  I desire to process every character one at a time as it is received. I normally send characters over a serial port connected to the Zybo with a simple C# app but for test purposes, TeraTerm works fine for sending characters.

I'm using Vivado and Vitis 2022.2 and a ZyboZ7-10.  I have a very simple project based on the XUartPs interrupt example (which of course just uses the loopback mode and a fixed set of send/receive chars).  I've attached the c file to this post (no header file needed) which I think could be executed by anyone with a ZyboZ7-10 with no additional work.

The program initializes the Gic, then initializes the Xuart, then initializes the GPIO (LED) of the Zybo so I could see in a run (not Debug) environment if the hander for the Uart 'event' is ever called (the LED just turns on and off when the Uart event occurs).

The Vitis debug environment isn't consistent.  Sometimes the Uart handler is entered, sometimes not - which is why I stuck the LED on/off change in there).  Since the debug environment isn't consistent, it's awfully difficult to step through the code to see what exactly is going on with the XUartPs software.  And the XUartPS documentation, like most Xilinx documentation, is pretty sparse.  At most, it seems the UartISR handler is entered four times - this ought to be a clue but I've haven't discovered what it means yet.

I've included the InitUart function below.  Note the Fifo threshold is set to 1 in the InitUart function (which I guess means the Uart 'event' handler UartISR) would be entered with every received character.  It is (very) unclear to me based on the comments in the Xilinx XuartPs software what the function XUartPs_Recv() is supposed to do.  To quote the xuartps.c file in which the function is provided...

     * In interrupt mode, this function will start the receiving, if not the entire
     * buffer has been received, the interrupt handler will continue receiving data
     * until the entire buffer has been received. A callback function, as specified
     * by the application, will be called to indicate the completion of the
     * receiving or error conditions.

For interrupt mode, it *seems* I need to call the XUartPs_Recv() function only once (that's why I did it at the end of the InitUart function) and the hander UartISR() would then be called whenever a single new character is received.  That character would be available in the 'RecvBuffer' that was provided in the XUartPs_Recv() function call.

I know it's asking a lot but would greatly appreciate some help on this.  It's critical to my Zybo use that I can faithfully receive and process characters.  The Zybo is a *huge* (and much desired) upgrade over my previous use of AVR controllers for similar applications but no so much if I can't get this working.

int InitUart(XUartPs *ptrUart, u16 UartDeviceId, u16 UartInterruptId)
{
	int Status;
	u32 IntrMask;
	XUartPs_Config *ptrConfig;

	// Look-up the Uart config
	ptrConfig = XUartPs_LookupConfig(UartDeviceId);
	if (ptrConfig == NULL) {return XST_FAILURE;}

	// Initialize Uart
	Status = XUartPs_CfgInitialize(ptrUart, ptrConfig, ptrConfig->BaseAddress);
	if (Status != XST_SUCCESS) {return XST_FAILURE;}

	// Self-test Uart
	Status = XUartPs_SelfTest(ptrUart);
	if (Status != XST_SUCCESS) {return XST_FAILURE;}

	// Setup Uart specific items
	XUartPs_SetBaudRate(ptrUart, 115200);
	IntrMask = XUARTPS_IXR_RXFULL | XUARTPS_IXR_RXEMPTY | XUARTPS_IXR_RXOVR;
	XUartPs_SetInterruptMask(ptrUart, IntrMask);
	XUartPs_SetOperMode(ptrUart, XUARTPS_OPER_MODE_NORMAL);
	XUartPs_SetFifoThreshold(ptrUart, 1);

	// Add Uart to Gic system (remember to always use XUartPs_InterruptHandler)
	Status = XScuGic_Connect(ptrGic, UartInterruptId, (Xil_ExceptionHandler)XUartPs_InterruptHandler, (void *)ptrUart);
	if (Status != XST_SUCCESS) {return Status;}

	// Setup a handler for Uart raised 'events'
	XUartPs_SetHandler(ptrUart, (XUartPs_Handler)UartISR, ptrUart);

	// Enable Uart interrupts with the Gic
	XScuGic_Enable(ptrGic, UartInterruptId);

	// Enable the Uart
	XUartPs_EnableUart(ptrUart);

	// In interrupt mode, start receiving so receive events can occur?
	XUartPs_Recv(ptrUart, RecvBuffer, TEST_BUFFER_SIZE);

	return XST_SUCCESS;
}

 

 

UartTest.c

Edited by engrpetero
Link to comment
Share on other sites

12 answers to this question

Recommended Posts

  • 0

Even though the XUartPS interrupt example shows a line similar to the XUartPs_SetHandler() one shown above, the function prototype seems to suggest the line should be...

	// Setup a handler for Uart raised 'events'
	XUartPs_SetHandler(ptrUart, (XUartPs_Handler)UartISR, (void *)ptrUart);

This of course wouldn't affect the UartISR() from being called, just an the ptrUart reference inside that ISR.

Link to comment
Share on other sites

  • 0

ZYNQ PS UARTs just aren't very wonderful. The FPGA development tool support can be a hard climb for someone coming from a uController background or trying to port a uController design to an FPGA based one.

Personally, I tend not to use the standalone library approach to simple ZYNQ designs where a more straight-forward C-style coding makes more sense to me. Just remember that ARM processors have MMUs and data cache and instruction cache; things that might not be familiar. This approach demands that the designer do a bit of reading to understand how the ZYNQ and its interfaces work. You aren't likely to find all of the information that you need in one place so download all of the relevant information and tart speed reading though all of it until you get a sense of how Xilinx organizes its documentation.

If the Software tools and or the PS constraints for a function block like a UART are giving me more grief than I want to put up with ( and this happens often ) there's always an optimum way to get what I want, and that's by implementing it in the PL logic. This approach requires some competency with the HDL design flow, and knowing how to pass information between PS resources and PL resources. Sooner or later you'll need to to be competent with either approach.  ZYNQ devices are very complex and figuring out how to best use them takes time and work.

I realize that this isn't the answer that you are looking for, but perhaps it's the best one.

Edited by zygot
Link to comment
Share on other sites

  • 0

Thanks, @zygot.  Yes, I've found the learning curve to be quite steep - I'm somewhere in the middle of the climb still.  I do feel like I've read 2000 pages of Xilinx documentation over the last 6 weeks or so. :-)  I am using the PL side of the device (or I'd just be using a ucontroller, of course!) for some relatively simple functionality.  That remains a work in progress.  I am becoming more comfortable with the HDL design flow.  And Axi peripherals.  I'll continue reading, studying the examples, and hope for the best.  Fortunately, outputting data on either of the PS Uarts always works exactly as I want so some debugging is a breeze.  

Link to comment
Share on other sites

  • 0

It seems the examples for the UartLite (implemented in PL, obviously) are virtually identical to the PS Uarts.  So there is a way around the issue I'm facing.  When I figure it out - and I will figure it out! :-) - I'll post back here with a fully working example for others to use.

Link to comment
Share on other sites

  • 0

Hi @engrpetero

On 3/31/2023 at 11:44 AM, engrpetero said:

     * In interrupt mode, this function will start the receiving, if not the entire
     * buffer has been received, the interrupt handler will continue receiving data
     * until the entire buffer has been received. A callback function, as specified
     * by the application, will be called to indicate the completion of the
     * receiving or error conditions.

I'd interpret this to mean that XUartPs_Recv starts the controller's receiving which stops once the buffer is filled. It would then needs to be restarted with another call to XUartPs_Recv. Similar to polled mode, you get as much data as you request via _Recv calls, and no more - the _Recv call sets up the internal counters that the interrupt handlers use to track how much data is received, see the ptrUart->ReceiveBuffer members. That said, I'm not 100% and haven't found other documentation to quote at the moment.

In debugging the code, I see unexpected extra XUARTPS_EVENT_RECV_DATA interrupts firing after all data for the buffer has been received. Maybe the driver expects to have another XUartPs_Recv call set up before more data comes in. Without the call, all it sees, potentially, is a ReceiveBuffer with no remaining bytes and a hardware FIFO with data over the trigger level.

I'm also seeing one additional XUARTPS_EVENT_RECV_DATA when things are first launched, with an EventData of 1, which occurs before XUartPs_Recv is called. I suspect this means that ReceiveBuffer should be set up via XUartPs_Recv before interrupts are enabled.

It feels like there could be some more interrupt control options I'm missing, the ReceiveDataHandler really is just "if there's more data in the hardware FIFO, copy it into the software buffer, then if software buffer is full, call the callback". Seems like it's left up to the user to add something to the upper-level handler that can tell whether you've already completed receiving a buffer, and that if you don't want more interrupts after receiving your buffer, you have to disable them.

Hope this helps, I'm just digging into interrupt mode for the first time myself as well.

-Arthur

Link to comment
Share on other sites

  • 0

Hi Arthur,

Yes, indeed it does help, if for nothing else than to confirm what I'm seeing.  I do see that extra RECV_DATA interrupt on start.  I did not think to setup the ReceiveBuffer before enabling the interrupt.  A good suggestion I will try.  I resigned myself to ignoring that first event since it was absolutely repeatable.  I temporarily side-tracked this part of the project to work on a simple AXI-Lite peripheral. 

Let me ponder your points and play with it some more.  I'll certainly report back on what I see and learn.  I did want to let you know I saw your post and greatly appreciate it.

Peter

 

Link to comment
Share on other sites

  • 0

So I'm sufficiently complete with a few other items to return to this.  I've read the Zynq document UG585 section 19 (UART CONTROLLER) a few times and *think* I understand how this is supposed to work.  However, I haven't found an effective way to use the Vitis debugger to look at code that isn't in my project.  For example, The XUartPs_ReceiveBuffer() function (in xuartps.c) I can look at.  But if I try to step through the code in the debugger, I always see things like shown in the picture.  Not sure how to be able to see such values.  Is there a secret I'm missing?

image.png.fd811cbb527f18642a66520ceda32993.png

Edited by engrpetero
Link to comment
Share on other sites

  • 0

I'm not sure why the symbols aren't available everywhere in a function while debugging in the BSP, perhaps something in BSP/platform build settings. A couple of related issues I've noticed with the debugger, and some suggested workarounds:

_ReadReg, _WriteReg and other similar macros can plant the debug trace in the file that the macro is declared in rather than the spot it's being used, which can mess with the scope of what's visible in the debugger. You can work around this by either determining the address of the EventData variable and using the memory viewer to look at the address range it's in, or by making sure the debug trace is actually planted in the right place by setting a breakpoint on an expression that doesn't include one of these macros like the ReceivedCount increment on line 477. This is also why you sometimes see the debugger bouncing between files when stepping forward through code. Both workarounds aren't fun.

Debugger trace placement in BSP sources also seems to not be consistent, stepping over lines sometimes skips lines. You could also copy driver source code into your application project to debug it there. There could be some additional macros or functions or other things that are only declared in the driver source and not included headers that you'd also need to copy over.

Quote

Curious...  I just noticed in the Zynq subsystem that the PS Uart on MIO 48-49 has pull-ups enabled by default.  Is this correct (that is, should the pull-ups be enabled if using these MIO lines as the second PS Uart)?

Given that UART is default high when idle, I don't think there'd be much issue with either leaving the pullups enabled or disabling them, but I'm not certain. Shouldn't affect interrupts, as most of the interrupt sources are to do with decode errors, framing errors, and other things directly related to the UART protocol, rather than the logic level alone.

Link to comment
Share on other sites

  • 0

I'll look through the build settings and see if I can find anything that looks useful.  In the meantime, I'll consider your suggestion of moving stuff into the application project to see if debugging works better.  As you pointed out, the _readReg and _writeReg inline macros are the places the debugger usually stops so perhaps looking at the memory is another good option.  That will be my task today.

To hopefully help out any future person who reads this thread, this figure I found helpful from UG585...

image.png.61fef2dddff3a180a338dd4f9b2eaca7.png

With regards to what I think is supposed to happen, UG585 seems to make it clear as shown in the second picture.  For my app, I know each receive message will consist of at least 5 bytes (no more than 27).  One way to set things up would be to set the trigger level at 5.  For a short message (5 bytes), I expect the interrupt to happen, after which I'd read data from the RX_FIFO until the FIFO is empty (uart.Channel_sts_reg0 [REMPTY] = 1);  For the longer message, I expect the interrupt to happen after the first 5 bytes are received but that the FIFO will continue to fill with the remaining 22 bytes.  I'm interested to see if the trigger interrupt will continue to happen (I imagine so) even though by the time I read from the FIFO, I imagine the other 22 bytes will already have been received.

image.png.d2ecba7fb76d9b09bc20ebbd2632959c.png

(of course, it also *seems* the functions in the xuartps.c file are setup to do this).

Edited by engrpetero
Link to comment
Share on other sites

  • 0

Boy did I sorely misunderstand the intent of the Xilinx code related to interrupt processing on the Uarts.  Here's what I learned through debugging (though I still see a few odd things with the debugger).  Again, I imagine most people figured this out on their own but just in case someone is reading in the future, maybe they find this useful.  Much of the code is similar or identical to what is in the example interrupt application, but I may have formatted it differently or made minor changes (to the interrupt mask, for example).

At the top of my file...

#define TEST_BUFFER_SIZE	12
static u8 DebugRecvBuffer[TEST_BUFFER_SIZE];	/* Buffer for Receiving Data */

The first code window below is my function that initializes the Uart. 

Note in step 4 the stuff to set the format that is commented out.  Those are the default settings anyway so they aren't needed, just there to show how to reset and to comment that the Uart needs to be disabled before changing the baud rate. Also note that mask that is being set which will interrupt on XUARTPS_IXR_RXFULL | XUARTPS_IXR_RXOVR (so when the receive FIFO is full or when the receive FIFO is above the interrupt threshold (which is set to 5 in the call to XUartPs_SetFifoThreshold)).  

Note in step 5 that the XUartPs_InterruptHandler mentioned is part if the XUartPs software - not the user ISR and it this line shouldn't be changed.  Some comments about that function (XUartPs_InterruptHandler) later.

Note in step 6 that this is where the user ISR is set (my ISR function is called UartISR).

Finally note in step 8 that the function XUartPs_Recv() is called.  Calling this function resets the XUartPs ReceiveBuffer items (RequestedBytes, RemainingBytes, and NextPtr).  This is important because the user ISR is only called when the ReceiveBuffer is full.

So with this init function, every time 5 bytes are received, the XUartPs_InterruptHandler is called by the processor.  And every time 12 bytes are received (TEST_BUFFER_SIZE), the user ISR is called (essentially by the XUartPs_InterruptHandler() function).

int InitUart(XScuGic *ptrGic, XUartPs *ptrUart, u16 UartDeviceId, u16 UartInterruptId)
{
	int Status;
	u32 IntrMask;
	XUartPs_Config *ptrConfig;

	/* ---------------------------------------------------------------------
	 * ------------ STEP 1: DEVICE LOOK-UP ------------
	 * -------------------------------------------------------------------- */
	ptrConfig = XUartPs_LookupConfig(UartDeviceId);
	if (ptrConfig == NULL) {return XST_FAILURE;}

	/* ---------------------------------------------------------------------
	 * ------------ STEP 2: DRIVER INITIALIZATION ------------
	 * -------------------------------------------------------------------- */
	Status = XUartPs_CfgInitialize(ptrUart, ptrConfig, ptrConfig->BaseAddress);
	if (Status != XST_SUCCESS) {return XST_FAILURE;}

	/* ---------------------------------------------------------------------
	* ------------ STEP 3: SELF TEST ------------
	* -------------------------------------------------------------------- */
	Status = XUartPs_SelfTest(ptrUart);
	if (Status != XST_SUCCESS) {return XST_FAILURE;}

	/* ---------------------------------------------------------------------
	* ------------ STEP 4: PROJECT-SPECIFIC CONFIGURATION ------------
	* -------------------------------------------------------------------- */
	XUartPs_DisableUart(ptrUart);	// UG5985 says to make sure the UART is disabled before writing to baud rate gen
//	XUartPsFormat psFormat;
//	psFormat.BaudRate = 115200;
//	psFormat.DataBits = XUARTPS_FORMAT_8_BITS;
//	psFormat.Parity = XUARTPS_MR_PARITY_NONE;
//	psFormat.StopBits = XUARTPS_MR_STOPMODE_1_BIT;
//	XUartPs_SetDataFormat(ptrUart, &psFormat);
	//IntrMask = XUARTPS_IXR_RXFULL | XUARTPS_IXR_RXEMPTY | XUARTPS_IXR_RXOVR;
	IntrMask = XUARTPS_IXR_RXFULL | XUARTPS_IXR_RXOVR;
	XUartPs_SetInterruptMask(ptrUart, IntrMask);
	XUartPs_SetOperMode(ptrUart, XUARTPS_OPER_MODE_NORMAL);
	XUartPs_SetFifoThreshold(ptrUart, 5);	// After this value, the RXOVR interrupt will hit the XUartPs_InterruptHandler

	/* ---------------------------------------------------------------------
	* ------------ STEP 5: ADD TO INTERUPT SYSTEM ------------
	* -------------------------------------------------------------------- */
	Status = XScuGic_Connect(ptrGic, UartInterruptId, (Xil_ExceptionHandler)XUartPs_InterruptHandler, (void *)ptrUart);
	if (Status != XST_SUCCESS) {return Status;}

	/* ---------------------------------------------------------------------
	* ------------ STEP 6: SET A HANDLER TO BE CALLED(back) WHEN EVENT OCCURS ------------
	* -------------------------------------------------------------------- */
	XUartPs_SetHandler(ptrUart, (XUartPs_Handler)UartISR, (void *)ptrUart);

	/* ---------------------------------------------------------------------
	* ------------ STEP 7: ENABLE THE INTERRUPT ------------
	* -------------------------------------------------------------------- */
	XScuGic_Enable(ptrGic, UartInterruptId);

	/* ---------------------------------------------------------------------
	* ------------ STEP 8: ENABLE THE UART ------------
	* -------------------------------------------------------------------- */
	XUartPs_EnableUart(ptrUart);

	/* ---------------------------------------------------------------------
	* ------------ STEP 8: RESET RECEIVEBUFFER ITEMS ------------
	* -------------------------------------------------------------------- */
	// Reset the ptrUart.ReceiveBuffer items (RequestedBytes, RemainingBytes, and NextPtr)
	// This is needed to eventually allow the user ISR to be called.
	XUartPs_Recv(ptrUart, DebugRecvBuffer, TEST_BUFFER_SIZE);	// After TEST_BUFFER_SIZE bytes are received, the user ISR will hit.

	return XST_SUCCESS;
}

Quick look at XUartPs_Recv()...  The main thing to note (at least to me) is that before calling the XUartPs_ReceiveBuffer() function, this function resets the RequestedBytes , RemainingBytes, and NextBytePtr values).  This is important because the user ISR is only called when the RemainingBytes counter reaches 0.

u32 XUartPs_Recv(XUartPs *InstancePtr,
			  u8 *BufferPtr, u32 NumBytes)
{
	u32 ReceivedCount;
	u32 ImrRegister;

	/* Assert validates the input arguments */
	Xil_AssertNonvoid(InstancePtr != NULL);
	Xil_AssertNonvoid(BufferPtr != NULL);
	Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);

#if defined  (XCLOCKING)
	Xil_ClockEnable(InstancePtr->Config.RefClk);
#endif
	/*
	 * Disable all the interrupts.
	 * This stops a previous operation that may be interrupt driven
	 */
	ImrRegister = XUartPs_ReadReg(InstancePtr->Config.BaseAddress,
				  XUARTPS_IMR_OFFSET);
	XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_IDR_OFFSET,
		XUARTPS_IXR_MASK);

	/* Setup the buffer parameters */
	InstancePtr->ReceiveBuffer.RequestedBytes = NumBytes;
	InstancePtr->ReceiveBuffer.RemainingBytes = NumBytes;
	InstancePtr->ReceiveBuffer.NextBytePtr = BufferPtr;

	/* Receive the data from the device */
	ReceivedCount = XUartPs_ReceiveBuffer(InstancePtr);

	/* Restore the interrupt state */
	XUartPs_WriteReg(InstancePtr->Config.BaseAddress, XUARTPS_IER_OFFSET,
		ImrRegister);

	return ReceivedCount;
}

The XUartPs_ReceiveBuffer() function isn't that interesting.  It really just fills the buffer from the Uart's FIFO (and it will call the user ISR if there is a frame error) so I didn't post it here (easy enough to look at yourself).

Similarly, the XUartPs_InterruptHandler() function isn't worth posting here - it's pretty straightforward and just looks at the interrupt that happened before calling one of several other functions depending on the interrupt type.  One of those functions I do show next though: ReceiveDataHandler().

Note that this function - besides getting any characters that are new since the interrupt happened with the call to XUartPs_ReceiveBuffer() - is responsible for calling the user ISR once the remaining bytes int he user's receive buffer (size TEST_BUFFER_SIZE in my case) reaches 0.  Also to note that no additional calls to the user ISR will happen unless the ReceiveBuffer items are reset with a new call to XUartPs_Recv().  The skeleton of my user ISR is shown at the end.

static void ReceiveDataHandler(XUartPs *InstancePtr)
{
	/*
	 * If there are bytes still to be received in the specified buffer
	 * go ahead and receive them. Removing bytes from the RX FIFO will
	 * clear the interrupt.
	 */
	 if (InstancePtr->ReceiveBuffer.RemainingBytes != (u32)0) {
		(void)XUartPs_ReceiveBuffer(InstancePtr);
	}

	 /* If the last byte of a message was received then call the application
	 * handler, this code should not use an else from the previous check of
	 * the number of bytes to receive because the call to receive the buffer
	 * updates the bytes ramained
	 */
	if (InstancePtr->ReceiveBuffer.RemainingBytes == (u32)0) {
		InstancePtr->Handler(InstancePtr->CallBackRef,
				XUARTPS_EVENT_RECV_DATA,
				(InstancePtr->ReceiveBuffer.RequestedBytes -
				InstancePtr->ReceiveBuffer.RemainingBytes));
	}

}

My user ISR skeleton.  Note the call at the end to XUartPs_Recv() to reset the ReceiveBuffer items so this ISR will eventually be called again.  Also note the //! lines which is the place something should be done with the info in the user ReceiveBuffer before we reset it.

void UartISR(void *CallBackRef, u32 Event, unsigned int EventData)
{
	XUartPs *ptrSenderXUart = (XUartPs *) CallBackRef;

	// Send some relevant info about the event to the stdio
	xil_printf("ISR: Event: %i, EventData: %i, Remaining: %i, NextPtr: 0x%X\r\n", Event, EventData, ptrSenderXUart->ReceiveBuffer.RemainingBytes, &ptrSenderXUart->ReceiveBuffer.NextBytePtr);

	// Do something with the DebugRecvBuffer because we are about to reset it...
	//!
	//!
	//!
	
	// Reset the ReceiveBuffer items so this ISR will be called again
	XUartPs_Recv(ptrSenderXUart, DebugRecvBuffer, TEST_BUFFER_SIZE);
}

Now I haven't yet decided how best to use this info for the circumstance I mentioned with my 5 and 27 byte expected messages.  But I'm closer.  I hope someone else finds this useful.  It was useful for me to type, either way. :-)

Edited by engrpetero
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...