pico-sdk icon indicating copy to clipboard operation
pico-sdk copied to clipboard

`Interrupt FIFO Level Select Register` (ifls) Behavior for Transmission Threshold Interrupts

Open undici77 opened this issue 10 months ago • 6 comments

I am seeking clarification regarding the behavior of the Interrupt FIFO Level Select Register (ifls), specifically in relation to transmission threshold interrupts. Here are my observations:

  • At reset, ifls is set to 0x02 (b010), meaning the Transmit FIFO becomes <= 1/2 full.
  • The uart_open function resets the serial interface each time, restoring this value.
  • The uart_set_irqs_enabled function sets ifls to 0 (b000 = Receive FIFO becomes >= 1/8 full) after enabling interrupts.

During testing focused on transmission threshold interrupts, it appears that once the threshold for ifls is set, the interrupt handler only considers it after the threshold has been reached. This results in the first interrupt occurring after 8 characters are transmitted, and subsequent interrupts occur at every character thereafter, even if ifls is changed to 0.

I have also tried setting ifls before enabling interrupts within uart_set_irqs_enabled, but this does not alter the behavior. When ifls is set to 0, the interrupt seems to trigger for each character transmitted.

Has anyone conducted similar tests or can provide insights into this behavior? I am a bit confused as this pattern is consistently observed across multiple tests.

undici77 avatar Feb 27 '25 08:02 undici77

Are you using the RP2040 or the RP2350? Which version of pico-sdk are you using? Could you attach some example code that demonstrates your problem? (and please explain the difference between expected and observed behaviour)

This results in the first interrupt occurring after 8 characters are transmitted, and subsequent interrupts occur at every character thereafter, even if ifls is changed to 0.

How many new characters are you adding to the transmit FIFO each time you get the interrupt? The RP2040 datasheet says "The transmit FIFO is an 8-bit wide, 32 location deep, FIFO memory buffer." so it seems strange that the first interrupt occurs after 8 characters rather than after 32 characters?

lurch avatar Feb 28 '25 14:02 lurch

Thank you for your response. I am working with the RP2040 microcontroller and using Pico SDK version 2.1.1.

Check this example: I'm trying to integrate RS-485 communication using interrupts. By monitoring PIN 15 (RS_485_DE) and PIN 16 (UART_0_TX), the results are displayed in the image below.

Varibale Send and Ch emulate a queue mechanism.

Image

If you remove workaround(); no interrupt rise.

#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/irq.h"
#include "hardware/clocks.h"

/******************************************************************************/

#define UART_ID   uart0
#define UART_IRQ  UART0_IRQ
#define BAUD_RATE 115200
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY    UART_PARITY_EVEN

/******************************************************************************/

#define UART_TX_PIN 16
#define UART_RX_PIN 17
#define UART_DE_PIN 15

/******************************************************************************/

// These variables simulate a queue mechanism:
// 'Send' acts like the classic '!queue.empty()' function,
// indicating there is at least one character in the queue.
bool Send;

// 'Ch' holds the character to be sent when the queue is not empty.
uint8_t Ch;

/******************************************************************************/

#define UART_ENABLE_RX_IRQ(uart) uart_set_irqs_enabled(uart, true, false);
#define UART_ENABLE_TX_IRQ(uart) uart_set_irqs_enabled(uart, false, true);

/// @brief Handles UART interrupt for RS485 communication.
///        This function processes incoming data from the UART and manages the transmission of data emulating a RS485 mechanism.
///        It checks if there is any unread data available in the UART buffer and reads it. If there is no data,
///        it checks if the UART can accept new data. If the UART has a transmit interrupt enabled and the
///        transmitter is ready, it sends the next character from the 'Send' queue. If the 'Send' queue is empty,
///        it resets the UART_DE_PIN pin to indicate that transmission is complete.
///
/******************************************************************************/
void uart_irq()
/******************************************************************************/
{
	while (uart_is_readable(UART_ID))
	{
		uint8_t __unused ch = uart_getc(UART_ID);
	}

	if (uart_is_writable(UART_ID))
	{
		if ((uart_get_hw(UART_ID)->imsc & UART_UARTIMSC_TXIM_BITS) != 0)
		{
			if (((uart_get_hw(UART_ID)->fr & UART_UARTFR_TXFE_BITS) != 0) && ((uart_get_hw(UART_ID)->fr & UART_UARTFR_BUSY_BITS) == 0))
			{
				if (Send)
				{
					uart_get_hw(UART_ID)->dr = Ch;
					Send                     = false;
				}
				else
				{
					// Reset data direction, indicating trasmission complete
					gpio_put(UART_DE_PIN, false);
					UART_ENABLE_RX_IRQ(UART_ID);
				}
			}
		}
	}
}

