Skip to content

Improves UART reading performance #7525

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

Merged
merged 7 commits into from
Dec 13, 2022

Conversation

SuGlider
Copy link
Collaborator

@SuGlider SuGlider commented Nov 27, 2022

Description of Change

Improves UART reading more than 1 bytes, using HardwareSerial::read(uint8_t *buff, size_t len)

Tests scenarios

Tested with ESP32, ESP32S2, ESP32S3, ESP32C3 using this sketch and a Python script runing on a computer.
See #7505

General Testing Sketch:

//Testing by sending "0123456789abcdefghijklmnopqrstuvwxyz" using Serial Monitor

void setup() {
  Serial.begin(115200);
  Serial.println("\nSend anything to Serial... testing read() and peek()\n");
}

void loop() {
  if (Serial.available()) {
    Serial.printf("Serial has %d bytes available.\n", Serial.available());
    Serial.printf("Serial PEEK is %c\n", Serial.peek());
    int i = Serial.read();
    Serial.printf("Serial Read 1 byte after Peek: %d ['%c']\n", i, i);
    i = Serial.read();
    Serial.printf("Serial Read again 1 byte after First Read: %d ['%c']\n", i, i);
    Serial.println("Serial Read in blocks of 10 bytes:");
    i = Serial.peek();
    Serial.printf("Serial PEEK before trying to read a chunk of data is %d ['%c']\n", i, i);
    uint8_t buf[11];
    size_t len = 0;
    while (len = Serial.read(buf, 10)) {
    //while (Serial.available()) {
      buf[len] = '\0';
      Serial.println((char*)buf);
      i = Serial.peek();
      Serial.printf("Serial PEEK is %d ['%c']\n", i, i);
    }
    Serial.println("===============================================");
  }
}

Expected OUPUT when sending 0123456789abcdefghijklmnopqrstuvwxyz

Serial has 36 bytes available.
Serial PEEK is 0
Serial Read 1 byte after Peek: 48 ['0']
Serial Read again 1 byte after First Read: 49 ['1']
Serial Read in blocks of 10 bytes:
Serial PEEK before trying to read a chunk of data is 50 ['2']
23456789ab
Serial PEEK is 99 ['c']
cdefghijkl
Serial PEEK is 109 ['m']
mnopqrstuv
Serial PEEK is 119 ['w']
wxyz
Serial PEEK is -1 ['⸮']
===============================================

Related links

Fixes #7505
Fixes #7474

May Fix #6644
May Fix #6727

@SuGlider SuGlider added Area: Performance Issue related to performance problems and improvements Area: Peripherals API Relates to peripheral's APIs. Peripheral: UART labels Nov 27, 2022
@SuGlider SuGlider added this to the 2.0.6 milestone Nov 27, 2022
@SuGlider SuGlider self-assigned this Nov 27, 2022
Copy link
Collaborator

@mrengineer7777 mrengineer7777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In esp32-hal-uart.c->uartRead(), int len = uart_read_bytes(uart->num, &c, 1, 20 / portTICK_RATE_MS); can return negative.

Copy link
Collaborator

@mrengineer7777 mrengineer7777 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR drops call to available(). Likely OK, but need to test both HardwareSerial::read(void) and HardwareSerial::read(uint8_t *buffer, size_t size) when no data available to see if there are any side effects with calling IDF function uart_read_bytes() with 0 timeout.

PR looks promising. I see no other issues.

@SuGlider
Copy link
Collaborator Author

PR drops call to available(). Likely OK, but need to test both HardwareSerial::read(void) and HardwareSerial::read(uint8_t *buffer, size_t size) when no data available to see if there are any side effects with calling IDF function uart_read_bytes() with 0 timeout.

PR looks promising. I see no other issues.

I tested it using the example sketch of this PR.
I sent just "0", "01", "012" and "0123".
All of them try to read or peek from empty RX buffer at different moments.
Everything works as expected.

@SuGlider
Copy link
Collaborator Author

PR drops call to available(). Likely OK, but need to test both HardwareSerial::read(void) and HardwareSerial::read(uint8_t *buffer, size_t size) when no data available to see if there are any side effects with calling IDF function uart_read_bytes() with 0 timeout.

Sketch Example has been updated to test return of read(buffer) when there is no data to be read.

@P-R-O-C-H-Y
Copy link
Member

@SuGlider I am getting this output from the sketch on ESP32-S2 (did not test other chips yet).

Serial has 36 bytes available.
Serial PEEK is 0
Serial Read 1 byte after Peek: 48 ['0']
Serial Read again 1 byte after First Read: 49 ['1']
Serial Read in blocks of 10 bytes:
Serial PEEK before trying to read a chunk of data is 50 ['2']
Serial PEEK is 99 ['c']
23456789ab
Serial PEEK is 109 ['m']
cdefghijkl
Serial PEEK is 119 ['w']
mnopqrstuv
Serial PEEK is -1 ['⸮']
wxyz
===============================================

Is that right? If yes maybe update the expected OUTPUT :)

@P-R-O-C-H-Y
Copy link
Member

Also did a quick test of the performance of the reading as the sketch is written (only Serial.prints removed).

Before changes reading 36 bytes took 316-325 us.
After changes reading 36 bytes took 118-126 us.

Seems a really nice speed improvement. Great PR @SuGlider

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 4, 2022

