This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C123AH6PM: UART interrupt triggered for no reason?

Part Number: TM4C123AH6PM
Other Parts Discussed in Thread: TM4C129ENCPDT

Our UART ISR is triggered but when we call:

	// Find out why interrupt was triggered
	uint32_t Status = MAP_UARTIntStatus(UART6_BASE, 1);

The debugger is showing that Status is 0.

Why is the interrupt triggered?

We are doing UART communication with DMA and trying to receive a block of 8 bytes. We are expecting the interrupt to trigger with UART_INT_DMARX and to set a flag indicating DMA has completed. Instead Status is 0 and we never act upon the received data.

UART and DMA initialization:

#if defined (ewarm)
#pragma data_alignment=1024
uint8_t DmaControlTable[1024];
#elif defined (ccs)
#pragma DATA_ALIGN(DmaControlTable, 1024)
uint8_t DmaControlTable[1024];
#else
uint8_t DmaControlTable[1024] __attribute__ ((aligned(1024)));
#endif


	MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_UDMA);
	MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_UDMA);
	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
	while (MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_UDMA) != true) {
	}

	// Enable uDMA error interrupt to catch bus errors during transfers
	MAP_IntEnable(INT_UDMAERR);

	// Enable the uDMA controller
	MAP_uDMAEnable();

	// Point at the control table to use for channel control structures
	MAP_uDMAControlBaseSet(DmaControlTable);

	MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_UART6);
	MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_UART6);
	MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART6);
	while (MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_UART6) != true) {
	}

	// Setup GPIO port pin muxing and UART function
	MAP_GPIOPinConfigure(GPIO_PD4_U6RX);
	MAP_GPIOPinConfigure(GPIO_PD5_U6TX);
	MAP_GPIOPinTypeUART(GPIO_PORTD_BASE, GPIO_PIN_4 | GPIO_PIN_5);

	MAP_UARTConfigSetExpClk(UART6_BASE, Global.SysClockFreqHz, u->Baud, (UART_CONFIG_PAR_NONE | UART_CONFIG_STOP_ONE | UART_CONFIG_WLEN_8));

	// Set UART Tx and Rx FIFO trigger thresholds to 4
	MAP_UARTFIFOEnable(UART6_BASE);
	MAP_UARTFIFOLevelSet(UART6_BASE, UART_FIFO_TX4_8, UART_FIFO_RX4_8);

	// Enable UART and enable DMA for Tx and Rx
	MAP_UARTEnable(UART6_BASE);

	// Connect appropriate DMA channels to the selected UARTs
	MAP_uDMAChannelAssign(UDMA_CH10_UART6RX);
	MAP_uDMAChannelAssign(UDMA_CH11_UART6TX);

	// Make sure unneeded Rx channel DMA attributes are off
	MAP_uDMAChannelAttributeDisable(UDMA_CH10_UART6RX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK);

	// Set Rx DMA primary control structure:
	// - Data size 8 bits
	// - Source address does not increment; always points to UART Rx data register
	// - Destination address increment is byte (8-bit)
	// - Arbitration size is 4 to match UART Rx FIFO trigger threshold
	// - DMA will use 4 byte burst transfer if possible for efficiency
	MAP_uDMAChannelControlSet(
		UDMA_CH10_UART6RX | UDMA_PRI_SELECT,
		UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_4
	);

	// Make sure unneeded Tx channel DMA attributes are off
	MAP_uDMAChannelAttributeDisable(UDMA_CH11_UART6TX, UDMA_ATTR_ALTSELECT | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK);

	// Enable USEBURST to force Tx channel DMA to always use burst when
	// transferring from Tx buffer to UART.
	MAP_uDMAChannelAttributeEnable(UDMA_CH11_UART6TX, UDMA_ATTR_USEBURST);

	// Set Tx DMA primary control structure:
	// - Data size 8 bits
	// - Source address increment is byte (8-bit)
	// - Destination address does not increment; always points to UART Tx data register
	// - Arbitration size is 4 to match UART Tx FIFO trigger threshold
	MAP_uDMAChannelControlSet(
		UDMA_CH11_UART6TX | UDMA_PRI_SELECT,
		UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_4
	);

	// Enable master interrupt for this UART
	MAP_IntEnable(INT_UART6);

Begin receiving data block:

// Set Rx DMA primary control structure transfer parameters:
// - Mode set to basic (not ping-pong).
// - Source is UART data register
// - Destination is receive memory buffer
// - Transfer size is set to match the size of the buffer
//
MAP_uDMAChannelTransferSet(
UDMA_CH10_UART6RX | UDMA_PRI_SELECT,
UDMA_MODE_BASIC,
(void *)(UART6_BASE + UART_O_DR),
RxBuf,
SizeOfRxBuf
);

// Enable channel because it is disabled at completion of each transfer
MAP_uDMAChannelEnable(UDMA_CH10_UART6RX);

// Enable DMA Rx interrupt
MAP_UARTDMAEnable(UART6_BASE, UART_DMA_RX);
MAP_UARTIntEnable(UART6_BASE, UART_INT_DMARX);

Interrupt:

void IsrUart6Handler(void)
{
	uint32_t Status;

	// Find out why interrupt was triggered
	Status = MAP_UARTIntStatus(UART6_BASE, true);

	// Clear pending status
	MAP_UARTIntClear(UART6_BASE, Status);

	if (Status & UART_INT_DMARX) {
		RxDone = true;

		MAP_UARTDMADisable(UART6_BASE, UART_DMA_RX);
		MAP_UARTIntDisable(UART6_BASE, UART_INT_DMARX);
	}
}

