Page 1 of 1

UART activity detection

Posted: Wed Jun 11, 2025 9:11 pm
by dg9ngf
Hello,

I want to avoid collisions on my RS-485 bus and not start sending anything if another transmission has already started. On an AVR MCU I can have the UART receive byte interrupt and also have an interrupt handler for level changes on that pin (i.e. going from high to low) as an immediate indicator.

Can I also do that on ESP32? I'm using the normal uart_read_bytes() function and have connected the pins to the UART feature. Can I still add an ISR that handles level changes (i.e. falling edges)? I guess I should use the gpio_isr_register() function to do that, haven't done that before.

Alternatively, I'd also use an interrupt that is triggered only when a UART frame is starting, not on every single level change. If that exists.

I only need to know when somebody else is starting to speak. The communication is normally followed by all nodes on the bus so everyone knows when a message is over and the bus is free again.

If I cannot do that at all on ESP32, I might need to physically connect two pins and use one to receive and the other to monitor. But that's a PCB design change that I'd need to know in advance.

Re: UART activity detection

Posted: Wed Jun 11, 2025 10:46 pm
by MicroController
I think you should be able to have multiple input signals connected to a single pin at the same time, i.e. UART input and GPIO input.
However, this still creates a race condition because of the non-zero time(*) a call to uart_write_bytes(...) takes. If during that time another node starts transmitting, by the time you detect this via an interrupt, or any other means, your TX data is either already on its way to the TX pin (driver, UART TX buffer) or may already have started being transmitted.

(*) IIRC, when I timed uart_write_bytes(...) on a C3, it took something in the order of hundreds of microseconds to hand off a few bytes to the driver - with an empty TX buffer and no concurrent accesses.

Re: UART activity detection

Posted: Fri Jun 13, 2025 11:54 am
by ok-home
Hi,
would you be interested in a hardware collision detector?
https://docs.espressif.com/projects/es ... n-0ptions

Re: UART activity detection

Posted: Thu Jun 19, 2025 11:08 am
by dg9ngf
Would it help to do this for setup to disable any unnecessary delays before sending?

Code: Select all

CLEAR_PERI_REG_MASK(UART_IDLE_CONF_REG(RS485RX_GPIO), UART_TX_IDLE_NUM_M);
I've looked at this collision detection feature before, then later discarded it as unusable in my case, and now looked at it again. It seems to only detect and report observed collisions, but does nothing to avoid them. I can detect collisions on the application layer already, but still need to avoid them by not even starting to send if anything is currently transmitted elsewhere. I did some simple experiments with the AVR device long ago and it was pretty effective compared to blindly sending whenever there was something to tell.

So would it work like this? I have no clue what side effects all these functions have and how they'll step on each others' feet.

Code: Select all

#define RS485RX_GPIO 16

gpio_pad_select_gpio(RS485RX_GPIO);
gpio_set_direction(RS485RX_GPIO, GPIO_MODE_INPUT);
gpio_set_intr_type(RS485RX_GPIO, GPIO_INTR_NEGEDGE);
gpio_intr_enable(RS485RX_GPIO);
ESP_ERROR_CHECK(gpio_isr_register(onRS485Activity, NULL, ESP_INTR_FLAG_LEVEL3, NULL));

void onRS485Activity(void* unused)
{
	// TODO
}
I guess I'd have to clear some interrupt flag manually in the callback function but haven't figured out how to do that yet. The whole GPIO interrupt topic seems to be massively underdocumented. Is it even meant to be used or should I use polling in a loop? (Needless to say that it's way slower and less efficient.)

The UART code I call before this looks like following:

Code: Select all

#define RS485_UART_NUM (UART_NUM_2)

ESP_ERROR_CHECK(uart_driver_install(RS485_UART_NUM, rxBufferSize, txBufferSize, queueSize, &uartQueue, intr_alloc_flags));
ESP_ERROR_CHECK(uart_param_config(RS485_UART_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(RS485_UART_NUM, RS485TX_GPIO, RS485RX_GPIO, RS485DE_GPIO, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(uart_set_mode(RS485_UART_NUM, UART_MODE_RS485_HALF_DUPLEX));

Re: UART activity detection

Posted: Thu Jun 19, 2025 5:06 pm
by dg9ngf
Quick update: it doesn't work yet.

If I call all the functions
- gpio_pad_select_gpio
- gpio_set_direction
- gpio_set_intr_type
- gpio_isr_register
- gpio_intr_enable
then the ESP32 won't receive anything anymore.

If I leave out the first two, receiving works again. Same if I do call all of the listed functions and then this afterwards to reconnect the UART:

Code: Select all

esp_rom_gpio_connect_in_signal(RS485RX_GPIO, U2RXD_IN_IDX, false);
No lifesign from the ISR. Not with ESP_LOGI and not with another global variable and printing that in the main loop. Here's the current code:

Code: Select all

#include <tons of stuff>

static volatile bool isReceivingData = false;
static volatile bool isrCalled = false;

bool getIsrCalled()
{
	return isrCalled;
}

void app_main()
{
	// UART setup
	// ...

	ESP_ERROR_CHECK(gpio_set_direction(RS485RX_GPIO, GPIO_MODE_INPUT));
	ESP_ERROR_CHECK(gpio_set_intr_type(RS485RX_GPIO, GPIO_INTR_NEGEDGE));
	ESP_ERROR_CHECK(gpio_isr_register(onRS485Activity, NULL, ESP_INTR_FLAG_LEVEL3 | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED, NULL));
	ESP_ERROR_CHECK(gpio_intr_enable(RS485RX_GPIO));
	// no errors reported here

	while (true)
	{
		vTaskDelay(pdMS_TO_TICKS(1000));
		ESP_LOGI(TAG, "ISR ever called: %d", (getIsrCalled() ? 1 : 0));
	}
}

static void IRAM_ATTR onRS485Activity(void* unused)
{
	gpio_intr_disable(RS485RX_GPIO);   // will be re-enabled after the bus is free
	isReceivingData = true;
	isrCalled = true;
	ESP_LOGI(TAG, "Bus activity detected");   // never seeing this, not even once
}
Calling the full gpio_config stuff after the UART setup and before ISR setup also doesn't help.

How does the GPIO interrupt thing work, finally? Does it work at all? Never managed to get it right. Also no documentation available.

ChatGPT has guided me for the past few hours (it's tedious to rewire the thing between programming and testing) and now suggested manualy configuring the GPIO matrix, but it seems a little lost now. Essentially, it stated that what I want is not possible and I should physically mirror the pin. But why isn't the ISR even working without active UART?