Uart in 485 mode

mark.lamneck
Posts: 3
Joined: Fri Feb 09, 2024 7:43 am

Uart in 485 mode

Postby mark.lamneck » Sun Feb 11, 2024 4:41 pm

I’m new to the forum and hope to find some support here.

I want to use an esp32 as a node in a 485 bus with the following conditions:
  • baudrate: 921600 bits/sec
  • no master slave, so all nodes can send at all times
  • inter frame delay >= 10bytes ~ 100us
  • addressed nodes are sending a 1byte acknowledge within the inter frame delay
Requirements for the ESP:
  • Response time to send an ack <= 70us
  • Arbitration: only send if rx is not busy and not in the inter frame delay (except it needs to send an ack)
  • Collision detection: The receiver on the 485 chip is enabled all the time. The esp should compare the received bytes with the one it sends and stop sending if there’s a difference.
I started with the uart_events example and set rx_timeout_thresh=1. The data event is approx. 30us after the last byte has been completed (10us timeout + 20us delay by rtos). If I send an ack right away, I see it on the bus aprox. 30us later, so in sum 60us. That would be fine, but could be better. I can improve it slightly by setting rx_full_thresh=1. This results in 50us response time over all. Now I have a few questions.

Q1: By setting rx_full_thresh=1, I was expecting to see data events after each received byte (of course with a delay). But what I see is that these events occur after the end of the msg. In my understanding the events are sent to the queue from the isr and consumed by the thread reading the queue. But it seems like there is a layer in between?

Q2: The documentation says the UART_RS485_CLASH_INT is generated when there is a collision on the bus. However, in case of collisions, I never receive an event in my uart driver, regardless of the settings for uart_mode, loopback and interrupt configuration. How can I handle this interrupt? Do I have to register my own isr? How do I do that?

Q3: The documentation says if the UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN flag is set, the transmitter will only send if rx is not busy. I thought this flag would be set by setting uart_mode to UART_MODE_RS485_COLLISION_DETECT or UART_MODE_RS485_APP_CTRL, but it seems not. How can I manually set this flag? Or in general what is the recommended way to access hardware registers? For example, if I want to check if the bit is really set?

Q4: Any suggestions how to handle the arbitration? I guess even if the UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN approach, explained in Q3 would work, I cannot use it, because I have to have a pay attention to the inter frame delay. Sending right away after a msg is not allowed. And I guess this is what the hardware would do, right? One idea is to connect an additional pin to the rx pin of the uart, configure a pin change interrupt, but not enable it and just read/clear the interrupt request bit (the bit would be my indicator for bus activity within a timeslot between clearing and checking it again). How can achieve this on the esp?

Q5: I’ve checked the delay between the call of uart_write_bytes and the start of the msg physically on the bus. It’s around 10us. Is there any chance to speed this up as it increases the chance for collisions.

mark.lamneck
Posts: 3
Joined: Fri Feb 09, 2024 7:43 am

Re: Uart in 485 mode

Postby mark.lamneck » Mon Feb 19, 2024 7:15 pm

I figured out some things by myself. I found that the uart registers are accessible through the uart struct provided in soc/uart_struct.h.

Code: Select all

#include "soc/uart_struct.h"
...
UART2.rs485_conf.rx_busy_tx_en = 0;      //0 => enable hardware arbitration
...

Answer to Q3: I found that by setting the mode of the uart with uart_set_mode the hardware arbitration gets disabled. Using UART2.rs485_conf.rx_busy_tx_en = 0; after a call to uart_set_mode enables it and it works. However like expected, if the message is initiated while the bus is busy, the message is started right after the current message has finished (2 bits delay or so). Because I need an inter frame delay of 10bytes, I cannot use this feature. Is there an option to for a delay? I couldn't find it.

Suggestion to Q4: I found that I can use the st_urx_out status register to check if the bus is busy.

Code: Select all

bool rxFree(){
  for (int i=0; i<5; i++){
    tp2.testPulse();
    if (UART2.status.st_urx_out != 0) return false;
  }
  return true;
}
...
if (rxFree()){
  uart_write_bytes(UART_PORT, msg, msgLen);
}
This basically works, but the chance for collisions depends on the time between the call to uart_write_bytes and the first byte of the message physically on the bus. As already discussed in Q5 of my previous post, this time is fairly large and depends on the message length. This becomes a problem if another node of the bus comes in between the 2 things happening like in the following capture. Currently I'm using UART0 (yellow msg) and UART2 (blue message) and I have them connected so that the receive each other. The uart responsible for the blue messages should arbitrate, so no blue message should start within the yellow. In this example, the check for the bus to be free, is done when it is free (within the red circle), but because it takes 40us from the call to uart_write_bytes till the message is finally on the bus (blue gets low), this would end up with a collision.
Is there any chance to speed this up to reduce the chance for collisions? Other suggestions on how to do this?
collision.PNG
collision.PNG (122.57 KiB) Viewed 264 times

Who is online

Users browsing this forum: No registered users and 137 guests