UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 
Observer cg49me
Observer
8,811 Views
Registered: ‎12-17-2010

MicroBlaze MCS: UART TX in Interrupt Mode (I/O Module)

I'm getting my feet wet with the MicroBlaze MCS IP Core, and I'm having trouble using the XIOModule_Send function in interrupt mode.  My assumption is that I simply don't understand how things are supposed to be hooked up - I'm hoping someone can show me what I'm doing wrong.

 

Below is my C code: 

 

#include <stdio.h>
#include "xiomodule.h"
#include "xparameters.h"

int main() { u8 msg[20] = "Buffer Text\r\n"; XIOModule io; microblaze_enable_interrupts(); microblaze_register_handler(XIOModule_DeviceInterruptHandler, XPAR_IOMODULE_0_DEVICE_ID); XIOModule_Initialize(&io, XPAR_IOMODULE_0_DEVICE_ID); XIOModule_Uart_EnableInterrupt(&io); XIOModule_Connect(&io, XIN_IOMODULE_UART_TX_INTERRUPT_INTR, (XInterruptHandler)XIOModule_Uart_InterruptHandler, &io); XIOModule_Start(&io); XIOModule_Send(&io, msg, 20); xil_printf("Immediate Text\r\n"); }

 

If I understand correctly, as each byte of msg is transmitted, XIOModule_Uart_InterruptHandler should fire and call SendDataHandler, which in turn calls XIOModule_SendBuffer.  That function manages shifting the SendBuffer.NextBytePtr pointer to the next byte in the buffer, decrementing SendBuffer.RemainingBytes, and transmitting the next byte.  This process of interrupts should continue until the entire buffer (or however many bytes are specified in the XIOModule_Send call) are transmitted.

 

However, the output I end up with is this:

 

BImmediate Text

 

I was expecting something more like this:

 

BImmediate Text
uffer Text

 

Interestingly (to me, who admittedly doesn't fully understand how all this works), if I debug the program, and step through the code, making sure to step INTO the XIOModule_Send call, and step INTO the XIOModule_SendBuffer call, the output comes out like this:

 

Buffer Text
Immediate Text

 

If I step OVER either of those functions, my output is the same as simply letting the program execute.

 

I realize that I can use the XIOModule_Send function in "polled mode", essentially placing it in a loop, and using the return value of the function to see how many bytes were transmitted during the call (if any), then re-calling the function with an appropriately updated buffer pointer, and finally exiting the loop when the entire buffer is sent.  My objective, however, is to utilize the "interrupt mode", allowing the UART buffer to transmit while the micro performs other tasks (and not "blocking" it for the entire message all at once).

 

I'll also mention that I've made the correction to my XIOModule_Uart_InterruptHandler mentioned here.  I have written a few other pieces of test code, and can get receive interrupts to fire as expected.

 

Thanks for your time!

0 Kudos
5 Replies
Xilinx Employee
Xilinx Employee
8,745 Views
Registered: ‎10-08-2010

Re: MicroBlaze MCS: UART TX in Interrupt Mode (I/O Module)

Your understanding of how interrupt based transmission is handled seems to be correct. However, it is not the intention to mix polled and interrupt based transmission mode, which you do by using both XIOModule_Send and xil_printf.

 

Although I can't exactly explain why you get the output you see, my suggestion is to select either polled or interrupt mode, and not try to mix them.

 

 

0 Kudos
Observer cg49me
Observer
8,602 Views
Registered: ‎12-17-2010

Re: MicroBlaze MCS: UART TX in Interrupt Mode (I/O Module)

I hacked away at this for a couple of weeks, and finally got things straightened out.  The short of it is that the Xilinx drivers for the I/O Module UART aren't the most robust, and have problems that stem from three main issues:

 

1) The UART status register and interrupt enable/pending registers do not contain the same kind of data.  In more than one location, data from one is compared as though it came from the other.

2) UART interrupts are acknowledged before they're serviced.  This means the interrupt pending register can't be checked to see what type of UART interrupt (RX or TX) fired.

3) The UART TX interrupt fires when the UART TX buffer is emptied.  This means the UART TX buffer can't be relied on to tell what kind of UART interrupt fired.

 

