Hi, new here. I am using esp-idf v5.3.1 for an S3 device, and need freertos advice on getting a uart to stream bursts of data.
This device connects to a legacy system and the uart timing is important. I need to send 59 bytes at 230400-baud every 4 ms – the data packet lasts about 2.6ms, leaving 1.4ms inter-packet gap. The data packet cannot have pauses. This is easy-peasy on a non-rtos stm or such, but I am unsure whether I have this control under freertos, even with a dedicated core.
On core 0, I have an lvgl/gui task running at a low priority. No wifi or bluetooth at the moment. Also on core 0 is a medium-priority sd-card task that, when enabled, reads several-thousand-byte chunks from a file into ping-pong buffers (this happens every 600ms or so as throttled by the buffers being emptied). This code is all running properly.
I have a uart task on core 1 (I used a boot-time init task to ensure the uart isr is also on core 1). There is only one other lower-priority task on core 1, which is not used during uart streaming (does that task need to be disabled somehow?).
My plan is to use a 4ms timer to read from the ping-pong buffers – it will only grab 24 bytes at a time and send them to the uart task where it gets formatted into a 59-byte packet and sent to the uart. I am planning on using a queue.
I am concerned that rtos interruptions could happen at multiple points. How do I give the 4ms timer top priority so it does not get paused by freertos activity? The timer should be on core 0 where the source ping-pong buffers reside and just pump 24 bytes into a queue every 4ms. The uart task is on the other core (virtually-unloaded) feeding a uart with a tx buffer and isr, so it seems like it should be able to handle the streaming.
Where does the queue data reside? Can writing or reading the queue get paused due to freertos or just due to memory access considerations? In the uart task, do I need to wrap the uart_write_bytes(…) with critical-section code somehow so it does not get interrupted while dumping 59-bytes to the uart tx buffer?
Pardon the multiple questions – just a lot of inter-related moving parts I am starting to appreciate. I have been working with the esp32 and freertos for about a year (though have been coding C for many decades). Any insight appreciated – thx.
Under a real time system like FreeRTOS, UART output should be defined as interrupt driven, thus if the application prepares the full message, and then starts the transmission, then as the UART get room, it should generate an interrupt, and then the ISR puts the next chunk of characters into the UART hardware buffer. With that method, the only way you can get a gap is if some task disables interrupts for an extended period, which should be considered a violation of Real-Time requirements. (Task might need to disable interrupt for a few clock cycles to handle operations that need to be atomic).
Vendor supplied routines that don’t use interrupts aren’t really a good option in an RTOS, but you might be able to get it to work by making it a high priority task.
To extend on Richard’s answer, ESP-IDF has interrupt control for the UART and one FreeRTOS example. The GPS example uses a pattern interrupt to start parsing input after \n is received.
While there aren’t any examples, the documentation does mention a txfifo_empty_intr_thresh which could be used to schedule enqueuing the next 24 bytes. https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32/api-reference/peripherals/uart.html
Seems that the UART FIFOs even when using the default 128 bytes are large enough to store a complete packet and to transmit it under HW control. This should avoid intra-packet pauses.
The only thing to consider is the latency and jitter after the 4 ms timer fires to put the payload into the UART task queue, waking up the UART task (waiting on its input queue), format and store the complete frame into the the TX FIFO and start the transfer.
It seems that enabling the TX empty interrupt starts the UART transmission so you can use the empty interrupt to disable it to prepare the next transmission/storing the next complete frame and just set a flag or sth. as error check that the TX FIFO is empty as expected when the next payload packet arrives in the UART task input queue.
You might also have a look at FreeRTOS stream & message buffers - FreeRTOS™ since you you seem to have a single writer - single reader data flow.
I tried getting this running with the 4ms timer feeding a queue, and a uart task pulling out of the queue to send to uart, as I first envisioned. I also set/reset gpios on the timer and task to watch activity on my scope.
However, while the 4ms timer was rock-solid, the uart task was only getting one in three payloads (I had to disable queue-send error messages to see what was happening). I am not certain why it got one and missed two, as the task was ending quickly and there should have been time for it to run again to catch the next post to the queue. I have freertos set to 1000 Hz for 1ms switches, but that must not have been it. Is 1000 Hz too fast for normal use and should I switch back to 100Hz?
Anyway, then I just got rid of the uart task and queue altogether, and in the 4ms timer I did all of the work – pulling data from the ping-pong buffers, building the packet, and sending the 59-bytes to the uart (which has a tx buffer and isr). To my delight, it worked great, and the time spent inside of the timer was only 25-30 us. The uart cadence is perfect now. Top trace is uart output and lower trace is 4ms timer:
Thanks to all for the information. I still don’t fully understand what I could have changed to get a more responsive task getting the queue data, but I did find a workaround, so on to the next head-scratcher…