From 9d787c2362819c60ba09a49f73e60653599ef84f Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Sun, 24 Mar 2024 23:04:59 -0300 Subject: [PATCH 1/7] feat: onReceiveExample.ino Adds a new example that uses HardwareSerial::onReceive(). The example demosntrates how to read all the data sent to UART0, considering that the end of transmission is defined by a period of time with UART in idle state. --- .../onReceiveExample/onReceiveExample.ino | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino new file mode 100644 index 00000000000..5de4c00b845 --- /dev/null +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -0,0 +1,65 @@ +// this will make UART0 work in any case (using or not USB) +#if ARDUINO_USB_CDC_ON_BOOT +#define UART0 Serial0 +#else +#define UART0 Serial +#endif + +// global variable to keep the results from onReceive() +String uart_buffer = ""; +// a pause of a half second in the UART transmission is considered the end of transmission. +const uint32_t communicationTimeout_ms = 500; + +// Create a mutex for the access to uart_buffer +// only one task can read/write it at a certain time +SemaphoreHandle_t uart_buffer_Mutex = NULL; + +// UART_RX_IRQ will be executed as soon as data is received by the UART +// This is a callback function executed from a high priority +// task created when onReceive() is used +void UART0_RX_CB() { + // take the mutex, waits forever until loop() finishes its processing + if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { + uint32_t now = millis(); // tracks timeout + while ((millis() - now) < communicationTimeout_ms) { + if (UART0.available()) { + uart_buffer += (char) UART0.read(); + now = millis(); // reset the timer + } + } + // releases the mutex for data processing + xSemaphoreGive(uart_buffer_Mutex); + } +} + +// setup() and loop()are callback functions executed by a low priority task +// Therefore, there are 2 tasks running when using onReceive() +void setup() { + // creates a mutex object to control access to uart_buffer + uart_buffer_Mutex = xSemaphoreCreateMutex(); + + UART0.begin(115200); + UART0.onReceive(UART0_RX_CB); // sets the callback function + UART0.println("Send data to UART0 in order to activate the RX callback"); +} + +uint32_t counter = 0; +void loop() { + if (uart_buffer.length() > 0) { + // signals that the onReceive function shall not change uart_buffer while processing + if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { + // process the received data from UART0 - example, just print it beside a counter + UART0.print("["); + UART0.print(counter++); + UART0.print("] ["); + UART0.print(uart_buffer.length()); + UART0.print(" bytes] "); + UART0.println(uart_buffer); + uart_buffer = ""; // reset uart_buffer for the next UART reading + } + // releases the mutex for more data to be received + xSemaphoreGive(uart_buffer_Mutex); + } + UART0.println("Sleeping for 1 second..."); + delay(1000); +} From d5e098d7858d8225b01d3e77e329433021fcfc21 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Sun, 24 Mar 2024 23:10:39 -0300 Subject: [PATCH 2/7] fix: onReceiveExample.ino Fixes typos --- .../ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 5de4c00b845..9c059d8bcfb 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -32,7 +32,7 @@ void UART0_RX_CB() { } } -// setup() and loop()are callback functions executed by a low priority task +// setup() and loop() are functions executed by a low priority task // Therefore, there are 2 tasks running when using onReceive() void setup() { // creates a mutex object to control access to uart_buffer From 793b1fb783369bdd7f96456bafef352d4a3d48ee Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 25 Mar 2024 12:25:42 -0300 Subject: [PATCH 3/7] feat: add explanation header --- .../onReceiveExample/onReceiveExample.ino | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 9c059d8bcfb..06bcf3ae0eb 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -1,3 +1,54 @@ +/* + + This Sketch demonstrates how to use onReceive(callbackFunc) with HardwareSerial + + void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false) + + It is possible to register an UART callback function that will be called + every time that UART receives data and an associated interrupt is generated. + + In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted + using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout(). + + OnReceive will be called, while receiving a stream of data, when every 120 bytes are received (default FIFO Full), + which may not help in case that the application needs to get all data at once before processing it. + Therefore, a way to make it work is by detecting the end of a stream transmission. This can be based on a protocol + or based on timeout with the UART line in idle (no data received - this is the case of this example). + + In some cases, it is necessary to wait for receiving all the data before processing it and parsing the + UART input. This example demonstrates a way to create a String with all data received from UART0 and + signaling it using a Mutex for another task to process it. This example uses a timeout of 500ms as a way to + know when the reception of data has finished. + + The onReceive() callback is called whenever the RX ISR is triggered. + It can occur because of two possible events: + + 1- UART FIFO FULL: it happens when internal UART FIFO reaches a certain number of bytes. + Its full capacity is 127 bytes. The FIFO Full threshold for the interrupt can be changed + using HardwareSerial::setRxFIFOFull(uint8_t fifoFull). + Default FIFO Full Threshold is set in the UART initialization using HardwareSerial::begin() + This will depend on the baud rate used when begin() is executed. + For a baud rate of 115200 or lower, it it just 1 byte, mimicking original Arduino UART driver. + For a baud rate over 115200 it will be 120 bytes for higher performance. + Anyway, it can be changed by the application at any time. + + 2- UART RX Timeout: it happens, based on a timeout equivalent to a number of symbols at + the current baud rate. If the UART line is idle for this timeout, it will raise an interrupt. + This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout) + + When any of those two interrupts occur, IDF UART driver will copy FIFO data to its internal + RingBuffer and then Arduino can read such data. At the same time, Arduino Layer will execute + the callback function defined with HardwareSerial::onReceive(). + + parameter (default false) can be used by the application to tell Arduino to + only execute the callback when the second event above happens (Rx Timeout). At this time all + received data will be available to be read by the Arduino application. But if the number of + received bytes is higher than the FIFO space, it will generate an error of FIFO overflow. + In order to avoid such problem, the application shall set an appropriate RX buffer size using + HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port. +*/ + + // this will make UART0 work in any case (using or not USB) #if ARDUINO_USB_CDC_ON_BOOT #define UART0 Serial0 From 0c60c7365fed975799c842aa5ac10d1385c9f7ef Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 25 Mar 2024 12:27:39 -0300 Subject: [PATCH 4/7] fix: mutex release --- .../examples/Serial/onReceiveExample/onReceiveExample.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 06bcf3ae0eb..6ec0dec01da 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -107,9 +107,9 @@ void loop() { UART0.print(" bytes] "); UART0.println(uart_buffer); uart_buffer = ""; // reset uart_buffer for the next UART reading + // releases the mutex for more data to be received + xSemaphoreGive(uart_buffer_Mutex); } - // releases the mutex for more data to be received - xSemaphoreGive(uart_buffer_Mutex); } UART0.println("Sleeping for 1 second..."); delay(1000); From d2007e1545849142c0e1bbdfaeeac311a2336a93 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 25 Mar 2024 12:40:11 -0300 Subject: [PATCH 5/7] fix: add Mutex verification --- .../Serial/onReceiveExample/onReceiveExample.ino | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 6ec0dec01da..16400e2a880 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -70,7 +70,7 @@ SemaphoreHandle_t uart_buffer_Mutex = NULL; // task created when onReceive() is used void UART0_RX_CB() { // take the mutex, waits forever until loop() finishes its processing - if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { + if (uart_buffer_Mutex != NULL && xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { uint32_t now = millis(); // tracks timeout while ((millis() - now) < communicationTimeout_ms) { if (UART0.available()) { @@ -88,7 +88,10 @@ void UART0_RX_CB() { void setup() { // creates a mutex object to control access to uart_buffer uart_buffer_Mutex = xSemaphoreCreateMutex(); - + if(uart_buffer_Mutex == NULL) { + log_e("Error creating Mutex. Sketch will fail."); + delay(1000); + } UART0.begin(115200); UART0.onReceive(UART0_RX_CB); // sets the callback function UART0.println("Send data to UART0 in order to activate the RX callback"); @@ -98,7 +101,7 @@ uint32_t counter = 0; void loop() { if (uart_buffer.length() > 0) { // signals that the onReceive function shall not change uart_buffer while processing - if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { + if (uart_buffer_Mutex != NULL && xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { // process the received data from UART0 - example, just print it beside a counter UART0.print("["); UART0.print(counter++); From 941931225b3fb96d1a40a01f092d85a28887a444 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 25 Mar 2024 16:44:23 -0300 Subject: [PATCH 6/7] feat: Mutex error message --- .../Serial/onReceiveExample/onReceiveExample.ino | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 16400e2a880..75da9d98111 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -86,13 +86,18 @@ void UART0_RX_CB() { // setup() and loop() are functions executed by a low priority task // Therefore, there are 2 tasks running when using onReceive() void setup() { + UART0.begin(115200); + // creates a mutex object to control access to uart_buffer uart_buffer_Mutex = xSemaphoreCreateMutex(); if(uart_buffer_Mutex == NULL) { - log_e("Error creating Mutex. Sketch will fail."); - delay(1000); + log_e("Error creating Mutex. Sketch will fail."); + while(true) { + UART0.println("Mutex error (NULL). Program halted."); + delay(2000); + } } - UART0.begin(115200); + UART0.onReceive(UART0_RX_CB); // sets the callback function UART0.println("Send data to UART0 in order to activate the RX callback"); } @@ -113,7 +118,7 @@ void loop() { // releases the mutex for more data to be received xSemaphoreGive(uart_buffer_Mutex); } - } + } UART0.println("Sleeping for 1 second..."); delay(1000); } From 3fecfce626196558dd93cf83034754aea9d787e4 Mon Sep 17 00:00:00 2001 From: Rodrigo Garcia Date: Mon, 25 Mar 2024 16:53:23 -0300 Subject: [PATCH 7/7] feat: Mutex NULL testing --- .../examples/Serial/onReceiveExample/onReceiveExample.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 75da9d98111..5338de15e96 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -70,7 +70,7 @@ SemaphoreHandle_t uart_buffer_Mutex = NULL; // task created when onReceive() is used void UART0_RX_CB() { // take the mutex, waits forever until loop() finishes its processing - if (uart_buffer_Mutex != NULL && xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { + if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { uint32_t now = millis(); // tracks timeout while ((millis() - now) < communicationTimeout_ms) { if (UART0.available()) { @@ -106,7 +106,7 @@ uint32_t counter = 0; void loop() { if (uart_buffer.length() > 0) { // signals that the onReceive function shall not change uart_buffer while processing - if (uart_buffer_Mutex != NULL && xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { + if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { // process the received data from UART0 - example, just print it beside a counter UART0.print("["); UART0.print(counter++);