So, on to the code...  I made changes to XIOModule_Uart_InterruptHandler and XIOModule_SendBuffer, both located in xiomodule_uart_intr.c, and things are now working basically as I would expect.  I'll quickly mention that, if anyone wishes to make similar adjustments, they should be made to the "master" source files located at \Xilinx\14.7\ISE_DS\EDK\sw\XilinxProcessorIPLib\drivers\iomodule_v1_04_a\src (assuming your version and installation are similar to mine), and then the board support package will need to be regenerated; changing the source files in an already-generated BSP won't actually do anything.  Play it safe - make backups, or just comment out the original stuff.

 

First off, let's pick apart the "out-of-the-box" XIOModule_Uart_InterruptHandler (this function is intended to be connected to UART RX and TX interrupts - XIN_IOMODULE_UART_RX_INTERRUPT_INTR and XIN_IOMODULE_UART_TX_INTERRUPT_INTR from xiomodule_l.h - using the XIOModule_Connect function from xiomodule.c):

 

void XIOModule_Uart_InterruptHandler(XIOModule *InstancePtr)
{
	u32 IsrStatus;

	Xil_AssertVoid(InstancePtr != NULL);

	/*
	 * Read the status register to determine which, could be both,
	 * interrupt is active
	 */

	IsrStatus = XIOModule_ReadReg(InstancePtr->BaseAddress,
					XIN_IPR_OFFSET);

	if ((IsrStatus & XUL_SR_RX_FIFO_VALID_DATA) != 0) {
		ReceiveDataHandler(InstancePtr);
	}

	if (((IsrStatus & XUL_SR_TX_FIFO_FULL) == XUL_SR_TX_FIFO_FULL) &&
		(InstancePtr->SendBuffer.RequestedBytes > 0)) {
		SendDataHandler(InstancePtr);
	}
}

 

To begin with, the value of the interrupt pending regsiter is read and stored to the IsrStatus variable.  Bit 0 is then compared (XUL_SR_RX_FIFO_VALID_DATA = 0x0 in xiomodule_l.h) to determine whether or not a UART RX interrupt fired (bit 0 of the UART status register indicates RX valid data; it tells whether or not the UART TX buffer, which holds a single byte of data, is full).  As matthijsbos points out in his post (link in OP), this comparison doesn't accomplish anything, and always results in a false result, since IsrStatus contains data not from the UART status register, but from the interrupt pending register, bit 0 of which indicates whether or not a UART error interrupt fired.  Hence, ReceiveDataHandler is never called.  Following is his recommended correction, the function can be modified thusly:

 

void XIOModule_Uart_InterruptHandler(XIOModule *InstancePtr)
{
	u32 IsrStatus;

	Xil_AssertVoid(InstancePtr != NULL);

	/*
	 * Read the status register to determine which, could be both,
	 * interrupt is active
	 */

	//IsrStatus = XIOModule_ReadReg(InstancePtr->BaseAddress,
	//				XIN_IPR_OFFSET);

	IsrStatus = XIOModule_ReadReg(InstancePtr->BaseAddress,
			XUL_STATUS_REG_OFFSET);

	if ((IsrStatus & XUL_SR_RX_FIFO_VALID_DATA) != 0) {
		ReceiveDataHandler(InstancePtr);
	}

	if (((IsrStatus & XUL_SR_TX_FIFO_FULL) == XUL_SR_TX_FIFO_FULL) &&
		(InstancePtr->SendBuffer.RequestedBytes > 0)) {
		SendDataHandler(InstancePtr);
	}
}

 

The offset passed to XIOModule_ReadReg is changed from XIN_IPR_OFFSET to XUL_STATUS_REG_OFFSET (from 0x34 to 0x8, both in xiomodule_l.h), resulting in the UART status register now being read into IsrStatus (the variable name should probably have been changed for clarity, but oh well).  Consequently, an appropriate comparison is made against the value of the RX valid data bit, and UART receives now work correctly in "interrupted mode".

 

