diff --git a/cores/esp32/HardwareSerial.cpp b/cores/esp32/HardwareSerial.cpp index 7b8ee993581..e60588f931e 100644 --- a/cores/esp32/HardwareSerial.cpp +++ b/cores/esp32/HardwareSerial.cpp @@ -139,8 +139,9 @@ _rxBufferSize(256), _txBufferSize(0), _onReceiveCB(NULL), _onReceiveErrorCB(NULL), -_onReceiveTimeout(true), +_onReceiveTimeout(false), _rxTimeout(2), +_rxFIFOFull(0), _eventTask(NULL) #if !CONFIG_DISABLE_HAL_LOCKS ,_lock(NULL) @@ -206,12 +207,23 @@ void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout) HSERIAL_MUTEX_LOCK(); // function may be NULL to cancel onReceive() from its respective task _onReceiveCB = function; - // When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes - _onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false; - // this can be called after Serial.begin(), therefore it shall create the event task - if (function != NULL && _uart != NULL && _eventTask == NULL) { - _createEventTask(this); // Create event task + // setting the callback to NULL will just disable it + if (_onReceiveCB != NULL) { + // When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes + _onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false; + + // in case that onReceive() shall work only with RX Timeout, FIFO shall be high + // this is a work around for an IDF issue with events and low FIFO Full value (< 3) + if (_onReceiveTimeout) { + uartSetRxFIFOFull(_uart, 120); + log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes."); + } + + // this method can be called after Serial.begin(), therefore it shall create the event task + if (_uart != NULL && _eventTask == NULL) { + _createEventTask(this); // Create event task + } } HSERIAL_MUTEX_UNLOCK(); } @@ -224,7 +236,14 @@ void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout) void HardwareSerial::setRxFIFOFull(uint8_t fifoBytes) { HSERIAL_MUTEX_LOCK(); + // in case that onReceive() shall work only with RX Timeout, FIFO shall be high + // this is a work around for an IDF issue with events and low FIFO Full value (< 3) + if (_onReceiveCB != NULL && _onReceiveTimeout) { + fifoBytes = 120; + log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes."); + } uartSetRxFIFOFull(_uart, fifoBytes); // Set new timeout + if (fifoBytes > 0 && fifoBytes < SOC_UART_FIFO_LEN - 1) _rxFIFOFull = fifoBytes; HSERIAL_MUTEX_UNLOCK(); } @@ -299,7 +318,6 @@ void HardwareSerial::_uartEventTask(void *args) } if (currentErr != UART_NO_ERROR) { if(uart->_onReceiveErrorCB) uart->_onReceiveErrorCB(currentErr); - if(uart->_onReceiveCB && uart->available() > 0) uart->_onReceiveCB(); // forces User Callback too } } } @@ -388,8 +406,24 @@ void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, in // Set UART RX timeout uartSetRxTimeout(_uart, _rxTimeout); + + // Set UART FIFO Full depending on the baud rate. + // Lower baud rates will force to emulate byte-by-byte reading + // Higher baud rates will keep IDF default of 120 bytes for FIFO FULL Interrupt + // It can also be changed by the application at any time + if (!_rxFIFOFull) { // it has not being changed before calling begin() + // set a default FIFO Full value for the IDF driver + uint8_t fifoFull = 1; + if (baud > 57600 || (_onReceiveCB != NULL && _onReceiveTimeout)) { + fifoFull = 120; + } + uartSetRxFIFOFull(_uart, fifoFull); + _rxFIFOFull = fifoFull; + } + _rxPin = rxPin; _txPin = txPin; + HSERIAL_MUTEX_UNLOCK(); } @@ -408,8 +442,12 @@ void HardwareSerial::end(bool fullyTerminate) if (uartGetDebug() == _uart_nr) { uartSetDebug(0); } + + _rxFIFOFull = 0; + uartDetachPins(_uart, _rxPin, _txPin, _ctsPin, _rtsPin); _rxPin = _txPin = _ctsPin = _rtsPin = -1; + } delay(10); uartEnd(_uart); diff --git a/cores/esp32/HardwareSerial.h b/cores/esp32/HardwareSerial.h index d5f9c7c1a7b..6291d241778 100644 --- a/cores/esp32/HardwareSerial.h +++ b/cores/esp32/HardwareSerial.h @@ -177,7 +177,7 @@ class HardwareSerial: public Stream OnReceiveErrorCb _onReceiveErrorCB; // _onReceive and _rxTimeout have be consistent when timeout is disabled bool _onReceiveTimeout; - uint8_t _rxTimeout; + uint8_t _rxTimeout, _rxFIFOFull; TaskHandle_t _eventTask; #if !CONFIG_DISABLE_HAL_LOCKS SemaphoreHandle_t _lock; diff --git a/cores/esp32/esp32-hal-uart.c b/cores/esp32/esp32-hal-uart.c index 0dbe9e52d76..9d79d1d0772 100644 --- a/cores/esp32/esp32-hal-uart.c +++ b/cores/esp32/esp32-hal-uart.c @@ -22,7 +22,9 @@ #include "hal/uart_ll.h" #include "soc/soc_caps.h" #include "soc/uart_struct.h" +#include "soc/uart_periph.h" +#include "driver/gpio.h" #include "hal/gpio_hal.h" #include "esp_rom_gpio.h" @@ -743,3 +745,54 @@ uartDetectBaudrate(uart_t *uart) return 0; #endif } + +/* + These functions are for testing puspose only and can be used in Arduino Sketches + Those are used in the UART examples +*/ + +/* + This is intended to make an internal loopback connection using IOMUX + The function uart_internal_loopback() shall be used right after Arduino Serial.begin(...) + This code "replaces" the physical wiring for connecting TX <--> RX in a loopback +*/ + +// gets the right TX SIGNAL, based on the UART number +#if SOC_UART_NUM > 2 +#define UART_TX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0TXD_OUT_IDX : (uartNumber == UART_NUM_1 ? U1TXD_OUT_IDX : U2TXD_OUT_IDX)) +#else +#define UART_TX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0TXD_OUT_IDX : U1TXD_OUT_IDX) +#endif +/* + Make sure UART's RX signal is connected to TX pin + This creates a loop that lets us receive anything we send on the UART +*/ +void uart_internal_loopback(uint8_t uartNum, int8_t rxPin) +{ + if (uartNum > SOC_UART_NUM - 1 || !GPIO_IS_VALID_GPIO(rxPin)) return; + esp_rom_gpio_connect_out_signal(rxPin, UART_TX_SIGNAL(uartNum), false, false); +} + +/* + This is intended to generate BREAK in an UART line +*/ + +// Forces a BREAK in the line based on SERIAL_8N1 configuration at any baud rate +void uart_send_break(uint8_t uartNum) +{ + uint32_t currentBaudrate = 0; + uart_get_baudrate(uartNum, ¤tBaudrate); + // calculates 10 bits of breaks in microseconds for baudrates up to 500mbps + // This is very sensetive timing... it works fine for SERIAL_8N1 + uint32_t breakTime = (uint32_t) (10.0 * (1000000.0 / currentBaudrate)); + uart_set_line_inverse(uartNum, UART_SIGNAL_TXD_INV); + ets_delay_us(breakTime); + uart_set_line_inverse(uartNum, UART_SIGNAL_INV_DISABLE); +} + +// Sends a buffer and at the end of the stream, it generates BREAK in the line +int uart_send_msg_with_break(uint8_t uartNum, uint8_t *msg, size_t msgSize) +{ + // 12 bits long BREAK for 8N1 + return uart_write_bytes_with_break(uartNum, (const void *)msg, msgSize, 12); +} \ No newline at end of file diff --git a/cores/esp32/esp32-hal-uart.h b/cores/esp32/esp32-hal-uart.h index 8e6e7ec4a1b..2b3268af966 100644 --- a/cores/esp32/esp32-hal-uart.h +++ b/cores/esp32/esp32-hal-uart.h @@ -102,6 +102,22 @@ void uartSetHwFlowCtrlMode(uart_t *uart, uint8_t mode, uint8_t threshold); void uartStartDetectBaudrate(uart_t *uart); unsigned long uartDetectBaudrate(uart_t *uart); +/* + These functions are for testing puspose only and can be used in Arduino Sketches + Those are used in the UART examples +*/ + +// Make sure UART's RX signal is connected to TX pin +// This creates a loop that lets us receive anything we send on the UART +void uart_internal_loopback(uint8_t uartNum, int8_t rxPin); + +// Routines that generate BREAK in the UART for testing purpose + +// Forces a BREAK in the line based on SERIAL_8N1 configuration at any baud rate +void uart_send_break(uint8_t uartNum); +// Sends a buffer and at the end of the stream, it generates BREAK in the line +int uart_send_msg_with_break(uint8_t uartNum, uint8_t *msg, size_t msgSize); + #ifdef __cplusplus } diff --git a/libraries/ESP32/examples/Serial/OnReceiveError_BREAK_Demo/OnReceiveError_BREAK_Demo.ino b/libraries/ESP32/examples/Serial/OnReceiveError_BREAK_Demo/OnReceiveError_BREAK_Demo.ino new file mode 100644 index 00000000000..5bb53bf90b8 --- /dev/null +++ b/libraries/ESP32/examples/Serial/OnReceiveError_BREAK_Demo/OnReceiveError_BREAK_Demo.ino @@ -0,0 +1,163 @@ +/* + + This Sketch demonstrates how to use onReceiveError(callbackFunc) with HardwareSerial + + void HardwareSerial::onReceiveError(OnReceiveErrorCb function) + + It is possible to register a UART callback function that will be called + everytime that UART detects an error which is also associated to an interrupt. + + There are some possible UART errors: + + UART_BREAK_ERROR - when a BREAK event is detected in the UART line. In that case, a BREAK may + be read as one or more bytes ZERO as part of the data received by the UART peripheral. + + UART_BUFFER_FULL_ERROR - When the RX UART buffer is full. By default, Arduino will allocate a 256 bytes + RX buffer. As data is received, it is copied to the UART driver buffer, but when it is full and data can't + be copied anymore, this Error is issued. To prevent it the application can use + HardwareSerial::setRxBufferSize(size_t new_size), before using HardwareSerial::begin() + + UART_FIFO_OVF_ERROR - When the UART peripheral RX FIFO is full and data is still arriving, this error is issued. + The UART driver will stash RX FIFO and the data will be lost. In order to prevent, the application shall set a + good buffer size using HardwareSerial::setRxBufferSize(size_t new_size), before using HardwareSerial::begin() + + UART_FRAME_ERROR - When the UART peripheral detects a UART frame error, this error is issued. It may happen because + of line noise or bad impiedance. + + UART_PARITY_ERROR - When the UART peripheral detects a parity bit error, this error will be issued. + + + In summary, HardwareSerial::onReceiveError() works like an UART Error Notification callback. + + Errors have priority in the order of the callbacks, therefore, as soon as an error is detected, + the registered callback is executed firt, and only after that, the OnReceive() registered + callback function will be executed. This will give opportunity for the Application to take action + before reading data, if necessary. + + In long UART transmissions, some data will be received based on FIFO Full parameter, and whenever + an error ocurs, it will raise the UART error interrupt. + + This sketch produces BREAK UART error in the begining of a transmission and also at the end of a + transmission. It will be possible to understand the order of the events in the logs. + +*/ + +#include + +// There are two ways to make this sketch work: +// By physically connecting the pins 4 and 5 and then create a physical UART loopback, +// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the +// same loopback internally. +#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally + +#define DATA_SIZE 26 // 26 bytes is a lower than RX FIFO size (127 bytes) +#define BAUD 9600 // Any baudrate from 300 to 115200 +#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values +#define RXPIN 4 // GPIO 4 => RX for Serial1 +#define TXPIN 5 // GPIO 5 => TX for Serial1 + +#define BREAK_BEFORE_MSG 0 +#define BREAK_AT_END_MSG 1 + + +uint8_t fifoFullTestCases[] = {120, 20, 5, 1}; +// volatile declaration will avoid any compiler optimization when reading variable values +volatile size_t sent_bytes = 0, received_bytes = 0; + +const char *uartErrorStrings[] = { + "UART_NO_ERROR", + "UART_BREAK_ERROR", + "UART_BUFFER_FULL_ERROR", + "UART_FIFO_OVF_ERROR", + "UART_FRAME_ERROR", + "UART_PARITY_ERROR" +}; + +// Callback function that will treat the UART errors +void onReceiveErrorFunction(hardwareSerial_error_t err) { + // This is a callback function that will be activated on UART RX Error Events + Serial.printf("\n-- onReceiveError [ERR#%d:%s] \n", err, uartErrorStrings[err]); + Serial.printf("-- onReceiveError:: There are %d bytes available.\n", Serial1.available()); +} + +// Callback function that will deal with arriving UART data +void onReceiveFunction() { + // This is a callback function that will be activated on UART RX events + size_t available = Serial1.available(); + received_bytes += available; + Serial.printf("onReceive Callback:: There are %d bytes available: {", available); + while (available --) { + Serial.print((char)Serial1.read()); + } + Serial.println("}"); +} + +void setup() { + // UART0 will be used to log information into Serial Monitor + Serial.begin(115200); + + // UART1 will have its RX<->TX cross connected + // GPIO4 <--> GPIO5 using external wire + Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3 +#if USE_INTERNAL_PIN_LOOPBACK + uart_internal_loopback(TEST_UART, RXPIN); +#endif + + for (uint8_t i = 0; i < sizeof(fifoFullTestCases); i++) { + Serial.printf("\n\n================================\nTest Case #%d BREAK at END\n================================\n", i + 1); + // First sending BREAK at the end of the UART data transmission + testAndReport(fifoFullTestCases[i], BREAK_AT_END_MSG); + Serial.printf("\n\n================================\nTest Case #%d BREAK at BEGINING\n================================\n", i + 1); + // Now sending BREAK at the begining of the UART data transmission + testAndReport(fifoFullTestCases[i], BREAK_BEFORE_MSG); + Serial.println("========================\nFinished!"); + } + +} + +void loop() { +} + +void testAndReport(uint8_t fifoFull, bool break_at_the_end) { + // Let's send 125 bytes from Serial1 rx<->tx and mesaure time using diferent FIFO Full configurations + received_bytes = 0; + sent_bytes = DATA_SIZE; // 26 characters + + uint8_t dataSent[DATA_SIZE + 1]; + dataSent[DATA_SIZE] = '\0'; // string null terminator, for easy printing. + + // initialize all data + for (uint8_t i = 0; i < DATA_SIZE; i++) { + dataSent[i] = 'A' + i; // fill it with characters A..Z + } + + Serial.printf("\nTesting onReceive for receiving %d bytes at %d baud, using RX FIFO Full = %d.\n", sent_bytes, BAUD, fifoFull); + Serial.println("onReceive is called on both FIFO Full and RX Timeout events."); + if (break_at_the_end) { + Serial.printf("BREAK event will be sent at the END of the %d bytes\n", sent_bytes); + } else { + Serial.printf("BREAK event will be sent at the BEGINING of the %d bytes\n", sent_bytes); + } + Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it + Serial1.setRxFIFOFull(fifoFull); // testing diferent result based on FIFO Full setup + Serial1.onReceive(onReceiveFunction); // sets a RX callback function for Serial 1 + Serial1.onReceiveError(onReceiveErrorFunction); // sets a RX callback function for Serial 1 + + if (break_at_the_end) { + sent_bytes = uart_send_msg_with_break(TEST_UART, dataSent, DATA_SIZE); + } else { + uart_send_break(TEST_UART); + sent_bytes = Serial1.write(dataSent, DATA_SIZE); + } + + Serial.printf("\nSent String: %s\n", dataSent); + while (received_bytes < sent_bytes) { + // just wait for receiving all byte in the callback... + } + + Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sent_bytes); + Serial.printf("onReceive() has read a total of %d bytes\n", received_bytes); + + Serial1.onReceiveError(NULL); // resets/disables the RX Error callback function for Serial 1 + Serial1.onReceive(NULL); // resets/disables the RX callback function for Serial 1 +} diff --git a/libraries/ESP32/examples/Serial/OnReceive_Demo/OnReceive_Demo.ino b/libraries/ESP32/examples/Serial/OnReceive_Demo/OnReceive_Demo.ino new file mode 100644 index 00000000000..192dbd020da --- /dev/null +++ b/libraries/ESP32/examples/Serial/OnReceive_Demo/OnReceive_Demo.ino @@ -0,0 +1,133 @@ +/* + + This Sketch demonstrates how to use onReceive(callbackFunc) with HardwareSerial + + void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false) + + It is possible to register a UART callback function that will be called + everytime that UART receives data and an associated interrupt is generated. + + The receiving data interrupt 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 at the UART initialzation using HardwareSerial::begin() + This will depend on the baud rate set with when begin() is executed. + For a baudrate of 115200 or lower, it it just 1 byte, mimicking original Arduino UART driver. + For a baudrate over 115200 it will be 120 bytes for higher performance. + Anyway it can be changed by the application at anytime. + + 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. + + In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted + using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout(). + +*/ + +#include + +// There are two ways to make this sketch work: +// By physically connecting the pins 4 and 5 and then create a physical UART loopback, +// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the +// same loopback internally. +#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally + +#define DATA_SIZE 26 // 26 bytes is a lower than RX FIFO size (127 bytes) +#define BAUD 9600 // Any baudrate from 300 to 115200 +#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values +#define RXPIN 4 // GPIO 4 => RX for Serial1 +#define TXPIN 5 // GPIO 5 => TX for Serial1 + +uint8_t fifoFullTestCases[] = {120, 20, 5, 1}; +// volatile declaration will avoid any compiler optimization when reading variable values +volatile size_t sent_bytes = 0, received_bytes = 0; + + +void onReceiveFunction(void) { + // This is a callback function that will be activated on UART RX events + size_t available = Serial1.available(); + received_bytes += available; + Serial.printf("onReceive Callback:: There are %d bytes available: ", available); + while (available --) { + Serial.print((char)Serial1.read()); + } + Serial.println(); +} + +void setup() { + // UART0 will be used to log information into Serial Monitor + Serial.begin(115200); + + // UART1 will have its RX<->TX cross connected + // GPIO4 <--> GPIO5 using external wire + Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3 +#if USE_INTERNAL_PIN_LOOPBACK + uart_internal_loopback(TEST_UART, RXPIN); +#endif + + + for (uint8_t i = 0; i < sizeof(fifoFullTestCases); i++) { + Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1); + // onReceive callback will be called on FIFO Full and RX timeout - default behaviour + testAndReport(fifoFullTestCases[i], false); + } + + Serial.printf("\n\n================================\nTest Case #6\n================================\n"); + // onReceive callback will be called just on RX timeout - using onlyOnTimeout = true + // FIFO Full parameter (5 bytes) won't matter for the execution of this test case + // because onReceive() uses only RX Timeout to be activated + testAndReport(5, true); +} + +void loop() { +} + +void testAndReport(uint8_t fifoFull, bool onlyOnTimeOut) { + // Let's send 125 bytes from Serial1 rx<->tx and mesaure time using diferent FIFO Full configurations + received_bytes = 0; + sent_bytes = DATA_SIZE; // 26 characters + + uint8_t dataSent[DATA_SIZE + 1]; + dataSent[DATA_SIZE] = '\0'; // string null terminator, for easy printing. + + // initialize all data + for (uint8_t i = 0; i < DATA_SIZE; i++) { + dataSent[i] = 'A' + i; // fill it with characters A..Z + } + + Serial.printf("\nTesting onReceive for receiving %d bytes at %d baud, using RX FIFO Full = %d.\n", sent_bytes, BAUD, fifoFull); + if (onlyOnTimeOut) { + Serial.println("onReceive is called just on RX Timeout!"); + } else { + Serial.println("onReceive is called on both FIFO Full and RX Timeout events."); + } + Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it + Serial1.setRxFIFOFull(fifoFull); // testing diferent result based on FIFO Full setup + Serial1.onReceive(onReceiveFunction, onlyOnTimeOut); // sets a RX callback function for Serial 1 + + sent_bytes = Serial1.write(dataSent, DATA_SIZE); // ESP32 TX FIFO is about 128 bytes, 125 bytes will fit fine + Serial.printf("\nSent String: %s\n", dataSent); + while (received_bytes < sent_bytes) { + // just wait for receiving all byte in the callback... + } + + Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sent_bytes); + Serial.printf("onReceive() has read a total of %d bytes\n", received_bytes); + Serial.println("========================\nFinished!"); + + Serial1.onReceive(NULL); // resets/disables the RX callback function for Serial 1 +} diff --git a/libraries/ESP32/examples/Serial/RxFIFOFull_Demo/RxFIFOFull_Demo.ino b/libraries/ESP32/examples/Serial/RxFIFOFull_Demo/RxFIFOFull_Demo.ino new file mode 100644 index 00000000000..f5c8dabaf39 --- /dev/null +++ b/libraries/ESP32/examples/Serial/RxFIFOFull_Demo/RxFIFOFull_Demo.ino @@ -0,0 +1,103 @@ +/* + * + * This Sketch demonstrates the effect of changing RX FIFO Full parameter into HardwareSerial Class + * Serial.setRxFIFOFull(byte) is used to change it. + * By default, UART ISR will wait for 120 bytes to arrive into UART before making the data available + * to be read by an Arduino Sketch. It may also release fewer bytes after an RX Timeout equivalent by + * default to 2 UART symbols. + * + * The way we demonstrate the effect of this parameter is by measuring the time the Sketch takes + * to read data using Arduino HardwareSerial API. + * + * The higher RX FIFO Full is, the lower consumption of the core to process and make the data available. + * At the same time, it may take longer for the Sketch to be able to read it, because the data must first + * populate RX UART FIFO. + * + * The lower RX FIFO Full is, the higher consumption of the core to process and make the data available. + * This is because the core will be interrupted often and it will copy data from the RX FIFO to the Arduino + * internal buffer to be read by the sketch. By other hand, the data will be made available to the sketch + * faster, in a close to byte by byte communication. + * + * Therefore, it allows the decision of the architecture to be designed by the developer. + * Some application based on certain protocols may need the sketch to read the Serial Port byte by byte, for example. + * + */ + +#include + +// There are two ways to make this sketch work: +// By physically connecting the pins 4 and 5 and then create a physical UART loopback, +// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the +// same loopback internally. +#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally + +#define DATA_SIZE 125 // 125 bytes is a bit higher than the default 120 bytes of RX FIFO FULL +#define BAUD 9600 // Any baudrate from 300 to 115200 +#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values +#define RXPIN 4 // GPIO 4 => RX for Serial1 +#define TXPIN 5 // GPIO 5 => TX for Serial1 + +uint8_t fifoFullTestCases[] = {120, 20, 5, 1}; + +void setup() { + // UART0 will be used to log information into Serial Monitor + Serial.begin(115200); + + // UART1 will have its RX<->TX cross connected + // GPIO4 <--> GPIO5 using external wire + Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3 +#if USE_INTERNAL_PIN_LOOPBACK + uart_internal_loopback(TEST_UART, RXPIN); +#endif + + for (uint8_t i = 0; i < sizeof(fifoFullTestCases); i++) { + Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1); + testAndReport(fifoFullTestCases[i]); + } + +} + +void loop() { +} + +void testAndReport(uint8_t fifoFull) { + // Let's send 125 bytes from Serial1 rx<->tx and mesaure time using diferent FIFO Full configurations + uint8_t bytesReceived = 0; + uint8_t dataSent[DATA_SIZE], dataReceived[DATA_SIZE]; + uint32_t timeStamp[DATA_SIZE], bytesJustReceived[DATA_SIZE]; + uint8_t i; + // initialize all data + for (i = 0; i < DATA_SIZE; i++) { + dataSent[i] = '0' + (i % 10); // fill it with a repeated sequence of 0..9 characters + dataReceived[i] = 0; + timeStamp[i] = 0; + bytesJustReceived[i] = 0; + } + + Serial.printf("Testing the time for receiving %d bytes at %d baud, using RX FIFO Full = %d:", DATA_SIZE, BAUD, fifoFull); + Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it + Serial1.setRxFIFOFull(fifoFull); // testing diferent result based on FIFO Full setup + + size_t sentBytes = Serial1.write(dataSent, sizeof(dataSent)); // ESP32 TX FIFO is about 128 bytes, 125 bytes will fit fine + uint32_t now = millis(); + i = 0; + while (bytesReceived < DATA_SIZE) { + bytesReceived += (bytesJustReceived[i] = Serial1.read(dataReceived + bytesReceived, DATA_SIZE)); + timeStamp[i] = millis(); + if (bytesJustReceived[i] > 0) i++; // next data only when we read something from Serial1 + // safety for array limit && timeout... in 5 seconds... + if (i == DATA_SIZE || millis() - now > 5000) break; + } + + uint32_t pastTime = millis() - now; + Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sentBytes); + Serial.printf("It took %d milliseconds to read %d bytes\n", pastTime, bytesReceived); + Serial.printf("Per execution Serial.read() number of bytes data and time information:\n"); + for (i = 0; i < DATA_SIZE; i++) { + Serial.printf("#%03d - Received %03d bytes after %d ms.\n", i, bytesJustReceived[i], i > 0 ? timeStamp[i] - timeStamp[i - 1] : timeStamp[i] - now); + if (i != DATA_SIZE - 1 && bytesJustReceived[i + 1] == 0) break; + } + + Serial.println("========================\nFinished!"); +} + diff --git a/libraries/ESP32/examples/Serial/RxTimeout_Demo/RxTimeout_Demo.ino b/libraries/ESP32/examples/Serial/RxTimeout_Demo/RxTimeout_Demo.ino new file mode 100644 index 00000000000..35a94bd2747 --- /dev/null +++ b/libraries/ESP32/examples/Serial/RxTimeout_Demo/RxTimeout_Demo.ino @@ -0,0 +1,95 @@ +/* + + This Sketch demonstrates the effect of changing RX Timeout parameter into HardwareSerial Class + Serial.setRxTimeout(byte) is used to change it. + By default, UART ISR will wait for an RX Timeout equivalent to 2 UART symbols to understand that a flow. + of UART data has ended. For example, if just one byte is received, UART will send about 10 to + 11 bits depending of the configuration (parity, number of stopbits). The timeout is measured in + number of UART symbols, with 10 or 11 bits, in the current baudrate. + For 9600 baud, 1 bit takes 1/9600 of a second, equivalent to 104 microseconds, therefore, for 10 bits, + it takes about 1ms. A timeout of 2 UART symbols, with about 20 bits, would take about 2.1 milliseconds + for the ESP32 UART to trigger an IRQ telling the UART driver that the transmission has ended. + Just at this point, the data will be made available to Arduino HardwareSerial API (read(), available(), etc). + + The way we demonstrate the effect of this parameter is by measuring the time the Sketch takes + to read data using Arduino HardwareSerial API. + + The higher RX Timeout is, the longer it will take to make the data available, when a flow of data ends. + UART driver works copying data from UART FIFO to Arduino internal buffer. + The driver will copy data from FIFO when RX Timeout is detected or when FIFO is full. + ESP32 FIFO has 128 bytes and by default, the driver will copy the data when FIFO reaches 120 bytes. + If UART receives less than 120 bytes, it will wait RX Timeout to understand that the bus is IDLE and + then copy the data from the FIFO to the Arduino internal buffer, making it availble to the Arduino API. + +*/ + +#include + +// There are two ways to make this sketch work: +// By physically connecting the pins 4 and 5 and then create a physical UART loopback, +// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the +// same loopback internally. +#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally + +#define DATA_SIZE 10 // 10 bytes is lower than the default 120 bytes of RX FIFO FULL +#define BAUD 9600 // Any baudrate from 300 to 115200 +#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values +#define RXPIN 4 // GPIO 4 => RX for Serial1 +#define TXPIN 5 // GPIO 5 => TX for Serial1 + +uint8_t rxTimeoutTestCases[] = {50, 20, 10, 5, 1}; + +void setup() { + // UART0 will be used to log information into Serial Monitor + Serial.begin(115200); + + // UART1 will have its RX<->TX cross connected + // GPIO4 <--> GPIO5 using external wire + Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3 +#if USE_INTERNAL_PIN_LOOPBACK + uart_internal_loopback(TEST_UART, RXPIN); +#endif + + for (uint8_t i = 0; i < sizeof(rxTimeoutTestCases); i++) { + Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1); + testAndReport(rxTimeoutTestCases[i]); + } + +} + +void loop() { +} + +void testAndReport(uint8_t rxTimeout) { + // Let's send 10 bytes from Serial1 rx<->tx and mesaure time using diferent Rx Timeout configurations + uint8_t bytesReceived = 0; + uint8_t dataSent[DATA_SIZE], dataReceived[DATA_SIZE]; + uint8_t i; + // initialize all data + for (i = 0; i < DATA_SIZE; i++) { + dataSent[i] = '0' + (i % 10); // fill it with a repeated sequence of 0..9 characters + dataReceived[i] = 0; + } + + Serial.printf("Testing the time for receiving %d bytes at %d baud, using RX Timeout = %d:", DATA_SIZE, BAUD, rxTimeout); + Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it + Serial1.setRxTimeout(rxTimeout); // testing diferent results based on Rx Timeout setup + // For baud rates lower or equal to 57600, ESP32 Arduino makes it get byte-by-byte from FIFO, thus we will change it here: + Serial1.setRxFIFOFull(120); // forces it to wait receiving 120 bytes in FIFO before making it availble to Arduino + + size_t sentBytes = Serial1.write(dataSent, sizeof(dataSent)); // ESP32 TX FIFO is about 128 bytes, 10 bytes will fit fine + uint32_t now = millis(); + while (bytesReceived < DATA_SIZE) { + bytesReceived += Serial1.read(dataReceived, DATA_SIZE); + // safety for array limit && timeout... in 5 seconds... + if (millis() - now > 5000) break; + } + + uint32_t pastTime = millis() - now; + Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sentBytes); + Serial.printf("It took %d milliseconds to read %d bytes\n", pastTime, bytesReceived); + Serial.print("Received data: ["); + Serial.write(dataReceived, DATA_SIZE); + Serial.println("]"); + Serial.println("========================\nFinished!"); +}