When we examined unmasked UART interrupt status in the debugger, it was 0xf; three of those set bits are reserved and one was CTSRIS which we are not using. Masked interrupt status is 0.

  • Have you attempted to prevent (any) arrival of (unwanted) signal to the UART pins - upon power-up and thereafter?

    As a "fan of KISS" - it is suspected that your (temporary) bypass of µDMA - will better enable the understanding of the UART's various interrupt mechanisms - along w/ other behaviors. Only then - would I restore the µDMA - and armed w/that further UART knowledge - the likelihood of, "When/Where" your issue occurs - should better reveal. (one hopes)
  • cb1_mobile said:
    Have you attempted to prevent (any) arrival of (unwanted) signal to the UART pins - upon power-up and thereafter?

    As a "fan of KISS" - it is suspected that your (temporary) bypass of µDMA - will better enable the understanding of the UART's various interrupt mechanisms - along w/ other behaviors. Only then - would I restore the µDMA - and armed w/that further UART knowledge - the likelihood of, "When/Where" your issue occurs - should better reveal. (one hopes)

    It took a lot of further study and hair-pulling to figure out what was happening here, but we got to the bottom of it.

    We have two different models, one running on TM4C129ENCPDT, the other on TM4C123AH6PM. These parts are very similar, but ever so slightly different.

    In particular, there is a difference in how code detects completion of a DMA transfer between UART (Tx or Rx FIFOs) and memory.

    In the TM4C129ENCPDT (and quite possibly many other parts), the DMA sends a dma_done signal to the UART, and the UART asserts its DMARXRIS and/or DMATXRIS interrupt flags in UARTRIS / UARTMIS. This can be read in the interrupt handler by calling UARTIntStatus() and looking for UART_INT_DMARX and/or UART_INT_DMATX.

    In the TM4C123AH6PM (and, again, quite possibly many other parts), those two bits do not exist in the UART's UARTRIS / UARTMIS. They are labeled "reserved" in the datasheet. Instead, the DMA peripheral sets bits in its DMACHIS register and triggers the UART's interrupt. In this case, UARTIntStatus() will not show anything for UART_INT_DMARX and/or UART_INT_DMATX because they do not exist. That explains why it appeared that our UART interrupt was triggered "for no reason." In addition to calling UARTIntStatus() (and UARTIntClear()) and handling any bits which do exist in the device, software must ALSO call uDMAIntStatus() (and uDMAIntClear()) and check for DMA transfer completion by checking the bits corresponding to the channels being used.

    Because we compile the same code for both platforms, we implemented code to deal with this difference in our UART interrupt handler:

    	bool TxDone = false;
    	bool RxDone = false;
    
    	#if defined (PART_TM4C129ENCPDT)
    	{
    		uint32_t UartIsrStatus = MAP_UARTIntStatus(u->Base, true);
    		
    		if (UartIsrStatus & UART_INT_DMATX) {
    			MAP_UARTIntClear(u->Base, UART_INT_DMATX);
    			TxDone = true;
    		}
    
    		if (UartIsrStatus & UART_INT_DMARX) {
    			MAP_UARTIntClear(u->Base, UART_INT_DMARX);
    			RxDone = true;
    		}
    	}
    	#elif defined (PART_TM4C123GH6PM)
    	{
    		uint32_t DmaStatus = MAP_uDMAIntStatus();
    		uint32_t Mask;
    		
    		Mask = (1 << (u->TxDmaCh & 0xf));
    		if (DmaStatus & Mask) {
    			MAP_uDMAIntClear(Mask);
    			TxDone = true;
    		}
    
    		Mask = (1 << (u->RxDmaCh & 0xf));
    		if (DmaStatus & Mask) {
    			MAP_uDMAIntClear(Mask);
    			RxDone = true;
    		}
    	}
    	#else
    		#error "Not checking for UART DMA transfer completion!"
    	#endif
    
    	if (TxDone) {
    		// Handle Tx completion
    		// .
    		// .
    		// .
    	}
    
    	if (RxDone) {
    		// Handle Rx completion
    		// .
    		// .
    		// .
    	}
    

    
    

    Note about the code: u->Base maps to one of the UARTn_BASE defines from TivaWare driverlib (UART6_BASE in our case). u->RxDmaCh maps to one of the UDMA_CHtt_UARTnRX defines from TivaWare driverlib (UDMA_CH10_UART6RX in our case). u->TxDmaCh maps to one of the UDMA_CHtt_UARTnTX defines from TivaWare driverlib (UDMA_CH11_UART6TX in our case).

    We are still not finished with this aspect of our implementation (which is a nice way of saying that communication is not working robustly yet) and hopefully we won't discover additional subtle differences between the two devices that mess things up.

  • Hello twelve12pm,

    Amit shared the following on the E2E forums in the past. Can you see if that is applicable for your case?

    "The DMA had a rather convoluted mechanism in TM4C123. The DMA and interrupt for the peripheral done is triggered on the peripheral interrupt. When the interrupt handler is called, the CPU must check the interrupt status bit of the peripheral and the UDMACHIS to find out the cause of the interrupt. If the uDMACHIS is set then it must be cleared."

    It looks like your code only checks the peripheral status bit, so please see if the UDMACHIS is the reason for your unknown interrupt.
  • Thank you for explaining and pointing out Amit's comment on the issue. In my last post (above), I did discover about UDMACHIS and showed the code we're using to handle the difference between TM4C123 and TM4C129.

    Since these parts are so similar in so many respects, the subtle differences can be confusing and come as a surprise.