From 5cac99b22edc8d922b145e025235cb727c277b20 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sun, 27 Apr 2025 23:29:52 -0300 Subject: [PATCH 1/5] feat(uart): simplifies UART example based on MODBUS standard --- .../onReceiveExample/onReceiveExample.ino | 120 ++++++++---------- 1 file changed, 55 insertions(+), 65 deletions(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index fe66b07b875..119925fea2d 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -5,20 +5,20 @@ 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. + every time that UART receives data and an associated UART interrupt is generated. - In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted - using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout(). + 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 case that is not changed or it is set to , the callback function is + executed whenever any event happens first (FIFO Full or RX Timeout). + OnReceive will be called when every 120 bytes are received(default FIFO Full), + or when RX Timeout occurs after 1 UART symbol by default. - 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. + This example demonstrates a way to create a String with all data received from UART0 only + after RX Timeout. This example uses an RX timeout of about 3.5 Symbols as a way to know + when the reception of data has finished. + In order to achieve it, the sketch sets to . The onReceive() callback is called whenever the RX ISR is triggered. It can occur because of two possible events: @@ -34,18 +34,23 @@ 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) + This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout). + is bound to the clock source. + In order to use it properly, ESP32 and ESP32-S2 shall set the UART Clock Source to APB. 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 + parameter can be used by the application to tell Arduino to only execute + the callback when Rx Timeout happens, by setting it to . + At this time all received data will be available to be read by the Arduino application. + The application shall set an appropriate RX buffer size using HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port. + + MODBUS timeout of 3.5 symbol is based on these documents: + https://www.automation.com/en-us/articles/2012-1/introduction-to-modbus + https://minimalmodbus.readthedocs.io/en/stable/serialcommunication.html */ // this will make UART0 work in any case (using or not USB) @@ -57,67 +62,52 @@ // 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 +// The Modbus RTU standard prescribes a silent period corresponding to 3.5 characters between each +// message, to be able fo figure out where one message ends and the next one starts. +const uint32_t modbusRxTimeoutLimit = 4; +const uint32_t baudrate = 19200; + +// UART_RX_IRQ will be executed as soon as data is received by the UART and an RX Timeout occurs +// This is a callback function executed from a high priority monitor task +// All data will be buffered into RX Buffer, which may have its size set to whatever necessary 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); + while (UART0.available()) { + uart_buffer += (char)UART0.read(); } } // 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."); - while (true) { - UART0.println("Mutex error (NULL). Program halted."); - delay(2000); - } - } +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 + // UART_CLK_SRC_APB will allow higher values of RX Timeout + // default for ESP32 and ESP32-S2 is REF_TICK which limits the RX Timeout to 1 + // setClockSource() must be called before begin() + UART0.setClockSource(UART_CLK_SRC_APB); +#endif + // the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes + UART0.setRxBufferSize(1024); // default is 256 bytes + UART0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) + // set RX Timeout based on UART symbols ~ 3.5 symbols of 11 bits (MODBUS standard) ~= 2 ms at 19200 + UART0.setRxTimeout(modbusRxTimeoutLimit); // 4 symbols at 19200 8N1 is about 2.08 ms (40 bits) + // sets the callback function that will be executed only after RX Timeout + UART0.onReceive(UART0_RX_CB, true); - 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() { + // String is filled by the UART Callback whenever data is received and RX Timeout occurs 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); - } + // 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 } - UART0.println("Sleeping for 1 second..."); - delay(1000); + delay(1); } From 252bf6fcf8dc7015aac14c8435b4eb7080c3a502 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Sun, 27 Apr 2025 23:42:16 -0300 Subject: [PATCH 2/5] fix(uart): fixes a uart example typo --- .../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 119925fea2d..58a3ffabe9d 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -85,7 +85,7 @@ void setup() { // setClockSource() must be called before begin() UART0.setClockSource(UART_CLK_SRC_APB); #endif - // the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes + // the amount of data received or waiting to be processed shall not exceed this limit (1024 bytes) UART0.setRxBufferSize(1024); // default is 256 bytes UART0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) // set RX Timeout based on UART symbols ~ 3.5 symbols of 11 bits (MODBUS standard) ~= 2 ms at 19200 From 47a9de319a2e68363227503c074d4babb2e9e9f6 Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Mon, 28 Apr 2025 13:40:14 -0300 Subject: [PATCH 3/5] feat(uart): replaces UART0 by Serial0 in the code --- .../onReceiveExample/onReceiveExample.ino | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino index 58a3ffabe9d..3ebfd48beee 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -53,13 +53,6 @@ https://minimalmodbus.readthedocs.io/en/stable/serialcommunication.html */ -// 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 = ""; // The Modbus RTU standard prescribes a silent period corresponding to 3.5 characters between each @@ -71,42 +64,42 @@ const uint32_t baudrate = 19200; // This is a callback function executed from a high priority monitor task // All data will be buffered into RX Buffer, which may have its size set to whatever necessary void UART0_RX_CB() { - while (UART0.available()) { - uart_buffer += (char)UART0.read(); + while (Serial0.available()) { + uart_buffer += (char)Serial0.read(); } } // setup() and loop() are functions executed by a low priority task // Therefore, there are 2 tasks running when using onReceive() void setup() { + // Using Serial0 will work in any case (using or not USB CDC on Boot) #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 // UART_CLK_SRC_APB will allow higher values of RX Timeout // default for ESP32 and ESP32-S2 is REF_TICK which limits the RX Timeout to 1 // setClockSource() must be called before begin() - UART0.setClockSource(UART_CLK_SRC_APB); + Serial0.setClockSource(UART_CLK_SRC_APB); #endif - // the amount of data received or waiting to be processed shall not exceed this limit (1024 bytes) - UART0.setRxBufferSize(1024); // default is 256 bytes - UART0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) + // the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes + Serial0.setRxBufferSize(1024); // default is 256 bytes + Serial0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) // set RX Timeout based on UART symbols ~ 3.5 symbols of 11 bits (MODBUS standard) ~= 2 ms at 19200 - UART0.setRxTimeout(modbusRxTimeoutLimit); // 4 symbols at 19200 8N1 is about 2.08 ms (40 bits) + Serial0.setRxTimeout(modbusRxTimeoutLimit); // 4 symbols at 19200 8N1 is about 2.08 ms (40 bits) // sets the callback function that will be executed only after RX Timeout - UART0.onReceive(UART0_RX_CB, true); - - UART0.println("Send data to UART0 in order to activate the RX callback"); + Serial0.onReceive(UART0_RX_CB, true); + Serial0.println("Send data using Serial Monitor in order to activate the RX callback"); } uint32_t counter = 0; void loop() { // String is filled by the UART Callback whenever data is received and RX Timeout occurs if (uart_buffer.length() > 0) { - // 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); + // process the received data from Serial - example, just print it beside a counter + Serial0.print("["); + Serial0.print(counter++); + Serial0.print("] ["); + Serial0.print(uart_buffer.length()); + Serial0.print(" bytes] "); + Serial0.println(uart_buffer); uart_buffer = ""; // reset uart_buffer for the next UART reading } delay(1); From 81a39063893ff0ea211c4e4f0bf35daf6b0e6354 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:36:39 +0000 Subject: [PATCH 4/5] ci(pre-commit): Apply automatic fixes --- .../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 3ebfd48beee..7752fa09015 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -80,8 +80,8 @@ void setup() { Serial0.setClockSource(UART_CLK_SRC_APB); #endif // the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes - Serial0.setRxBufferSize(1024); // default is 256 bytes - Serial0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) + Serial0.setRxBufferSize(1024); // default is 256 bytes + Serial0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) // set RX Timeout based on UART symbols ~ 3.5 symbols of 11 bits (MODBUS standard) ~= 2 ms at 19200 Serial0.setRxTimeout(modbusRxTimeoutLimit); // 4 symbols at 19200 8N1 is about 2.08 ms (40 bits) // sets the callback function that will be executed only after RX Timeout From eccec05fd60dff9640be396317007f5927fbb5da Mon Sep 17 00:00:00 2001 From: Sugar Glider Date: Mon, 28 Apr 2025 15:00:26 -0300 Subject: [PATCH 5/5] fix(uart): typo error message in commentary --- .../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 7752fa09015..17d800b3b39 100644 --- a/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino +++ b/libraries/ESP32/examples/Serial/onReceiveExample/onReceiveExample.ino @@ -56,7 +56,7 @@ // global variable to keep the results from onReceive() String uart_buffer = ""; // The Modbus RTU standard prescribes a silent period corresponding to 3.5 characters between each -// message, to be able fo figure out where one message ends and the next one starts. +// message, to be able to figure out where one message ends and the next one starts. const uint32_t modbusRxTimeoutLimit = 4; const uint32_t baudrate = 19200;