Skip to content

HardwareSerial not returning any read data if UART_BREAK_ERROR triggered #6849

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task done
AcuarioCat opened this issue Jun 9, 2022 · 10 comments
Closed
1 task done
Assignees
Labels
Area: Peripherals API Relates to peripheral's APIs. Peripheral: UART Type: For reference Common questions & problems
Milestone

Comments

@AcuarioCat
Copy link

AcuarioCat commented Jun 9, 2022

Board

ESP32 WROOM-32E

Device Description

Plain module on pcb

Hardware Configuration

GPIO21,22 connected to I2C

Version

v2.0.3

IDE Name

Visual Micro

Operating System

Windows 11

Flash frequency

80MHz

PSRAM enabled

no

Upload speed

921600

Description

I'm trying to read from an SDM120M modbus energy meter. The meter is returning data. The last byte is 00 but after the last bit the data line stays low causing a framing error. This triggers an RX break in the uartEventTask.

No data is then available (serialX.available() returns 0)
Finally after a few calls the UART FIFO fills up and the uart then returns 120 from serialX.available() and provides provides the 120 bytes (presumably as UART_FULL_THRESH_DEFAULT is triggered).

Sketch

Sketch is the sdm_live_page_esp32_hwserial.ino example from the SDM library :
https://github.com/reaper7/SDM_Energy_Meter

I added an onReceiveError callback to the SDM.cpp to trap the error.

void rxerr(int err) {
    Serial.printf("Receive error:%d\n", err);
}

Debug Message

[1232656][W][HardwareSerial.cpp:256] _uartEventTask(): UART1 RX break.
Receive error:0

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.
@AcuarioCat AcuarioCat added the Status: Awaiting triage Issue is waiting for triage label Jun 9, 2022
@SuGlider SuGlider self-assigned this Jun 9, 2022
@SuGlider
Copy link
Collaborator

Finally after a few calls the UART FIFO fills up and the uart then returns 120 from serialX.available() and provides provides the 120 bytes (presumably as UART_FULL_THRESH_DEFAULT is triggered).

I understand that the BREAK event doesn't prevent the sketch from getting the data.
Could you also provide more details about the sketch used to read the SDM120M device?

Have you tried to used a library such as https://github.com/reaper7/SDM_Energy_Meter

@VojtechBartoska VojtechBartoska added Area: Peripherals API Relates to peripheral's APIs. Resolution: Awaiting response Waiting for response of author and removed Status: Awaiting triage Issue is waiting for triage labels Jun 10, 2022
@AcuarioCat
Copy link
Author

AcuarioCat commented Jun 10, 2022

That's exactly the library and code I'm using. I have a Saleae logic probe connected and I see both the request going to the SDM and the reply coming back but, as I mentioned, the last byte causes a BREAK as the data line does not go high. The code (from SDM.cpp) then times out as the available() call returns 0.

SDM.cpp
resptime = millis();
while (sdmSer.available() < FRAMESIZE) {
if (millis() - resptime > msturnaround) {
readErr = SDM_ERR_TIMEOUT; //err debug (4)
break;
}
yield();
}

After several calls the available() returns 120 and the data is available to read.

@AcuarioCat
Copy link
Author

If it's of use - I just tried the same code but using SoftwareSerial instead of HardwareSerial, same GPIO pins (RX-26, TX-25) and it functions correctly. Change back to HardwareSerial and it fails.

@SuGlider
Copy link
Collaborator

Let me try to reproduce this issue with 2 ESP32 (one receiving data and the other one simulating the SDM120M with a break at the end of the packet). I'll post here the findings.

@SuGlider
Copy link
Collaborator

SuGlider commented Jun 14, 2022

Would it be possible for you to modify the SDM120M library to add this lines:

   // assumption that it is using Serial1...
   // forces available() to get data when there is no data in UART for more than 
   // 1 symbol (about 11 bits) in timeout at the current baudrate. Example 11 bits at 9600 is about 1 ms.
    Serial1.setRxTimeout(1);  

    // it needs #include "driver/uart.h"
    // this forces a time out even when FIFO FULL with exactly 120 bytes 
    uart_set_always_rx_timeout(1, true); // 1 is for Serial1, in this case

I think that it may help.

Other important information:
When UART gets a BREAK symbol, it basically first generates the BREAK event and then it generates a DATA event in a "second round".
Because of it, when there is BREAK, there are no data available to read.... this is an IDF issue (or "feature").
After a timeout of about 11 symbols, by default, it generates a timeout and then data are availble to HardwareSerial.
It may be a bit confusing... but this is how it works at this time.

There is fix to it that may solve the issue in the UART IDF driver that we use in the ESP32 Arduino Core 2.0.0+.

PR espressif/esp-idf#5959 based in the same issue you have reported espressif/esp-idf#4537

@AcuarioCat
Copy link
Author

Unfortunately it makes no difference.
I'll stick with SoftwareSerial for the moment as that works ok. When the fix is implemented I'll try again.
Thanks for your help.

@VojtechBartoska VojtechBartoska moved this to Under investigation in Arduino ESP32 Core Project Roadmap Jun 15, 2022
@VojtechBartoska VojtechBartoska added this to the 2.0.4 milestone Jun 15, 2022
@SuGlider SuGlider moved this from Under investigation to In Progress in Arduino ESP32 Core Project Roadmap Jun 28, 2022
@SuGlider
Copy link
Collaborator

