Time-Critical function with "taskENTER_CRITICAL()" ?

esp-geek
Posts: 2
Joined: Thu May 03, 2018 3:07 pm

Time-Critical function with "taskENTER_CRITICAL()" ?

Postby esp-geek » Thu May 03, 2018 8:31 pm

Hello there,

I am working on a application that uses uart for communication via a rs485 bus and wifi.
The rs485 host requires precise timing in means of not pausing the transmission in-between the data-packet and to disable the transmitter immediately after the last byte has been sent. Without running wifi this is no problem. But as soon as wifi is active it causes occasional errors by interrupting the data transmission by ~1ms in-between the packet or prior disabling the transmitter.

During research, I came across that topic: https://esp32.com/viewtopic.php?t=1703 describing the use of taskENTER_CRITICAL()/ taskEXIT_CRITICAL(). Since it seems to cover my problem I modified my code as follows:

Code: Select all

uint16_t rs485_send(uint8_t *data, uint16_t len){
	static const char *TAG = "send_rs485";	
	uint16_t sent_bytes = 0;
	portMUX_TYPE myMutex = portMUX_INITIALIZER_UNLOCKED;
	gpio_set_level(RS485_TXEN_PIN,1);	//select rs485 comm type: transmitter
	taskENTER_CRITICAL(&myMutex);
	sent_bytes = uart_write_bytes(RS485_UART, (char*) data, len);
	if(uart_wait_tx_done(RS485_UART, 2) == ESP_ERR_TIMEOUT) ESP_LOGE(TAG,"timeout occurred during rs485 tx");	
	gpio_set_level(RS485_TXEN_PIN,0);	//select rs485 comm type: receiver	
	taskEXIT_CRITICAL(&myMutex);
	return sent_bytes;
}
But it did not solve the issue.
Now the core panics printing this:

Code: Select all

Guru  Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU0)
[...]
0x40087a46: vListInsert at C:/esp/esp-idf/components/freertos/list.c:188 (discriminator 1)
0x4008581f: vTaskPlaceOnEventList at C:/esp/esp-idf/components/freertos/tasks.c:3529
0x40086780: xQueueGenericReceive at C:/esp/esp-idf/components/freertos/queue.c:2037
0x40127d8d: uart_wait_tx_done at C:/esp/esp-idf/components/driver/uart.c:1153
This crash maybe is caused by uart.c using freeRTOS APIs. Using these in a critical section is not allowed according to https://www.freertos.org/taskENTER_CRIT ... TICAL.html .

I also tried to assign the highest priority to the task calling the rs485_send function prior experimenting with critical section but this did not change anything.

Does somebody have a idea how to deal with that timing issue.
The data packets are around 300Bytes in size and are transmitted in less than 6ms. 'Basically' I need to pause everything that could interfere with the transmission for that short timespan.

Kind regards

ESP_Angus
Posts: 2344
Joined: Sun May 08, 2016 4:11 am

Re: Time-Critical function with "taskENTER_CRITICAL()" ?

Postby ESP_Angus » Thu May 03, 2018 11:49 pm

Hi,

This is an interesting question! As well as calling non-interrupt-safe FreeRTOS APIs, the UART driver uses an interrupt handler to process data transmission (ie filling the transmit 128 byte transmit FIFO as it empties), so the driver can't work as expected with interrupts disabled.

There are at least two ways you can tackle the problem:

If you want to keep using the UART driver:

- When you call uart_driver_install(), pass ESP_INTR_FLAG_LEVEL3 for the flags parameter. This requests the highest priority interrupt line which can still be handled in C.

- Pin the task which calls uart_driver_install() to Core 1. This core doesn't get WiFi interrupts, so it's a lot less likely that the UART driver will have to contend with timing problems. If you were calling uart_driver_install() from app_main(), then you'll unfortunately have to create a separate short-lived task on Core 1 to do this as app_main() is pinned to Core 0. Use xTaskCreatePinnedToCore(.... , 1) for this.

It is less important which core the code which calls uart_write_bytes() and other UART driver APIs is pinned to, as this code is pushing into buffers which are handled by the interrupt handler routine on its assigned core.

**

If you don't want to use the UART driver (or the above approach doesn't work for some reason), you can write into the UART FIFO registers directly yourself with interrupts disabled. You can crib from some low-level code in IDF which does this, for example gdstub:
https://github.com/espressif/esp-idf/bl ... stub.c#L44

(The above routine waits until there is at least one free byte in the transmit FIFO, then writes a byte to the transmit FIFO. You can do this in a loop with interrupts disabled to transmit an array of bytes in the minimum possible time, albeit at the cost of having the CPU busy that entire time. Note that it's safe to return and re-enable interrupts once all bytes are written to the hardware FIFO, it will keep transmitting them in the background.)

You've probably gathered that this approach is not compatible with using that particular UART for other things or enabling the driver on that number UART, but I'm thinking that for RS-485 you probably are using a dedicated UART already.

esp-geek
Posts: 2
Joined: Thu May 03, 2018 3:07 pm

Re: Time-Critical function with "taskENTER_CRITICAL()" ?

Postby esp-geek » Fri May 04, 2018 5:14 pm

Hello,

While the first possibility did not solve the problem 100% (although the error occurred not that often), your second approach absolutely did the trick! THX!

But nevertheless, it is interesting to know, that the uart driver runs on the core that called its setup routine.
During troubleshooting I had also tried to pin the task that triggers the transmission to core 1. -without observing improved results-
As you already guessed, my setup routine was called by app_main() installing the driver on core 0. That explains the unchanged behavior...
Note that it's safe to return and re-enable interrupts once all bytes are written to the hardware FIFO, it will keep transmitting them in the background.)
Should someone run in the similar timing issue while using RS485, it is important to wait until all bits are transmitted, prior disabling the transmitter (e.g. by (re-)setting a pin). Otherwise the data in the TX-FIFO will leave the esp UART but will not be sent out onto the bus. I did this by polling UART_ST_UTX_OUT to reach the state TX_IDLE (0).

Thanks for your great input!

Who is online

Users browsing this forum: No registered users and 132 guests