UART Interrupts

Configuring the Zynq UART Module to interrupt upon receiving a byte

3193

Setting up Interrupts for the UART module

To request attention from the processor most peripherals in Zynq are capable of driving interrupt request (IRQ) lines when they need service. Each peripheral has it’s own conditions for when to drive its IRQ line and these conditions can often be configured. Since the ARM core only has a single IRQ line, all peripheral interrupt lines are connected to the GIC, which processes the individual interrupt requests and drives the ARM’s IRQ based on the GIC’s configuration and peripheral IRQ inputs.

UART Interrupts

The UART module is capable of generating interrupt requests for multiple events. Interrupts can be generated based on the status of the receive and transmit FIFOs, as well as for data errors on the tx/rx lines (Parity error, Framing error, etc.). The module can be configured to generate interrupts for a single event or any combination of events. Interrupt behavior is defined through a group of 4 registers, the interrupt mask register (IMR), interrupt enable register (IER), interrupt disable register (IDR), and the interrupt status register (ISR).

The bitfield description of these registers can be found in the Zynq’s Technical Reference Manual.

UART Interrupt Status Register

The interrupt status register (ISR) indicates whether the interrupt events have occured. When an interrupt event occurs, the corresponding bit gets set in the ISR. The bits in the ISR are ‘sticky’, meaning after the bit is set, it will remain set until cleared by the programmer. To clear a flag a value can be written to the ISR with bitfield for that flag set; writing a zero to a field will not change a flag’s state.

Note: there is also a channel status register (UART1 address 0xE000102C), this is seperate from the interrupt status register but provides similar information. The channel status register’s bits reflect the current state of the FIFO’s and channel. If the receiver is empty, the corresponding bit will be set, if it’s not the bit will be clear (the bits can clear themselves). The ISR’s bits set when the event occurs and stay set.

UART Interrupt Mask Register

The interrupt mask register (IMR) is a read-only register, each bit in the register corresponds to an interrupt event. If the bit is set, the corresponding interrupt is enabled. If clear, the interrupt is disabled. The IMR can only be modified by the interrupt enable (IER) and interrupt disable (IDR) registers. Writing a value to the IER with a bitfield set will enable the corresponding interrupt. Similary, writing the same value to the IDR will disable the interrupts with bits set.

The interrupt line of the module is a function of the IMR and the ISR. If the same bit is set in both the ISR and IMR, the UART’s IRQ will be asserted. The IRQ line’s behavior can be described as first a bitwise AND of the IMR and ISR, then a reduction OR of that result.

Practically, the IMR can be used to enable interrupts you want to occur and to disable interrupts you don’t want. The ISR can be used as an indicator of what needs to be serviced during an interrupt. Since the IRQ signal is driven as long as matching bits in the IMR and ISR are set, the ISR is also used clear the interrupt status. If bits are not cleared during an interrupt service routine, the processor will interrupt immediatly after returning from the handler.

Setting up UART receiver interrupts

While the UART module can be configured for many interrupt events, this document will only cover configuring the UART module to generate an interrupt signal when a character arrives.

UART receiver threshold

The first bitfield (bit 0) in the ISR/IMR is defined in the Zynq’s TRM as ‘receiver FIFO Trigger’. The ‘Trigger’ is related to a programmable threshold value, found in the UART Receive Trigger Level register (UART1 address 0xE00010020). The value of this register is compared to the number of entries in the receive FIFO. If the number of entries in the RX fifo is greater than or equal to the threshold value, bit 0 in the ISR will set. If the corresponding bit is set in the IMR (it has been unmasked), the interrupt signal will be driven.

Configuration

To have the uart module interrupt when a byte arrives, set the threshold value to 1 byte and unmask the corresponding interrupt bit. A code example of doing so is shown below.

First register addresses are defined:

#define UART1_IER	*((uint32_t *) 0xE0001008)
#define UART1_IDR	*((uint32_t *) 0xE000100C)
#define UART1_ISR	*((uint32_t *) 0xE0001014)
#define UART1_RXTG	*((uint32_t *) 0xE0001020)

Then Functions for configuring registers:

void set_uart1_rx_trigger(uint32_t n)
{
	UART1_RXTG = n;
}

void unmask_uart1_rxtg_int(void)
{
	UART_1_IER=1;
}

void mask_uart1_rxtg_int(void)
{
	UART_1_IDR=1;
}

void enable_uart1_byte_rx_int(void)
{
	set_uart1_rx_trigger(1);
	unmask_uart1_rxtg_int;
}

After calling enable_uart1_byte_rx_int, whenever there is at least one byte in the receive FIFO, the interrupt signal from the UART module to the GIC will be asserted. The GIC must be configured to allow this interrupt signal to reach the processor.

Servicing a UART interrupt

When the UART interrupt is configured and enabled, the interrupt must be serviced when it occurs. If an enabled interrupt is not handled correctly it can hang up the entire system. In the case of the interrupt we configured above, data must be read from the FIFO until it’s below the threshold. The flag must also be cleared, otherwise the interrupt will occur again after returning.

The code below services the UART interrupt by reading all the bytes in the RX FIFO. Since the threshold is set to 1 byte, all data must be read from the FIFO. If the threshold was higher, the data count in the fifo would just need to be under the threshold. The received data is read into a dummy variable, but not used. Make sure to add meaningful effects in the service handler you write. At the end of the function, all of the UART flags are cleared to indicate the interrupt request has been serviced.

//clears all UART1 interrupt flags
void clear_uart1_int_flags(void)
{
	UART1_ISR = UART1_ISR;
}

//reads all data out of receive FIFO and clears interrupt flags.
void service_uart1_int(void)
{
	char dummy;

	//read until FIFO is empty		
	while(!uart1_rx_empty())
		dummy = uart1_read_char(); //place in dummy character

	clear_uart1_int_flags();	//clear flags

}

The function service_uart1_int above cannot be used as a interrupt service routine as it is. The function does not check which peripheral caused the interrupt, nor does it acknowledge the end of the interrupt to the GIC. Make sure to write a proper interrupt handler, which can call your service function or perform the processing directly in the handler function.