How to use custom interrupt for handling UART

torkleyy
Posts: 6
Joined: Tue Sep 05, 2023 5:51 pm

How to use custom interrupt for handling UART

Postby torkleyy » Fri Sep 08, 2023 10:53 am

Hi,

I would like to use custom interrupts for handling UART rx. The reason I cannot use event queues is that I will sometimes need to reply within 300us of receiving a byte (and event queues have more latency than that due to FreeRTOS context switches).

Note: I do not need high throughput, only low latency.

In ESP IDF v4.4.5, there was an API to register a custom interrupt handler (https://docs.espressif.com/projects/esp ... interrupts - see

Code: Select all

uart_is_register
).

However, as of ESP IDF v5 this API no longer exists, with no note in the migration guide and seemingly no replacement. Busy looping

Code: Select all

uart_read_bytes
is not an option since it will starve other tasks.

How can I implement low-latency UART communication using the ESP IDF v5+?

Thank you so much in advance!

ESP_Sprite
Posts: 9720
Joined: Thu Nov 26, 2015 4:08 am

Re: How to use custom interrupt for handling UART

Postby ESP_Sprite » Sat Sep 09, 2023 1:35 am

uart_read_bytes is blocking (as in: while waiting for bytes, FreeRTOS simply won't run the calling task and instead gives CPU time to all other unblocked tasks) and as such it won't starve other tasks. You can happily call it in a loop.

torkleyy
Posts: 6
Joined: Tue Sep 05, 2023 5:51 pm

Re: How to use custom interrupt for handling UART

Postby torkleyy » Sat Sep 09, 2023 11:37 am

Sorry, I wasn't clear about the

Code: Select all

uart_read_bytes
. I called it busy looping on uart_read_bytes since the only option would be to call it with timeout NON_BLOCKING, since, as you say, otherwise the task will be suspended.

I cannot have the task get suspended because once I receive data on UART, it will take too much time until FreeRTOS gives back the context to me (not to mention that even if it was fast enough, the delay would not be predictable which is important for the protocol I have to implement in my application).

MicroController
Posts: 1700
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: How to use custom interrupt for handling UART

Postby MicroController » Sat Sep 09, 2023 4:29 pm

torkleyy wrote:
Fri Sep 08, 2023 10:53 am
I will sometimes need to reply within 300us of receiving a byte
You'll want to make sure that you are quickly informed about the received byte, i.e. you want to configure a low rx_timeout_thresh and possibly also a low rxfifo_full_thresh.
event queues have more latency than that due to FreeRTOS context switches
Sounds quite implausible. Source?

torkleyy
Posts: 6
Joined: Tue Sep 05, 2023 5:51 pm

Re: How to use custom interrupt for handling UART

Postby torkleyy » Sat Sep 09, 2023 6:38 pm

MicroController wrote:
Sat Sep 09, 2023 4:29 pm
event queues have more latency than that due to FreeRTOS context switches
Sounds quite implausible. Source?
From the FreeRTOS docs:
When a task attempts to read from an empty queue the task will be placed into the Blocked state (so it is not consuming any CPU time and other tasks can run) until either data becomes available on the queue, or the block time expires.
https://www.freertos.org/Embedded-RTOS-Queues.html

That means, other tasks will need to yield before I'm able to receive the event. Even if tasks on that core never blocked for more than 300us (minus the time it takes FreeRTOS to switch back to my task), I will end up with a new problem which is a varying delay between receiving the byte physically and processing the event in my task. Unfortunately for my use case, there is an exact time frame with minimal error (+/-20us tolerance) in which I need to reply...

I fear in this case my only option is to write a custom UART driver on top of the HAL provided by the IDF.

ESP_Sprite
Posts: 9720
Joined: Thu Nov 26, 2015 4:08 am

Re: How to use custom interrupt for handling UART

Postby ESP_Sprite » Sun Sep 10, 2023 2:20 am

torkleyy wrote:
Sat Sep 09, 2023 6:38 pm
That means, other tasks will need to yield before I'm able to receive the event. HAL provided by the IDF.
Not in ESP-IDF. We configure FreeRTOS as pre-emptive, meaning that if something happens (in an interrupt or otherwise) that un-blocks a task that is of higher priority than the one currently running, it will immediately switch to that task.

MicroController
Posts: 1700
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: How to use custom interrupt for handling UART

Postby MicroController » Sun Sep 10, 2023 8:34 am

torkleyy wrote:
Sat Sep 09, 2023 6:38 pm
I fear in this case my only option is to write a custom UART driver on top of the HAL provided by the IDF.
Before resorting to such extreme measures ;-) I suggest you first actually try the quick&easy approach:
set RX timeout and RX FIFO threshold to whatever is appropriate for your protocol, then

