espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.7k stars 7.3k forks source link

RS485 data collision detector failing (IDFGH-1274) #3569

Open neorevx opened 5 years ago

neorevx commented 5 years ago

Hi,

I'm working with UART RS485 at low level and I received UART_RS485_CLASH_INT interruptions without actually there is data collision. Oscilloscope image attached: yellow is TX and blue is RX, connected direct on ESP32 pins. TEK0002

My code was inspired by the source "uart.c". To prove that the system does not detect collision correctly, I did a little test as shown:

static void writer_task()
{

    const int uart_num = UART_NUM_2;

    uart_config_t uart_config = {
        .baud_rate = J1708_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .rx_flow_ctrl_thresh = 122,
        .use_ref_tick = false
    };

    ESP_LOGI(TAG, "Starting Writer...");

    // Configure UART parameters
    uart_param_config(uart_num, &uart_config);

    ESP_LOGI(TAG, "UART1 set pins, mode and install driver.");

    uart_set_pin(uart_num, ECHO_TXD1, ECHO_RXD1, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    // Inverse line to match with hw
    uart_set_line_inverse(uart_num, UART_INVERSE_TXD);

    // Install UART driver
    uart_driver_install(uart_num, 1024, 0, 0, NULL, 0);

    // Set RS485 mode
    uart_set_mode(uart_num, UART_MODE_RS485_COLLISION_DETECT);

    // buffer for send
    uint8_t *dtmp = (uint8_t *)malloc(1024);

    while (true)
    {
        uart_write_bytes(uart_num, (const char*)dtmp, 5);

        bool col;
        uart_get_collision_flag(uart_num, &col);

        printf("COLLISION1: %d\n", col);

        vTaskDelay(1000 / portTICK_PERIOD_MS);

        uart_get_collision_flag(uart_num, &col);

        printf("COLLISION2: %d\n", col);

        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

The result is basically:

COLLISION1: 1
COLLISION2: 1
COLLISION1: 1
COLLISION2: 1
COLLISION1: 1
COLLISION2: 1

In my version, when a put: uart->rs485_conf.tx_rx_en = 1; there's no collision, but I receive data from rx by tx.

There any workaround for that? Maybe uart.c miss some configuration.

negativekelvin commented 5 years ago

Could it be because of the uart_set_line_inverse?

neorevx commented 5 years ago

No. I thought about that possibility. Then I shorted RX with TX. In this case there is no collision. I think it's something about sampling. As the data is sent by the transceiver to the bus and is later read, there is a small time lag that the hardware is not accepting. I am using the MAX3485 as a transceiver in the "C" mode described in the documentation.

negativekelvin commented 5 years ago

Well this seems wrong, mixing enable bits and status bits (I guess this doesn't matter if they are the same mask)

https://github.com/espressif/esp-idf/blob/81ca1c01395f604972fbf141cfbe49764a746023/components/driver/uart.c#L994-L996

And only clearing one

https://github.com/espressif/esp-idf/blob/81ca1c01395f604972fbf141cfbe49764a746023/components/driver/uart.c#L998

neorevx commented 5 years ago

This is not the best practice, but it should not be the problem. It's same mask. Otherwise, the interrupt would not be triggered. In my version I used the correct flag and got same problem.

About clear, it's missing! And it can be a big problem: I think interrupt will be triggered every time. However, it still would not solve the problem. I have verified that the clash is responsible for the error, not frame error or parity error.

I found another error in uart.c, but I'll open another Issue.

alisitsyn commented 5 years ago

Hi neorevx, negativekelvin,

Thank you for reporting this issue. I agree that UART_RS485_FRM_ERR_INT_ST and UART_RS485_PARITY_ERR_INT_ST bits should be cleared in status register. Your issue reproduced on my side. I perform investigation of collision detection issue and will communicate with hardware team to clarify hw behavior of the feature. It will take some time to get information and test it. I will let you know as soon as receive any information. The driver will be corrected accordingly after this.

koobest commented 5 years ago

Hi, @neorevx This is a hardware issue, the clash interrupt will be triggered by mistake in half-duplex mode(rs485_conf.tx_rx_en=0). Can you accept the use of full-duplex((rs485_conf.tx_rx_en=1) mode to workaround this issue?

neorevx commented 5 years ago

For my implementation there will be few problems. Nothing severe. I will have to process the sent message because the sending is received. Or discard my actively sent message. But in order to implement correctly, please provide me with some information:

By the way, the GLITCH interrupt does not work as START BIT. I imagine there should be some confusion in the documentation (reference manual). I get GLITCH when there is noise in the signal. Is that correct? Or could it be the same problem with the half-duplex?

Thank you.

koobest commented 5 years ago

Hi, @neorevx When tx_rx_en is set, the hardware can not detect the collision(hardware issue). If you are not using UART2, please try the following method(I guess you just want to discard the data you received when sending data.).

  1. Set the register rxfifo_rst = 1 before you fill the txfifo.
  2. Enable tx_done interrupt after the data is filled in txfifo.
  3. Clear the register rxfifo_rst=0 when the tx_done interrupt arrives.

thanks !!

neorevx commented 5 years ago

Hi, @koobest, You told me that the interrupt does not work correctly in half-duplex (rs485_conf.tx_rx_en = 0). But now you told me that it also does not work in full-duplex (rs485_conf.tx_rx_en = 1).

When tx_rx_en is set

I think it meant "when tx_rx_en is not set". Or there is no way to detect collision via hardware. I will have to implement reading and comparing to the sent byte. In that case I would need a timeout if the collision generates an invalid frame and does not get back into the RX.

Look at this snippet of code. The comment says one thing and the code another. This is the code that initializes the UART to collision detection mode. https://github.com/espressif/esp-idf/blob/58df1d93bc17c74499d58e05390af9c309192a5c/components/driver/uart.c#L1503-L1504

I will try to use rxfifo_rst = 1. However, I read somewhere that this flag can cause problems in other FIFOs. Do you think there will be problems? So will there be normal collision detection? I need to detect collision when transmitting the first byte of a message. It is a key point of the J1708 protocol. So here's the question: if there is a collision in the first byte and there are other bytes in the FIFO, Is the rest discarded or will it be sent after the collision?

Thank you!!!

koobest commented 5 years ago

Hi,@neorevx

  1. Yes, the hardware fifo_reset has a bug. but this bug does not matter if UART1 and UART2 are not used at the same time.
  2. Data in fifo is not discarded when a collision occurs.

In your hardware, the data you send will be read back by the hardware. image

Currently, the collision detection mode works only in half-duplex mode (rs485_conf.tx_rx_en = 0), and the RX line needs to be held low while transmitting data. Otherwise, a collision interrupt will occur. The data you send will be read back by the hardware and the ESP32 thinks it is a conflict. I think this is not what you want. you can use full-duplex mode(rs485_conf.tx_rx_en = 1), determine whether conflicts occur by comparing the received and transmitted data, if they are the same, no conflicts occur, and if they are not the same, a conflict occurs.

thanks !!

koobest commented 5 years ago

Hi, @neorevx There are some new updates to this issue. A few days ago, I did this test in an incorrect way, so I got the wrong conclusion. I'm sorry for this. Now I can provide a test code to support this conclusion.

  1. Due to hardware problems, the collision detection function of half-duplex mode (rs485_conf.tx_rx_en = 0) does not work (in fact, it can be used to detect collisions, but the RX idle level is required to be low when transmitting data).

  2. The collision detection function of full-duplex mode (rs485_conf.tx_rx_en = 1) can work. If the data you send (on TX line) is different from the received data (on the RX line), a collision inerrupt will occur. Data will continue to be sent when a collision occurs.

You need two ESP32 board to do this test(you need to set rs485_conf.tx_rx_en=1).

  1. Burn the test code onto two boards.(need to modify the macro #if 0 / #if 1 to select the board).
  2. Connect GPIO13 of these two board. connect GPIO18 of board1 with GPIO19 of board0.

You will see the collision detection works.

//Test code

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/uart.h"
#include "freertos/queue.h"
#include "esp_log.h"

#define ECHO_TEST_TXD   (18)
#define ECHO_TEST_RXD   (19)

#define ECHO_TEST_RTS   (UART_PIN_NO_CHANGE)

#define ECHO_TEST_CTS  UART_PIN_NO_CHANGE

#define BUF_SIZE        (1024)
#define BAUD_RATE       (115200)

// Read packet timeout
#define PACKET_READ_TICS        (100 / portTICK_RATE_MS)
#define ECHO_TASK_STACK_SIZE    (2048)
#define ECHO_TASK_PRIO          (10)
#define ECHO_UART_PORT          (UART_NUM_1)

static const char *TAG = "RS485_ECHO_APP";

#include "soc/io_mux_reg.h"
#include "driver/gpio.h"

//make flash to board 1
#if 1

void test_task(void *param)
{
    uint8_t* data = (uint8_t*) malloc(BUF_SIZE);    
    for(int i = 0; i < 1024; i++) {
        data[i] = i + 1;
    }
    const int uart_num = ECHO_UART_PORT;
    uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS,
        .rx_flow_ctrl_thresh = 122,
    };
    // Configure UART parameters
    uart_param_config(uart_num, &uart_config);
    uart_set_pin(uart_num, 18, 19, -1, 13);
    uart_driver_install(uart_num, BUF_SIZE * 2, 0, 0, NULL, 0);

    while(1) {
        vTaskDelay(2000/portTICK_PERIOD_MS);
        uart_write_bytes(uart_num, (const char *)data, 1024);
        uart_wait_tx_done(uart_num, 1000/portTICK_PERIOD_MS);
        vTaskDelay(2000/portTICK_PERIOD_MS);
    }
}

//make flash to board 0
#else

void test_task(void *param)
{
    uint8_t* data = (uint8_t*) malloc(BUF_SIZE);    
    for(int i = 0; i < 1024; i++) {
        data[i] = 0xff;
    }
    const int uart_num = ECHO_UART_PORT;
    uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .rx_flow_ctrl_thresh = 122,
    };
    // Configure UART parameters
    uart_param_config(uart_num, &uart_config);
    uart_set_pin(uart_num, 18, 19, -1, -1);
    uart_driver_install(uart_num, BUF_SIZE * 2, 0, 0, NULL, 0);
    uart_set_mode(uart_num, UART_MODE_RS485_COLLISION_DETECT);
    gpio_set_level(13, 1);
    PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[13], PIN_FUNC_GPIO);
    gpio_set_pull_mode(13, GPIO_PULLUP_ONLY);
    gpio_set_direction(13, GPIO_MODE_OUTPUT);

    while(1) {
        printf("start\n");
        vTaskDelay(2000/portTICK_PERIOD_MS);
        gpio_set_level(13, 0);
        ets_delay_us(1000)
        uart_write_bytes(uart_num, (const char *)data, 1024);
        vTaskDelay(2000/portTICK_PERIOD_MS);
        gpio_set_level(13, 1);
    }
}

#endif

void app_main()
{
    test_task(NULL);
}

thanks!!

neorevx commented 5 years ago

Hello, I had already tested the full-duplex collision and it worked (with interruption). However, I need to remove the sent message. In most cases it is only necessary to read all data from the RX-FIFO in the TX-DONE interrupt. But in some cases, TX-DONE is sent before the byte is in the RX-FIFO. In that case, I believe we can look at the RX finite state machine and check if the byte is being read when TX-DONE happens. And if there is a byte, we discard the next byte of the RX-FIFO at the next reading. There is also a collision before the TX-DONE. In my case, I need to record that there was a collision and discard all bytes when TX-DONE happens.

In half-duplex, I've inverted the TX with external hardware to see if it impacts the collision, but the answer is: No. But I got some conclusions about inverting the logic level of the pins. There is no collision when I invert the RX (logical only). Obviously, there is also no correct data, since the RX is inverted. I tried to invert the RX only when sending data, but it generated false collisions as well. In some cases a 0x00 byte is also read by the transmitter, indicating that the hardware could not remove the byte sent, probably because the data is inverted ou RX delay.

It is interesting to note that jump RX with TX does not cause collision! But only with TX or RX inverted. When both are inverted or not, there is a collision. Apparently the hardware expects the inverted bits but with no logical level inverted! I could use another GPIO and external hardware to invert the bits of the RX during the transmission, but it's a lot of effort. The inversion could only happen during transmission, while reception can not be inverted. Better to remove the Full-Duplex bytes. There is also no guarantee that the collision will be detected when there is. I still do not know exactly the behavior of the hardware.

I also tried other things without success.

About your example, I believe you miss something. You can not test by directly connecting the pins of two boards. It is necessary that you have a transceiver. The transceiver will always send the TX value to the RX, with some microseconds or nanoseconds of delay. The ESP32 hardware that removes TX from the RX. By connecting the pins directly, you will not have the loopback, so you will not be able to find collisions in the transmitter.

I'll test some things with RTS. Currently I control sw_rts during transmission. But in J1708 there is no RTS line, so I can not use it. I think there no where to go with RTS.

I tried putting the RX Pull-Down during transmission. But it did not help.

I will implement using full-duplex. If I have other news, I'll let you know.

alisitsyn commented 2 years ago

The update for this issue to demonstrate described configuration of collision detection feature is added here .

alisitsyn commented 2 years ago

Hi,

Could anyone check the above mentioned code with collision detection using HAL layer and provide some feedback for this issue?

alisitsyn commented 9 months ago

Related issue: https://github.com/espressif/esp-idf/issues/7446