SuGlider commented Jun 28, 2022

@AcuarioCat

I would like to propose a fix for Serial1.read() and Serial1.available() that I think would improve UART response time and ignore BRK IRQ in order to receive data Zero instead of a BREAK interrupt:

// Necessary include for testing the fix
#include "driver/uart.h"


void setup() {
  // for example, start Serial1 - UART1
  Serial1.begin(115200);

  // right after starting UART1, add this code:
  uart_intr_config_t uart_intr = {
      .intr_enable_mask = (0x1<<0) | (0x8<<0),  // UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT,
      .rx_timeout_thresh = 1,
      .txfifo_empty_intr_thresh = 10,
      .rxfifo_full_thresh = 112,
  };
  uart_intr_config((uart_port_t) 1, &uart_intr);  // One is the UART number for Arduino Serial
}

Could you please test it and let me know.
Thanks.

@AcuarioCat
Copy link
Author

Hi, I tried out the code but unfortunately still the same.

@VojtechBartoska VojtechBartoska removed this from the 2.0.4 milestone Jun 29, 2022
@VojtechBartoska VojtechBartoska added Status: Test needed Issue needs testing and removed Resolution: Awaiting response Waiting for response of author labels Aug 23, 2022
@SuGlider
Copy link
Collaborator

@AcuarioCat

We have done a few updates to UART in the new Arduino Core 2.0.5 version.
There are new functions that may help the application to deal with BREAK:

  • void onReceive(OnReceiveCb function, bool onlyOnTimeout = false);
    onReceive() will setup a callback that will be called whenever an UART interruption occurs (UART_INTR_RXFIFO_FULL or
    UART_INTR_RXFIFO_TOUT). This means that it is possible to set up a callback that will be executed as soon as data is
    received.
    UART_INTR_RXFIFO_FULL interrupt triggers at UART_FULL_THRESH_DEFAULT bytes received (defined as 120 bytes by
    default in IDF). This default value of 120 can be changed using setRxFIFOFull() with a value from 1 to 127.
    UART_INTR_RXFIFO_TOUT interrupt triggers at UART_TOUT_THRESH_DEFAULT symbols passed without any reception
    (defined as 2 symbols by default). This can also be changed with setRxTimeout()

    onlyOnTimeout parameter will define how onReceive will behave:
    true -- The callback will only be called when RX Timeout happens.
    Whole stream of bytes will be ready for being read on the callback function at once.
    This option may lead to Rx Overflow depending on the Rx Buffer Size and number of bytes received
    in the streaming
    Default: false -- The callback will be called when FIFO reaches RXFIFO_FULL bytes and also on RX Timeout.
    The stream of incommig bytes will be "split" into blocks of minimum RXFIFO_FULL bytes on each callback.
    This option avoids any sort of Rx Overflow, but leaves the UART packet reassembling work
    to the Application.

  • void onReceiveError(OnReceiveErrorCb function);
    onReceiveError() will be called on error events (see hardwareSerial_error_t) and it will also call the onReceive() callback in
    case some was defined. By this, it is possible to read data using onReceive() callback when a BREAK is sent to UART, for
    instance.

  • void setRxTimeout(uint8_t symbols_timeout);
    setRxTimeout() sets the timeout after which onReceive callback will be called (after receiving data, it waits for this time of UART rx inactivity to call the callback fnc)
    param symbols_timeout defines a timeout threshold in uart symbol periods.
    Setting 0 (zero) symbol timeout disables the callback call by timeout.
    Maximum timeout setting is calculacted automatically by IDF. If set above the maximum, it is ignored and an error is printed on Serial0 (check console).
    For example symbols_timeout=1 defines a timeout equal to transmission time of one symbol (~11 bit) on current baudrate.
    For a baudrate of 9600, SERIAL_8N1 (10 bit symbol) and symbols_timeout = 3, the timeout would be 3 / (9600 / 10) = 3.125 ms

  • void setRxFIFOFull(uint8_t fifoBytes);
    setRxFIFOFull() will set the number of bytes that will trigger UART_INTR_RXFIFO_FULL interrupt and fill up RxRingBuffer
    This affects some functions such as Serial::available() and Serial.read() because, in a UART flow of receiving data,
    Serial internal RxRingBuffer will be filled only after these number of bytes arrive or a RX Timeout happens.
    This parameter can be set to a low value, such as 1 in order to receive byte by byte, but it will also consume more
    CPU time as the ISR will be activates often.

@SuGlider
Copy link
Collaborator

SuGlider commented Nov 20, 2022

I have created an example for the BREAK event in Serial (at the end and the begining of a data stream)
Please take a look:

#7412

https://github.com/espressif/arduino-esp32/pull/7412/files#diff-6c04e4a8f029f43cdef2b4babfd07e68603fa5a7c9f7960f6515885f723b5faf

Repository owner moved this from In Progress to Done in Arduino ESP32 Core Project Roadmap Nov 20, 2022
@SuGlider SuGlider added Type: For reference Common questions & problems and removed Status: Test needed Issue needs testing labels Nov 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Peripherals API Relates to peripheral's APIs. Peripheral: UART Type: For reference Common questions & problems
Projects
Development

No branches or pull requests

3 participants