...however, let's continue on to look at the code handling UART TX interrupts.  The value of the TX used bit, again from the UART status register, is compared, and the value of the SendBuffer.RequestedBytes variable (inside the XIOModule struct, defined in xiomodule.h) is checked to see whether or not a UART TX interrupt has fired.  SendBuffer.RequestedBytes is set when XIOModule_Send is called by the application, and is reset to 0 by SendDataHandler after the indicated number of bytes in the referenced buffer have been placed into the UART TX buffer (both of these functions are in xiomodule_uart_intr.c).  This means it's a solid check to see if more bytes still need to be transmitted, but it's not a stand-alone indicator of whether or not a UART TX interrupt has fired, hence the coupling with the TX used bit check.  This bit tells whether or not the UART TX buffer is full (similar to the UART RX buffer, it holds only a single byte).  Unfortunately, as mentioned at the top of the post, the UART TX interrupt fires when the UART TX buffer is emptied, so when this comparison is performed, it will always return false, and SendDataHandler will never be called.  The inverse comparison can't be made, since it's possible for a UART RX interrupt to occur, and for there to be a byte of data in the UART TX buffer which simply hasn't been transmitted yet (it would also fail following transmission of the final byte of data).

 

As a solution, I pose the following.  XIOModule_UART_InterruptHandler is intended to be called only for UART RX and UART TX interrupts.  As written, it is intended to deal with both interrupts firing at the same time (though, for the above reasons, it doesn't actually work) with default interrupt servicing (only the highest priority interrupt will be serviced - to set otherwise, XIOModule_SetOptions from xiomodule_options.c would need to be called passing XIN_SVC_ALL_ISRS_OPTION from xiomodule.h).  The likelkihood of both interrupts occurring "simultaneously" (within the same microprocessor clock cycle) is remote, and thus I claim only a single comparison is necessary to determine which of the two interrupts fired:

 

void XIOModule_Uart_InterruptHandler(XIOModule *InstancePtr)
{
	u32 IsrStatus;

	Xil_AssertVoid(InstancePtr != NULL);

	/*
	 * Read the status register to determine which, could be both,
	 * interrupt is active
	 */

	//IsrStatus = XIOModule_ReadReg(InstancePtr->BaseAddress,
	//				XIN_IPR_OFFSET);

	IsrStatus = XIOModule_ReadReg(InstancePtr->BaseAddress,
			XUL_STATUS_REG_OFFSET);

	//if ((IsrStatus & XUL_SR_RX_FIFO_VALID_DATA) != 0) {
	//	ReceiveDataHandler(InstancePtr);
	//}

	//if (((IsrStatus & XUL_SR_TX_FIFO_FULL) == XUL_SR_TX_FIFO_FULL) &&
	//	(InstancePtr->SendBuffer.RequestedBytes > 0)) {
	//	SendDataHandler(InstancePtr);
	//}

	if ((IsrStatus & XUL_SR_RX_FIFO_VALID_DATA) != 0)
	{
		ReceiveDataHandler(InstancePtr);
	}
	else
	{
		SendDataHandler(InstancePtr);
	}
}

 

In my version of the function, only the UART RX receive buffer is checked to determine which of the interrupts fired.  Since XIOModule_Uart_InterruptHandler will ONLY be called when either a UART RX or UART TX interrupt fires, neither of the appropriate handlers will be called erroneously.  There is only the (extremely) small chance that both interrupts will fire within the same microprocessor clock cycle, in which case only ReceiveDataHandler will be called, resulting in only the UART RX interrupt being appropriately serviced.

 

...yet this still doesn't completely get "interrupted mode" UART transmits working.  Let's now take a look at the unedited version of XIOModule_SendBuffer:

 

unsigned int XIOModule_SendBuffer(XIOModule *InstancePtr)
{
	unsigned int SentCount = 0;
	u8 StatusRegister;
	u8 IntrEnableStatus;

	/*
	 * Read the status register to determine if the transmitter is full
	 */
	StatusRegister = XIOModule_GetStatusReg(InstancePtr->BaseAddress);

	/*
	 * Enter a critical region by disabling all the UART interrupts to allow
	 * this call to stop a previous operation that may be interrupt driven
	 */
	XIomodule_Out32(InstancePtr->BaseAddress + XIN_IER_OFFSET,
			StatusRegister & 0xFFFFFFF8);

	/*
	 * Save the status register contents to restore the interrupt enable
	 * register to it's previous value when that the critical region is
	 * exited
	 */
	IntrEnableStatus = StatusRegister;

	/*
	 * Fill the FIFO from the the buffer that was specified
	 */

	while (((StatusRegister & XUL_SR_TX_FIFO_FULL) == 0) &&
		(SentCount < InstancePtr->SendBuffer.RemainingBytes)) {
		XIOModule_WriteReg(InstancePtr->BaseAddress,
					XUL_TX_OFFSET,
					InstancePtr->SendBuffer.NextBytePtr[
					SentCount]);

		SentCount++;

		StatusRegister =
			XIOModule_GetStatusReg(InstancePtr->BaseAddress);
	}

	/*
	 * Update the buffer to reflect the bytes that were sent from it
	 */
	InstancePtr->SendBuffer.NextBytePtr += SentCount;
	InstancePtr->SendBuffer.RemainingBytes -= SentCount;

	/*
	 * Increment associated counters
	 */
	 InstancePtr->Uart_Stats.CharactersTransmitted += SentCount;

	/*
	 * Restore the interrupt enable register to it's previous value such
	 * that the critical region is exited
	 */
	XIomodule_Out32(InstancePtr->BaseAddress + XIN_IER_OFFSET,
	    (InstancePtr->CurrentIER & 0xFFFFFFF8) | (IntrEnableStatus & 0x7));

	/*
	 * Return the number of bytes that were sent, althought they really were
	 * only put into the FIFO, not completely sent yet
	 */
	return SentCount;
}

 

First, XIOModule_GetStatusReg (from xiomodule_l.h) is called to retreive the value of the UART status register.  This value is then apparently used with a bit mask to disable any enabled UART interrupts.  Unfortunately, this is another case of comparing dissimilar data from two different registers.  Further complicating matters, the contents of the interrupt enable register can't be read as an alternative (an attempt to read any of the constituent bits will return a 0, even if the associated interrupt is truly enabled).  Continuing, the value of the UART status register is again checked to ensure the UART TX buffer is empty, appropriate variables in the XIOModule struct are checked to see how many bytes still need to be transmitted, and the address of the next byte to place into the UART TX buffer is used to do just that.  After send counts are updated, the value of the UART status register is again erroneously used in an attempt to re-enable any UART interrupts that had been disabled earlier in the function.

 

My corrections instead rely on the value of the CurrentIER ("current interrupt enable register") variable in the XIOModule struct, which is appriopriately updated by other functions as interrupts are modified, to correctly disable and enable UART interrupts:

 

unsigned int XIOModule_SendBuffer(XIOModule *InstancePtr)
{
	unsigned int SentCount = 0;
	u8 StatusRegister;
	u8 IntrEnableStatus;

	/*
	 * Read the status register to determine if the transmitter is full
	 */
	StatusRegister = XIOModule_GetStatusReg(InstancePtr->BaseAddress);

	/*
	 * Enter a critical region by disabling all the UART interrupts to allow
	 * this call to stop a previous operation that may be interrupt driven
	 */
	//XIomodule_Out32(InstancePtr->BaseAddress + XIN_IER_OFFSET,
	//		StatusRegister & 0xFFFFFFF8);

	/*
	 * Save the status register contents to restore the interrupt enable
	 * register to it's previous value when that the critical region is
	 * exited
	 */
	//IntrEnableStatus = StatusRegister;

	XIomodule_Out32(InstancePtr->BaseAddress + XIN_IER_OFFSET, InstancePtr->CurrentIER & 0xFFFFFFF8);

	/*
	 * Fill the FIFO from the the buffer that was specified
	 */

	while (((StatusRegister & XUL_SR_TX_FIFO_FULL) == 0) &&
		(SentCount < InstancePtr->SendBuffer.RemainingBytes)) {
		XIOModule_WriteReg(InstancePtr->BaseAddress,
					XUL_TX_OFFSET,
					InstancePtr->SendBuffer.NextBytePtr[
					SentCount]);

		SentCount++;

		StatusRegister =
			XIOModule_GetStatusReg(InstancePtr->BaseAddress);
	}

	/*
	 * Update the buffer to reflect the bytes that were sent from it
	 */
	InstancePtr->SendBuffer.NextBytePtr += SentCount;
	InstancePtr->SendBuffer.RemainingBytes -= SentCount;

	/*
	 * Increment associated counters
	 */
	 InstancePtr->Uart_Stats.CharactersTransmitted += SentCount;

	/*
	 * Restore the interrupt enable register to it's previous value such
	 * that the critical region is exited
	 */
	//XIomodule_Out32(InstancePtr->BaseAddress + XIN_IER_OFFSET,
	//    (InstancePtr->CurrentIER & 0xFFFFFFF8) | (IntrEnableStatus & 0x7));

	XIomodule_Out32(InstancePtr->BaseAddress + XIN_IER_OFFSET, InstancePtr->CurrentIER & 0xFFFFFFFF);

	/*
	 * Return the number of bytes that were sent, althought they really were
	 * only put into the FIFO, not completely sent yet
	 */
	return SentCount;
}

 

With both of these functions modified as I have above, UART transmits work as I would expect them to in "interrupted mode".  Once a byte is placed into the UART TX buffer, execution returns to the primary application.  When the UART TX buffer clears, a UART TX interrupt fires, the next byte is placed into the UART TX buffer, and execution again returns to the primary application.  This continues until all indicated bytes are transmitted, and then the application provided send data handler is called (set with XIOModule_SetSendHandler in xiomodule_uart_intr.c).  A good pracitce for this handler would be to immediately disable/disconnect UART TX interrupts in order to avoid complications and unintended results.  It's worth mentioning that attempting to interject non-interrupted mode UART transmits (say, for instance, using xil_printf) in the middle of an interrputed mode transmit will produce very unpredictable results.

 

That all being said...  I would wager that many of those using an embedded MicroBlaze controller for its UART functionality are probably doing so while their VHDL is doing all the "heavy lifting", and the gains made by utilizing "interrupted mode" transmits would be marginal.

 

Observer drbyte
Observer
1,330 Views
Registered: ‎03-07-2011

Re: MicroBlaze MCS: UART TX in Interrupt Mode (I/O Module)

Thank for sharing your experience and solution!
0 Kudos
173 Views
Registered: ‎04-30-2019

Re: MicroBlaze MCS: UART TX in Interrupt Mode (I/O Module)

Sorry for bringing up an old thread but it looks like Xilinx still haven't fixed this as of 2018.3.  I've made the changes to the BSP as detailed in this thread but I'm still not getting any interrupts firing for the UART.  I've successfully got GPI and Timer interrupts working but am now stuck.

My latest debug hacking looks like the following.  I'm not seeing either of the RX or TX UART interrupts fire.

Any thoughts?

   /* Set up the UART. */
   XIOModule_SetRecvHandler(&IOModule, Uart_RecvHandler, (void *)&IOModule);
   XIOModule_SetSendHandler(&IOModule, Uart_SendHandler, (void *)&IOModule);
   XIOModule_RegisterHandler(IOModule.BaseAddress, XIN_IOMODULE_UART_TX_INTERRUPT_INTR, (XInterruptHandler)XIOModule_Uart_InterruptHandler, (void *)&IOModule);
   XIOModule_RegisterHandler(IOModule.BaseAddress, XIN_IOMODULE_UART_RX_INTERRUPT_INTR, (XInterruptHandler)XIOModule_Uart_InterruptHandler, (void *)&IOModule);
   XIOModule_Uart_EnableInterrupt(&IOModule);

   Xil_Out32(IOModule.BaseAddress + XUL_TX_OFFSET, 'z');
   u32 status = Xil_In32(IOModule.BaseAddress + XUL_STATUS_REG_OFFSET);
   while(1) {
	   Xil_Out32(IOModule.BaseAddress + XIN_IER_OFFSET, 0x7);
	   Xil_Out32(IOModule.BaseAddress + XUL_TX_OFFSET, 'z');
	   u32 uart_status = Xil_In32(IOModule.BaseAddress + XUL_STATUS_REG_OFFSET);
	   u32 irq_pending = Xil_In32(IOModule.BaseAddress + XIN_IPR_OFFSET);
	   if ((uart_status == 0xF) && (irq_pending == 0xF)) {
		   Xil_Out32(IOModule.BaseAddress + XIN_IER_OFFSET, 0x0);
	   }
   }
0 Kudos
Highlighted
154 Views
Registered: ‎04-30-2019

Re: MicroBlaze MCS: UART TX in Interrupt Mode (I/O Module)

In order to fix this I've had to do two things:

1.  Disable all optimisations for the BSP by editing the Makefile in the microblaze_l directory.

2.  Make sure to acknowledge all interrupts on entry to main.  I'm guessing the system isn't reset correctly so the old state persists.

3.  Power cycle the target board every 5 attempts as at some point it will stop behaving and give you very strange results.  Nice.

0 Kudos