Issues with UART controlling Isolated RS485/MODBUS
Posted: Tue Jul 02, 2024 2:11 pm
I am currently working with a PCBA that has a esp32s2 chip that uses a UART channel to control a VFD over a simple MODBUS interface. The module is set up to use UART serial that is connected to a RS485 Field Isolator with RX, TX, and TX_enable. When my module sends a packet, it sets TX_enable in order for the isolator to pass the signal, and drops it to receive the response.
I am seeing an issue where when the module transmits a UART packet, it will hold the TX_enable for high too long and I miss the response. In my test systems I have two identical PCBAs running identical firmware connected to identical VFDs and one will show this issue for less than 5% of transceive events, and the other will fail on over 25% of packet comms, so there could be some hardware issues as well.
In this trace we are monitoring the TX line out of the esp32 on the yellow channel and the TX_enable out of the esp32 on the green channel. You can see that the TX_enable line is getting held for ~6ms past the end of the TX packet - and unfortunately the VFD response is just 2ms after the packet and it gets clobbered in the Field Isolator because the TX_enable line is still high.
The UART Initalization is pretty straight-forward (the code is basically from the examples). I'm using ASCII, N,8,1 @ 115.2k:
The primary TX handler sets the TX_enable pin, sends the packet, then drops the TX_enable pin (I left in some commented out lines of some attempts to fix this issue:
... and the _do_tx() function is pretty simple:
According to the API for uart_write_bytes:
Has anyone run into something like this before, and where should I look to start to address this?
Thanks in advance.
I am seeing an issue where when the module transmits a UART packet, it will hold the TX_enable for high too long and I miss the response. In my test systems I have two identical PCBAs running identical firmware connected to identical VFDs and one will show this issue for less than 5% of transceive events, and the other will fail on over 25% of packet comms, so there could be some hardware issues as well.
In this trace we are monitoring the TX line out of the esp32 on the yellow channel and the TX_enable out of the esp32 on the green channel. You can see that the TX_enable line is getting held for ~6ms past the end of the TX packet - and unfortunately the VFD response is just 2ms after the packet and it gets clobbered in the Field Isolator because the TX_enable line is still high.
The UART Initalization is pretty straight-forward (the code is basically from the examples). I'm using ASCII, N,8,1 @ 115.2k:
Code: Select all
//==============================================================================
// Initialize UART
//==============================================================================
void McuUartBasic_Esp32::init(uart_port_t uart_num, int tx_io_num, int rx_io_num, int rts_io_num, int cts_io_num, \
int baud_rate, uart_word_length_t data_bits, uart_parity_t parity, uart_stop_bits_t stop_bits, \
uart_hw_flowcontrol_t flow_ctrl, uart_sclk_t source_clk ) {
ESP_LOGI(LOG_Tag, "[%p|%d] Initializing UART#%d...", this, __LINE__, uart_num);
_uart_num = uart_num;
uart_config_t uart_config;
memset( &uart_config, 0, sizeof(uart_config_t) );
uart_config.baud_rate = baud_rate;
uart_config.data_bits = data_bits;
uart_config.parity = parity;
uart_config.stop_bits = stop_bits;
uart_config.flow_ctrl = flow_ctrl;
uart_config.source_clk = source_clk;
// Configure UART parameters
uart_param_config(_uart_num, &uart_config);
// Set UART pins (TX, RX, RTS)
uart_set_pin(_uart_num, tx_io_num, rx_io_num, rts_io_num, cts_io_num);
// Install UART driver using an event queue here if needed
uart_driver_install(_uart_num, MB_UART_RX_BUF_SIZE, 0, 0, NULL, 0);
uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX);
ESP_LOGI(LOG_Tag, "[%p|%d] Initialized UART#%d!", this, __LINE__, _uart_num);
}
Code: Select all
#==============================================================================
# UART
#==============================================================================
add_compile_definitions( MODBUS_UART_CHANNEL=UART_NUM_1 )
add_compile_definitions( MODBUS_UART_BAUD=115200 )
add_compile_definitions( MODBUS_UART_WORD_LEN=UART_DATA_8_BITS )
add_compile_definitions( MODBUS_UART_PARITY=UART_PARITY_DISABLE )
add_compile_definitions( MODBUS_UART_STOP_BITS=UART_STOP_BITS_1 )
add_compile_definitions( MODBUS_UART_FLOW_CTRL=UART_HW_FLOWCTRL_DISABLE )
add_compile_definitions( MODBUS_UART_CLOCK_SRC=UART_SCLK_DEFAULT )
The primary TX handler sets the TX_enable pin, sends the packet, then drops the TX_enable pin (I left in some commented out lines of some attempts to fix this issue:
Code: Select all
void DevModbus::_do_modbus_tx(void) {
uint16_t _tx_len = strlen((char*)_tx_buffer);
//uint8_t _timeout_ms = (uint8_t)(1000.0f * (float)_tx_len * 8.0f / 115200.0f) + 1;
//ESP_LOGI(LOG_Tag, "_do_read03_tx_ascii() _tx_len:%u _timeout_ms:%u", _tx_len, _timeout_ms);
if(_tx_en) _tx_en->set(1);
_uart_channel->_do_tx( _tx_buffer, _tx_len, 20 / portTICK_PERIOD_MS, true );
//_uart_channel->_do_tx( _tx_buffer, _tx_len, _timeout_ms / portTICK_PERIOD_MS, true );
//_uart_channel->_do_tx( _tx_buffer, _tx_len, 0, true );
if(_tx_en) _tx_en->set(0);
}
Code: Select all
//==============================================================================
// Basic UART Transmit
//==============================================================================
void McuUartBasic_Esp32::_do_tx(uint8_t* tx_buffer, size_t len, uint32_t timeout_tics, bool rx_flush ) {
if(_uart_num == -1) {
ESP_LOGI(LOG_Tag, "[%p|%d] Can't do_tx without initialized UART! [%d]", this, __LINE__, _uart_num);
return;
}
if(rx_flush) uart_flush(_uart_num);
uart_write_bytes(_uart_num, (const char*)tx_buffer, len);
if(timeout_tics)
uart_wait_tx_done(_uart_num, timeout_tics);
}
It seems that the uart_write_bytes is blocking for much longer than it should, and I can't seem to track down any reason why it would be. As you can see in the intialization that I have not set up a TX buffer so another confirmation that I should expect the block during TX:Send data to the UART port from a given buffer and length,.
If the UART driver's parameter 'tx_buffer_size' is set to zero: This function will not return until all the data have been sent out, or at least pushed into TX FIFO.
Otherwise, if the 'tx_buffer_size' > 0, this function will return after copying all the data to tx ring buffer, UART ISR will then move data from the ring buffer to TX FIFO gradually.
This functionality all lives in its own FreeRTOS task. I've tried to move the task between the cores, I've tried to increase the priority, I've tried to give it more stack (just reaching at this point), but nothing will help those transmit events where the TX_enable holds over.tx_buffer_size -- UART TX ring buffer size. If set to zero, driver will not use TX buffer, TX function will block task until all data have been sent out.
Has anyone run into something like this before, and where should I look to start to address this?
Thanks in advance.