New commit e072302 overrides Stream::readBytes() to make it faster with IDF.

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 4, 2022

@SuGlider I am getting this output from the sketch on ESP32-S2 (did not test other chips yet).

Serial has 36 bytes available.
Serial PEEK is 0
Serial Read 1 byte after Peek: 48 ['0']
Serial Read again 1 byte after First Read: 49 ['1']
Serial Read in blocks of 10 bytes:
Serial PEEK before trying to read a chunk of data is 50 ['2']
Serial PEEK is 99 ['c']
23456789ab
Serial PEEK is 109 ['m']
cdefghijkl
Serial PEEK is 119 ['w']
mnopqrstuv
Serial PEEK is -1 ['⸮']
wxyz
===============================================

Is that right? If yes maybe update the expected OUTPUT :)

Thanks. Example and Output are now updated.

@mrengineer7777
Copy link
Collaborator

@SuGlider See my review comments above, especially on readBytes().

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 6, 2022

@SuGlider See my review comments above, especially on readBytes().

This one #7525 (review)?

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 6, 2022

In esp32-hal-uart.c->uartRead(), int len = uart_read_bytes(uart->num, &c, 1, 20 / portTICK_RATE_MS); can return negative.

The code already treats negative return:

if (size > 0) {
       int len = uart_read_bytes(uart->num, buffer, size, pdMS_TO_TICKS(timeout_ms));
       if (len < 0) len = 0;  // error reading UART   <<<=======================Here
       bytes_read += len;
    }

@mrengineer7777
Copy link
Collaborator

mrengineer7777 commented Dec 6, 2022

@SuGlider Apologies, Github is being weird about my review notes and is showing review as pending (but not posted?). I'll cancel the review, look over the current code and just comment here.

@mrengineer7777
Copy link
Collaborator

mrengineer7777 commented Dec 6, 2022

On cores/esp32/HardwareSerial.h readBytes(char *buffer, size_t length), should this be uint8_t instead of char? recommend: return readBytes((uint8_t *)buffer, length);

On cores/esp32/esp32-hal-uart.c uint8_t uartRead(uart_t* uart) (not currently modified by PR) uart_read_bytes() can return negative. Recommend changing if (len == 0) { to if(len <= 0) {. uartPeek() may have the same problem.

On cores/esp32/esp32-hal-uart.c uint8_t uartRead(uart_t* uart) I recommend the following comments on the function:

//Reads one byte from uart.
//Check uartAvailable() > 0 before calling uartRead().

Also consider marking uartRead() as //Deprecated. Use uartReadBytes() instead.

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 6, 2022

On cores/esp32/HardwareSerial.h readBytes(char *buffer, size_t length), should this be uint8_t instead of char? recommend: return readBytes((uint8_t *)buffer, length);

On cores/esp32/esp32-hal-uart.c uint8_t uartRead(uart_t* uart) (not currently modified by PR) uart_read_bytes() can return negative. Recommend changing if (len == 0) { to if(len <= 0) {. uartPeek() may have the same problem.

On cores/esp32/esp32-hal-uart.c uint8_t uartRead(uart_t* uart) I recommend the following comments on the function:

//Reads one byte from uart.
//Check uartAvailable() > 0 before calling uartRead().

Also consider marking uartRead() as //Deprecated. Use uartReadBytes() instead.

It is a bit confusing to review code this way. Do you mind using the traditional way as below:

When reviewing it within GitHub, you can use 2 buttons:

image

The Red Arrow will insert the review immediately.
The Yellow Arrow, adds the review, but it will be published only when all the review is submitted, by pressing the Yellow marked button below (in the top of the GH web page):

image

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 6, 2022

Thanks @mrengineer7777 for the review.

On cores/esp32/HardwareSerial.h readBytes(char *buffer, size_t length), should this be uint8_t instead of char? recommend: return readBytes((uint8_t *)buffer, length);

Yes, correct. Fixed now.

On cores/esp32/esp32-hal-uart.c uint8_t uartRead(uart_t* uart) (not currently modified by PR) uart_read_bytes() can return negative. Recommend changing if (len == 0) { to if(len <= 0) {. uartPeek() may have the same problem.

Yes, noted. Both functions are now fixed.

On cores/esp32/esp32-hal-uart.c uint8_t uartRead(uart_t* uart) I recommend the following comments on the function:

//Reads one byte from uart.
//Check uartAvailable() > 0 before calling uartRead().

Also consider marking uartRead() as //Deprecated. Use uartReadBytes() instead.

Actually the functions exposed in esp32-hal-uart.c shall not be used by the application.
This is a middleware to connect IDF to Arduino API.

@mrengineer7777
Copy link
Collaborator

@SuGlider Apologies for not replying sooner. Hectic day at work yesterday. Your explanation on Github review buttons is very helpful.

Just read through all the code changes again. With your latest commits PR looks good to me.

@SuGlider
Copy link
Collaborator Author

SuGlider commented Dec 7, 2022

@SuGlider Apologies for not replying sooner. Hectic day at work yesterday. Your explanation on Github review buttons is very helpful.

Just read through all the code changes again. With your latest commits PR looks good to me.

@mrengineer7777 - Thank you for the code review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: Performance Issue related to performance problems and improvements Area: Peripherals API Relates to peripheral's APIs. Peripheral: UART
Projects
5 participants