Code: Select all

uint8_t rx_byte;
while(1) {
  if (uart_read_bytes(MY_UART, &rx_byte, 1, portMAX_DELAY) > 0) {
    // react to the data received...
  }
}
(uart_read_bytes should always be called with a length of 1 if a timely return is needed; see also viewtopic.php?f=13&t=35430#p119353)

Let this run in a task of elevated priority and see if it works for your case.

torkleyy
Posts: 6
Joined: Tue Sep 05, 2023 5:51 pm

Re: How to use custom interrupt for handling UART

Postby torkleyy » Sun Sep 10, 2023 11:59 am

ESP_Sprite wrote:
torkleyy wrote:
Sat Sep 09, 2023 6:38 pm
That means, other tasks will need to yield before I'm able to receive the event. HAL provided by the IDF.
Not in ESP-IDF. We configure FreeRTOS as pre-emptive, meaning that if something happens (in an interrupt or otherwise) that un-blocks a task that is of higher priority than the one currently running, it will immediately switch to that task.
Ah, thanks for clearing that up. I should have read the FreeRTOS chapter fully... For anybody else interested, here is the source: https://docs.espressif.com/projects/esp ... preemption
MicroController wrote:
torkleyy wrote:
Sat Sep 09, 2023 6:38 pm
I fear in this case my only option is to write a custom UART driver on top of the HAL provided by the IDF.
Before resorting to such extreme measures ;-) I suggest you first actually try the quick&easy approach:
set RX timeout and RX FIFO threshold to whatever is appropriate for your protocol, then

Code: Select all

uint8_t rx_byte;
while(1) {
  if (uart_read_bytes(MY_UART, &rx_byte, 1, portMAX_DELAY) > 0) {
    // react to the data received...
  }
}
(uart_read_bytes should always be called with a length of 1 if a timely return is needed; see also viewtopic.php?f=13&t=35430#p119353)

Let this run in a task of elevated priority and see if it works for your case.
That is what I had tried, and it gave me unreliable delays. However, come to think of it I don't think I set it up well: While my uart task was pinned to CORE1 with high priority, I installed the driver on CORE0 so I think its interrupt handler might have been preempted by the Wifi communication... ^^'

I will give it another try, installing the driver on CORE1, and experiment with the interrupt config.

ESP_Sprite
Posts: 9720
Joined: Thu Nov 26, 2015 4:08 am

Re: How to use custom interrupt for handling UART

Postby ESP_Sprite » Mon Sep 11, 2023 9:06 am

Do keep in mind what was said here, though. The defaults are set for high bandwidth, not low latency, so you probably want to change those.

torkleyy
Posts: 6
Joined: Tue Sep 05, 2023 5:51 pm

Re: How to use custom interrupt for handling UART

Postby torkleyy » Mon Sep 11, 2023 12:21 pm

Thanks for the help, I was able to get it to work:

* install driver on task with CPU1 affinity
* set rx fifo full threshold to 1 and enable the corresponding interrupt
* block on DATA event (I use the events to detect a bunch of other conditions as well)
* do non blocking

Code: Select all

uart_read_bytes
* check if only a single byte was received to handle high latency cases (this helps catch rare instances of lags shortly after boot up - I'm not sure why this happens since I confined main and all tasks I spawn to CORE0. Of course not ideal since even if we only read a single byte there could have been some lag)

I also stumbled upon a problem where some bytes would arrive distorted if and only if Wifi was enabled. Using an external power supply solved the problem.

There is only one thing left: Is it possible to receive a notification when the TX FIFO becomes empty? I would like to maintain my own tx buffer in my application code in order to cancel transmission in certain cases, which I don't think is possible using the driver's functionality - it does not offer a way to clear the tx buffer / fifo (comments in the code seem to indicate there is hardware bug?).

Who is online

Users browsing this forum: No registered users and 73 guests