esp-idf: How to read UART bytes in queue after break? (IDFGH-2424)

I’m currently working on a DMX512 library for the ESP32 https://github.com/luksal/ESP32-DMX-RX . DMX512 is a protocol based on RS485 where after a break signal 512 bytes are transmitted.

The main concept is running an infinite loop and call xQueueReceive periodically and then checking the event.type. When a UART_DATA is received, I’m copying the received data to an array and waiting for the next event. That way I’m getting 120 bytes each event, in total 480 until the break occurs.

How can I receive the last 32 bytes? Because when event.type is UART_BREAK, then calling uart_get_buffered_data_len equals 0 and trying to use uart_read_bytes fails at this point.

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 1
  • Comments: 57 (27 by maintainers)

Most upvoted comments

There is the other way of handling DMX512 packet using UART. I propose to use MARK before BREAK - MBB time (2Sec > MBB > 0) to detect end of packet without additional changes in UART driver. In most of DMX512 controllers this time is not zero and it allows to use existing hardware UART TOUT feature to detect the end of DMX512 packet. The start of DMX condition is still BREAK. The simplified receiver code would be like :

static void uart_event_task(void *pvParameters)
{
    uart_event_t event;
    size_t buffered_size;
    uint8_t* dtmp = (uint8_t*) malloc(RD_BUF_SIZE);
    for(;;) {
        //Waiting for UART event.
        if(xQueueReceive(uart0_queue, (void * )&event, (portTickType)portMAX_DELAY)) {
            bzero(dtmp, RD_BUF_SIZE);
            ESP_LOGI(TAG, "uart[%d] event:", EX_UART_NUM);
            switch(event.type) {
                //Event of UART receving data
                /*We'd better handler data event fast, there would be much more data events than
                other types of events. If we take too much time on data event, the queue might
                be full.*/
                case UART_DATA:
                    // The approach uses the UART TOUT feature which triggers an event and gets the received buffer 
                    // if no any transmission during configured period of time (1 symbol as example).
                    // This time corresponds to the MARK before BREAK - MBB time (2Sec > MBB > 0) of DMX protocol.
                    // The approach assumes that MBB not less than 1 symbol transmission time on current baud rate.
                    // Usually most of DMX512 controllers satisfy to this requirement.
                    if (event.timeout_flag && break_detected) {
                        ESP_LOGI(TAG, "[DATA TOUT EVENT]: Add to ring buffer = %d bytes.", event.size);
                        uart_get_buffered_data_len(EX_UART_NUM, &event.size);
                        uart_read_bytes(EX_UART_NUM, dtmp, event.size, portMAX_DELAY);
                        memmove(&dmx_channels[0], dtmp, event.size);
                        print_dmx_data(dtmp, event.size);
                        break_detected = false;
                        // Send data back to receiver to check correctness (not part of DMX512 proto)
                        uart_write_bytes(EX_UART_NUM, (const char*) dtmp, event.size);
                    } else {
                        ESP_LOGI(TAG, "[UART DATA]: Add to ring buffer = %d bytes.", event.size);
                    }
                    break;
                //Event of HW FIFO overflow detected
                case UART_FIFO_OVF:
                    ESP_LOGI(TAG, "hw fifo overflow");
                    // If fifo overflow happened, you should consider adding flow control for your application.
                    // The ISR has already reset the rx FIFO,
                    // As an example, we directly flush the rx buffer here in order to read more data.
                    uart_flush_input(EX_UART_NUM);
                    xQueueReset(uart0_queue);
                    break;
                //Event of UART ring buffer full
                case UART_BUFFER_FULL:
                    ESP_LOGI(TAG, "ring buffer full");
                    // If buffer full happened, you should consider encreasing your buffer size
                    // As an example, we directly flush the rx buffer here in order to read more data.
                    uart_flush_input(EX_UART_NUM);
                    xQueueReset(uart0_queue);
                    break;
                //Event of UART RX break detected
                case UART_BREAK:
                    if (!break_detected) {
                        break_detected = true;
                        //uart_flush_input(EX_UART_NUM); // allows to remove first zero byte in the received packet
                    }
                    ESP_LOGI(TAG, "uart rx break");
                    break;
                //Event of UART parity check error
                case UART_PARITY_ERR:
                    ESP_LOGI(TAG, "uart parity error");
                    break;
                //Event of UART frame error
                case UART_FRAME_ERR:
                    ESP_LOGI(TAG, "uart frame error");
                    break;
                //UART_PATTERN_DET
                case UART_PATTERN_DET:
                    break;
                //Others
                default:
                    ESP_LOGI(TAG, "uart event type: %d", event.type);
                    break;
            }
        }
    }
    free(dtmp);
    dtmp = NULL;
    vTaskDelete(NULL);
}

void app_main(void)
{
    esp_log_level_set(TAG, ESP_LOG_INFO);

    /* Configure parameters of an UART driver,
     * communication pins and install the driver */
    uart_config_t uart_config = {
        .baud_rate = 250000,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
    };
    //Install UART driver, and get the queue.
    uart_driver_install(EX_UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 20, &uart0_queue, 0);
    uart_param_config(EX_UART_NUM, &uart_config);

    //Set UART log level
    esp_log_level_set(TAG, ESP_LOG_INFO);
    //Set UART pins (using UART0 default pins ie no changes.)
    uart_set_pin(EX_UART_NUM, DMX_TEST_TXD, DMX_TEST_RXD, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    // We are using the UART TIMEOUT feature to detect and of DMX message
    uart_set_rx_timeout(EX_UART_NUM, DMX_NO_SYM_TIME); //  set MBB = 1 symbol time on current baudrate
    uart_set_always_rx_timeout(EX_UART_NUM, true); 
    break_detected = false;
    
    //Create a task to handler UART event from ISR
    xTaskCreate(uart_event_task, "uart_event_task", 2048, NULL, 12, NULL);
}

The log: the log

I tested my dmx512 receiver and transmitter over RS485 and this approach works just fine. Please let me know if you have any comments.

I added code to watch the even.size that is received when the event is triggered. It always returns event.size = 120.

So here’s how I think the low level driver works (Note I am now using pre release code. The new code has added a hal level and the mod mentioned above is not being used.):

  1. Break is detected.
  2. uart begins to wait for 120 characters (120 is set by the driver code)
  3. uart receives 120 characters, transfers them to the buffer, and triggers an event and event.size is set to 120. Low level FIFO proceeds to receive more bytes.
  4. upstream code receives the event and reads 120 bytes, buffer is cleared.
  5. go to step 2 three more times to receive 120 x 4 bytes.
  6. uart receives 32 bytes, does nothing because 120 threshold has not been reached.
  7. uart detects break
  8. a break detect and data event is triggered (event.size = 120)
  9. return to step 1

What if you change this https://github.com/espressif/esp-idf/blob/647cb628a11a9ebd6952276fb7e6f354d4e3171d/components/driver/uart.c#L799-L802

To

else if ( (uart_intr_status & (UART_INTR_RXFIFO_TOUT | UART_INTR_RXFIFO_FULL | 
                             UART_INTR_CMD_CHAR_DET ) )
              || ( (uart_intr_status & UART_INTR_BRK_DET) 
                      && (uart_ll_get_rxfifo_len(uart_context[uart_num].hal.dev) > 0) ) ){

You should then get data event and break event