/// @brief Initializes the UART peripheral.
///        This function initializes the UART peripheral by setting up GPIO pins for communication,
///        configuring the UART hardware, and enabling interrupts. It also sets up the UART format
///        to use 8 data bits, 1 stop bit, no parity, and enables hardware flow control.
///
/******************************************************************************/
void init(void)
/******************************************************************************/
{
	Send = false;

	gpio_init(UART_DE_PIN);
	gpio_set_dir(UART_DE_PIN, GPIO_OUT);
	gpio_put(UART_DE_PIN, false);

	gpio_set_function(UART_TX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_TX_PIN));
	gpio_set_function(UART_RX_PIN, UART_FUNCSEL_NUM(UART_ID, UART_RX_PIN));

	uart_init(UART_ID, BAUD_RATE);

	hw_write_masked(&uart_get_hw(UART_ID)->ifls, 0 << UART_UARTIFLS_RXIFLSEL_LSB, UART_UARTIFLS_RXIFLSEL_BITS);
	hw_write_masked(&uart_get_hw(UART_ID)->ifls, 0 << UART_UARTIFLS_TXIFLSEL_LSB, UART_UARTIFLS_TXIFLSEL_BITS);

	uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
	uart_set_hw_flow(UART_ID, false, false);
	irq_set_exclusive_handler(UART_IRQ, uart_irq);
	irq_set_enabled(UART_IRQ, true);

	uart_set_fifo_enabled(UART_ID, true);
}

/// @brief Workaround function to enable queue interrupt functionality.
///        If this function is not called, the example will not operate correctly,
///        and interrupts will not be triggered.
///
/******************************************************************************/
void workaround(void)
/******************************************************************************/
{
	UART_ENABLE_TX_IRQ(UART_ID);
	for (int id = 0; id < 8; id++)
	{
		uart_putc_raw(UART_ID, 0x00);
	}
}

/// @brief Sends a character over UART.
///        This function sends the specified character through the UART interface.
///
/// @param ch The character to be sent over UART.
/******************************************************************************/
void send_char(char ch)
/******************************************************************************/
{
	gpio_put(UART_DE_PIN, true);

	Ch   = ch;
	Send = true;

	UART_ENABLE_TX_IRQ(UART_ID);
}

/// @brief Main function of the program.
///
/******************************************************************************/
int main()
/******************************************************************************/
{
	uint8_t ch;

	init();

	// If you don't call this, SERIAL PORT IS NOT WORKING
	//////////////////////////////////////////////////////////////
	workaround();
	//////////////////////////////////////////////////////////////
	
	while (1)
	{
		for (ch = '0'; ch <= '9'; ch++)
		{
			send_char(ch);
			sleep_ms(1);
		}
	}

	return(0);
}

undici77 avatar Mar 01 '25 17:03 undici77

In your uart_irq function you're only loading one character at a time into the UART's FIFO, so why are you surprised by the "subsequent interrupts occur at every character thereafter" behaviour?

Have you looked at the examples here? https://github.com/raspberrypi/pico-examples/?tab=readme-ov-file#uart

lurch avatar Mar 01 '25 18:03 lurch

I began with that example, but I couldn't find an explicit RS-485 example. Why might it be missing? The issue isn't just about loading one character into the queue. My question is why the interrupt doesn't trigger when the workaround function is not called. I'm quite sure there's a mistake on my part somewhere, as this situation seems unusual to me.

Additionally, using two queues—one software and one hardware—instead of a single software queue with interrupts appears more efficient. On other ARM Cortex platforms, simply enabling TX interrupt trigger immediately TX_QUEUE_EMPTY. Here happen after sending 8 bytes...

undici77 avatar Mar 01 '25 18:03 undici77

I began with that example, but I couldn't find an explicit RS-485 example. Why might it be missing?

Because nobody wrote one yet? 😉 If you'd like to see a specific example, you should create an issue at https://github.com/raspberrypi/pico-examples/ requesting it (but that's obviously no guarantee that such an example will get written).

My question is why the interrupt doesn't trigger when the workaround function is not called.

Page 426 of https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf says: "NOTE The transmit interrupt is based on a transition through a level, rather than on the level itself. When the interrupt and the UART is enabled before any data is written to the transmit FIFO the interrupt is not set. The interrupt is only set, after written data leaves the single location of the transmit FIFO and it becomes empty."

lurch avatar Mar 02 '25 02:03 lurch

It is a bit curious that you have to transmit a few (>5 in my testing) characters before the transmit irq goes off. I can't explain that.

peterharperuk avatar May 30 '25 18:05 peterharperuk