Skip to content

Commit 5dff15c

Browse files
authoredDec 21, 2022
Fixes inconsistencies and adds extended HardwareSerial examples (#7412)
* adds extended HardwareSerial examples * Adds new example with Serial RxTimeout * adds and improves Serial onReceive expamples * adjust includes CMake - UART example * adjust includes CMake - UART example * fixes CMake and CI * adds ESP/Serial to CMakeList * adds ESP/Serial to CMakeList * fixes demo include * fixes BREAK demo * fixes onReceive demo * Changes FIFO Full criteria Changed the "1-by-1" Serial only when baud rate is 57600 or lower. * example code replacement * replaces functions in hal
1 parent a95ce27 commit 5dff15c

File tree

8 files changed

+609
-8
lines changed

8 files changed

+609
-8
lines changed
 

‎cores/esp32/HardwareSerial.cpp

+45-7
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,9 @@ _rxBufferSize(256),
139139
_txBufferSize(0),
140140
_onReceiveCB(NULL),
141141
_onReceiveErrorCB(NULL),
142-
_onReceiveTimeout(true),
142+
_onReceiveTimeout(false),
143143
_rxTimeout(2),
144+
_rxFIFOFull(0),
144145
_eventTask(NULL)
145146
#if !CONFIG_DISABLE_HAL_LOCKS
146147
,_lock(NULL)
@@ -206,12 +207,23 @@ void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout)
206207
HSERIAL_MUTEX_LOCK();
207208
// function may be NULL to cancel onReceive() from its respective task
208209
_onReceiveCB = function;
209-
// When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes
210-
_onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false;
211210

212-
// this can be called after Serial.begin(), therefore it shall create the event task
213-
if (function != NULL && _uart != NULL && _eventTask == NULL) {
214-
_createEventTask(this); // Create event task
211+
// setting the callback to NULL will just disable it
212+
if (_onReceiveCB != NULL) {
213+
// When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes
214+
_onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false;
215+
216+
// in case that onReceive() shall work only with RX Timeout, FIFO shall be high
217+
// this is a work around for an IDF issue with events and low FIFO Full value (< 3)
218+
if (_onReceiveTimeout) {
219+
uartSetRxFIFOFull(_uart, 120);
220+
log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
221+
}
222+
223+
// this method can be called after Serial.begin(), therefore it shall create the event task
224+
if (_uart != NULL && _eventTask == NULL) {
225+
_createEventTask(this); // Create event task
226+
}
215227
}
216228
HSERIAL_MUTEX_UNLOCK();
217229
}
@@ -224,7 +236,14 @@ void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout)
224236
void HardwareSerial::setRxFIFOFull(uint8_t fifoBytes)
225237
{
226238
HSERIAL_MUTEX_LOCK();
239+
// in case that onReceive() shall work only with RX Timeout, FIFO shall be high
240+
// this is a work around for an IDF issue with events and low FIFO Full value (< 3)
241+
if (_onReceiveCB != NULL && _onReceiveTimeout) {
242+
fifoBytes = 120;
243+
log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
244+
}
227245
uartSetRxFIFOFull(_uart, fifoBytes); // Set new timeout
246+
if (fifoBytes > 0 && fifoBytes < SOC_UART_FIFO_LEN - 1) _rxFIFOFull = fifoBytes;
228247
HSERIAL_MUTEX_UNLOCK();
229248
}
230249

@@ -299,7 +318,6 @@ void HardwareSerial::_uartEventTask(void *args)
299318
}
300319
if (currentErr != UART_NO_ERROR) {
301320
if(uart->_onReceiveErrorCB) uart->_onReceiveErrorCB(currentErr);
302-
if(uart->_onReceiveCB && uart->available() > 0) uart->_onReceiveCB(); // forces User Callback too
303321
}
304322
}
305323
}
@@ -388,8 +406,24 @@ void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, in
388406

389407
// Set UART RX timeout
390408
uartSetRxTimeout(_uart, _rxTimeout);
409+
410+
// Set UART FIFO Full depending on the baud rate.
411+
// Lower baud rates will force to emulate byte-by-byte reading
412+
// Higher baud rates will keep IDF default of 120 bytes for FIFO FULL Interrupt
413+
// It can also be changed by the application at any time
414+
if (!_rxFIFOFull) { // it has not being changed before calling begin()
415+
// set a default FIFO Full value for the IDF driver
416+
uint8_t fifoFull = 1;
417+
if (baud > 57600 || (_onReceiveCB != NULL && _onReceiveTimeout)) {
418+
fifoFull = 120;
419+
}
420+
uartSetRxFIFOFull(_uart, fifoFull);
421+
_rxFIFOFull = fifoFull;
422+
}
423+
391424
_rxPin = rxPin;
392425
_txPin = txPin;
426+
393427
HSERIAL_MUTEX_UNLOCK();
394428
}
395429

@@ -408,8 +442,12 @@ void HardwareSerial::end(bool fullyTerminate)
408442
if (uartGetDebug() == _uart_nr) {
409443
uartSetDebug(0);
410444
}
445+
446+
_rxFIFOFull = 0;
447+
411448
uartDetachPins(_uart, _rxPin, _txPin, _ctsPin, _rtsPin);
412449
_rxPin = _txPin = _ctsPin = _rtsPin = -1;
450+
413451
}
414452
delay(10);
415453
uartEnd(_uart);

‎cores/esp32/HardwareSerial.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class HardwareSerial: public Stream
177177
OnReceiveErrorCb _onReceiveErrorCB;
178178
// _onReceive and _rxTimeout have be consistent when timeout is disabled
179179
bool _onReceiveTimeout;
180-
uint8_t _rxTimeout;
180+
uint8_t _rxTimeout, _rxFIFOFull;
181181
TaskHandle_t _eventTask;
182182
#if !CONFIG_DISABLE_HAL_LOCKS
183183
SemaphoreHandle_t _lock;

‎cores/esp32/esp32-hal-uart.c

+53
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
#include "hal/uart_ll.h"
2323
#include "soc/soc_caps.h"
2424
#include "soc/uart_struct.h"
25+
#include "soc/uart_periph.h"
2526

27+
#include "driver/gpio.h"
2628
#include "hal/gpio_hal.h"
2729
#include "esp_rom_gpio.h"
2830

@@ -743,3 +745,54 @@ uartDetectBaudrate(uart_t *uart)
743745
return 0;
744746
#endif
745747
}
748+
749+
/*
750+
These functions are for testing puspose only and can be used in Arduino Sketches
751+
Those are used in the UART examples
752+
*/
753+
754+
/*
755+
This is intended to make an internal loopback connection using IOMUX
756+
The function uart_internal_loopback() shall be used right after Arduino Serial.begin(...)
757+
This code "replaces" the physical wiring for connecting TX <--> RX in a loopback
758+
*/
759+
760+
// gets the right TX SIGNAL, based on the UART number
761+
#if SOC_UART_NUM > 2
762+
#define UART_TX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0TXD_OUT_IDX : (uartNumber == UART_NUM_1 ? U1TXD_OUT_IDX : U2TXD_OUT_IDX))
763+
#else
764+
#define UART_TX_SIGNAL(uartNumber) (uartNumber == UART_NUM_0 ? U0TXD_OUT_IDX : U1TXD_OUT_IDX)
765+
#endif
766+
/*
767+
Make sure UART's RX signal is connected to TX pin
768+
This creates a loop that lets us receive anything we send on the UART
769+
*/
770+
void uart_internal_loopback(uint8_t uartNum, int8_t rxPin)
771+
{
772+
if (uartNum > SOC_UART_NUM - 1 || !GPIO_IS_VALID_GPIO(rxPin)) return;
773+
esp_rom_gpio_connect_out_signal(rxPin, UART_TX_SIGNAL(uartNum), false, false);
774+
}
775+
776+
/*
777+
This is intended to generate BREAK in an UART line
778+
*/
779+
780+
// Forces a BREAK in the line based on SERIAL_8N1 configuration at any baud rate
781+
void uart_send_break(uint8_t uartNum)
782+
{
783+
uint32_t currentBaudrate = 0;
784+
uart_get_baudrate(uartNum, &currentBaudrate);
785+
// calculates 10 bits of breaks in microseconds for baudrates up to 500mbps
786+
// This is very sensetive timing... it works fine for SERIAL_8N1
787+
uint32_t breakTime = (uint32_t) (10.0 * (1000000.0 / currentBaudrate));
788+
uart_set_line_inverse(uartNum, UART_SIGNAL_TXD_INV);
789+
ets_delay_us(breakTime);
790+
uart_set_line_inverse(uartNum, UART_SIGNAL_INV_DISABLE);
791+
}
792+
793+
// Sends a buffer and at the end of the stream, it generates BREAK in the line
794+
int uart_send_msg_with_break(uint8_t uartNum, uint8_t *msg, size_t msgSize)
795+
{
796+
// 12 bits long BREAK for 8N1
797+
return uart_write_bytes_with_break(uartNum, (const void *)msg, msgSize, 12);
798+
}

‎cores/esp32/esp32-hal-uart.h

+16
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,22 @@ void uartSetHwFlowCtrlMode(uart_t *uart, uint8_t mode, uint8_t threshold);
102102
void uartStartDetectBaudrate(uart_t *uart);
103103
unsigned long uartDetectBaudrate(uart_t *uart);
104104

105+
/*
106+
These functions are for testing puspose only and can be used in Arduino Sketches
107+
Those are used in the UART examples
108+
*/
109+
110+
// Make sure UART's RX signal is connected to TX pin
111+
// This creates a loop that lets us receive anything we send on the UART
112+
void uart_internal_loopback(uint8_t uartNum, int8_t rxPin);
113+
114+
// Routines that generate BREAK in the UART for testing purpose
115+
116+
// Forces a BREAK in the line based on SERIAL_8N1 configuration at any baud rate
117+
void uart_send_break(uint8_t uartNum);
118+
// Sends a buffer and at the end of the stream, it generates BREAK in the line
119+
int uart_send_msg_with_break(uint8_t uartNum, uint8_t *msg, size_t msgSize);
120+
105121

106122
#ifdef __cplusplus
107123
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
3+
This Sketch demonstrates how to use onReceiveError(callbackFunc) with HardwareSerial
4+
5+
void HardwareSerial::onReceiveError(OnReceiveErrorCb function)
6+
7+
It is possible to register a UART callback function that will be called
8+
everytime that UART detects an error which is also associated to an interrupt.
9+
10+
There are some possible UART errors:
11+
12+
UART_BREAK_ERROR - when a BREAK event is detected in the UART line. In that case, a BREAK may
13+
be read as one or more bytes ZERO as part of the data received by the UART peripheral.
14+
15+
UART_BUFFER_FULL_ERROR - When the RX UART buffer is full. By default, Arduino will allocate a 256 bytes
16+
RX buffer. As data is received, it is copied to the UART driver buffer, but when it is full and data can't
17+
be copied anymore, this Error is issued. To prevent it the application can use
18+
HardwareSerial::setRxBufferSize(size_t new_size), before using HardwareSerial::begin()
19+
20+
UART_FIFO_OVF_ERROR - When the UART peripheral RX FIFO is full and data is still arriving, this error is issued.
21+
The UART driver will stash RX FIFO and the data will be lost. In order to prevent, the application shall set a
22+
good buffer size using HardwareSerial::setRxBufferSize(size_t new_size), before using HardwareSerial::begin()
23+
24+
UART_FRAME_ERROR - When the UART peripheral detects a UART frame error, this error is issued. It may happen because
25+
of line noise or bad impiedance.
26+
27+
UART_PARITY_ERROR - When the UART peripheral detects a parity bit error, this error will be issued.
28+
29+
30+
In summary, HardwareSerial::onReceiveError() works like an UART Error Notification callback.
31+
32+
Errors have priority in the order of the callbacks, therefore, as soon as an error is detected,
33+
the registered callback is executed firt, and only after that, the OnReceive() registered
34+
callback function will be executed. This will give opportunity for the Application to take action
35+
before reading data, if necessary.
36+
37+
In long UART transmissions, some data will be received based on FIFO Full parameter, and whenever
38+
an error ocurs, it will raise the UART error interrupt.
39+
40+
This sketch produces BREAK UART error in the begining of a transmission and also at the end of a
41+
transmission. It will be possible to understand the order of the events in the logs.
42+
43+
*/
44+
45+
#include <Arduino.h>
46+
47+
// There are two ways to make this sketch work:
48+
// By physically connecting the pins 4 and 5 and then create a physical UART loopback,
49+
// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the
50+
// same loopback internally.
51+
#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally
52+
53+
#define DATA_SIZE 26 // 26 bytes is a lower than RX FIFO size (127 bytes)
54+
#define BAUD 9600 // Any baudrate from 300 to 115200
55+
#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values
56+
#define RXPIN 4 // GPIO 4 => RX for Serial1
57+
#define TXPIN 5 // GPIO 5 => TX for Serial1
58+
59+
#define BREAK_BEFORE_MSG 0
60+
#define BREAK_AT_END_MSG 1
61+
62+
63+
uint8_t fifoFullTestCases[] = {120, 20, 5, 1};
64+
// volatile declaration will avoid any compiler optimization when reading variable values
65+
volatile size_t sent_bytes = 0, received_bytes = 0;
66+
67+
const char *uartErrorStrings[] = {
68+
"UART_NO_ERROR",
69+
"UART_BREAK_ERROR",
70+
"UART_BUFFER_FULL_ERROR",
71+
"UART_FIFO_OVF_ERROR",
72+
"UART_FRAME_ERROR",
73+
"UART_PARITY_ERROR"
74+
};
75+
76+
// Callback function that will treat the UART errors
77+
void onReceiveErrorFunction(hardwareSerial_error_t err) {
78+
// This is a callback function that will be activated on UART RX Error Events
79+
Serial.printf("\n-- onReceiveError [ERR#%d:%s] \n", err, uartErrorStrings[err]);
80+
Serial.printf("-- onReceiveError:: There are %d bytes available.\n", Serial1.available());
81+
}
82+
83+
// Callback function that will deal with arriving UART data
84+
void onReceiveFunction() {
85+
// This is a callback function that will be activated on UART RX events
86+
size_t available = Serial1.available();
87+
received_bytes += available;
88+
Serial.printf("onReceive Callback:: There are %d bytes available: {", available);
89+
while (available --) {
90+
Serial.print((char)Serial1.read());
91+
}
92+
Serial.println("}");
93+
}
94+
95+
void setup() {
96+
// UART0 will be used to log information into Serial Monitor
97+
Serial.begin(115200);
98+
99+
// UART1 will have its RX<->TX cross connected
100+
// GPIO4 <--> GPIO5 using external wire
101+
Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3
102+
#if USE_INTERNAL_PIN_LOOPBACK
103+
uart_internal_loopback(TEST_UART, RXPIN);
104+
#endif
105+
106+
for (uint8_t i = 0; i < sizeof(fifoFullTestCases); i++) {
107+
Serial.printf("\n\n================================\nTest Case #%d BREAK at END\n================================\n", i + 1);
108+
// First sending BREAK at the end of the UART data transmission
109+
testAndReport(fifoFullTestCases[i], BREAK_AT_END_MSG);
110+
Serial.printf("\n\n================================\nTest Case #%d BREAK at BEGINING\n================================\n", i + 1);
111+
// Now sending BREAK at the begining of the UART data transmission
112+
testAndReport(fifoFullTestCases[i], BREAK_BEFORE_MSG);
113+
Serial.println("========================\nFinished!");
114+
}
115+
116+
}
117+
118+
void loop() {
119+
}
120+
121+
void testAndReport(uint8_t fifoFull, bool break_at_the_end) {
122+
// Let's send 125 bytes from Serial1 rx<->tx and mesaure time using diferent FIFO Full configurations
123+
received_bytes = 0;
124+
sent_bytes = DATA_SIZE; // 26 characters
125+
126+
uint8_t dataSent[DATA_SIZE + 1];
127+
dataSent[DATA_SIZE] = '\0'; // string null terminator, for easy printing.
128+
129+
// initialize all data
130+
for (uint8_t i = 0; i < DATA_SIZE; i++) {
131+
dataSent[i] = 'A' + i; // fill it with characters A..Z
132+
}
133+
134+
Serial.printf("\nTesting onReceive for receiving %d bytes at %d baud, using RX FIFO Full = %d.\n", sent_bytes, BAUD, fifoFull);
135+
Serial.println("onReceive is called on both FIFO Full and RX Timeout events.");
136+
if (break_at_the_end) {
137+
Serial.printf("BREAK event will be sent at the END of the %d bytes\n", sent_bytes);
138+
} else {
139+
Serial.printf("BREAK event will be sent at the BEGINING of the %d bytes\n", sent_bytes);
140+
}
141+
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
142+
Serial1.setRxFIFOFull(fifoFull); // testing diferent result based on FIFO Full setup
143+
Serial1.onReceive(onReceiveFunction); // sets a RX callback function for Serial 1
144+
Serial1.onReceiveError(onReceiveErrorFunction); // sets a RX callback function for Serial 1
145+
146+
if (break_at_the_end) {
147+
sent_bytes = uart_send_msg_with_break(TEST_UART, dataSent, DATA_SIZE);
148+
} else {
149+
uart_send_break(TEST_UART);
150+
sent_bytes = Serial1.write(dataSent, DATA_SIZE);
151+
}
152+
153+
Serial.printf("\nSent String: %s\n", dataSent);
154+
while (received_bytes < sent_bytes) {
155+
// just wait for receiving all byte in the callback...
156+
}
157+
158+
Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sent_bytes);
159+
Serial.printf("onReceive() has read a total of %d bytes\n", received_bytes);
160+
161+
Serial1.onReceiveError(NULL); // resets/disables the RX Error callback function for Serial 1
162+
Serial1.onReceive(NULL); // resets/disables the RX callback function for Serial 1
163+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
3+
This Sketch demonstrates how to use onReceive(callbackFunc) with HardwareSerial
4+
5+
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false)
6+
7+
It is possible to register a UART callback function that will be called
8+
everytime that UART receives data and an associated interrupt is generated.
9+
10+
The receiving data interrupt can occur because of two possible events:
11+
12+
1- UART FIFO FULL: it happens when internal UART FIFO reaches a certain number of bytes.
13+
Its full capacity is 127 bytes. The FIFO Full threshold for the interrupt can be changed
14+
using HardwareSerial::setRxFIFOFull(uint8_t fifoFull).
15+
Default FIFO Full Threshold is set at the UART initialzation using HardwareSerial::begin()
16+
This will depend on the baud rate set with when begin() is executed.
17+
For a baudrate of 115200 or lower, it it just 1 byte, mimicking original Arduino UART driver.
18+
For a baudrate over 115200 it will be 120 bytes for higher performance.
19+
Anyway it can be changed by the application at anytime.
20+
21+
2- UART RX Timeout: it happens, based on a timeout equivalent to a number of symbols at
22+
the current baud rate. If the UART line is idle for this timeout, it will raise an interrupt.
23+
This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout)
24+
25+
When any of those two interrupts occur, IDF UART driver will copy FIFO data to its internal
26+
RingBuffer and then Arduino can read such data. At the same time, Arduino Layer will execute
27+
the callback function defined with HardwareSerial::onReceive().
28+
29+
<bool onlyOnTimeout> parameter (default false) can be used by the application to tell Arduino to
30+
only execute the callback when the second event above happens (Rx Timeout). At this time all
31+
received data will be available to be read by the Arduino application. But if the number of
32+
received bytes is higher than the FIFO space, it will generate an error of FIFO overflow.
33+
In order to avoid such problem, the application shall set an appropriate RX buffer size using
34+
HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port.
35+
36+
In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted
37+
using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout().
38+
39+
*/
40+
41+
#include <Arduino.h>
42+
43+
// There are two ways to make this sketch work:
44+
// By physically connecting the pins 4 and 5 and then create a physical UART loopback,
45+
// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the
46+
// same loopback internally.
47+
#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally
48+
49+
#define DATA_SIZE 26 // 26 bytes is a lower than RX FIFO size (127 bytes)
50+
#define BAUD 9600 // Any baudrate from 300 to 115200
51+
#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values
52+
#define RXPIN 4 // GPIO 4 => RX for Serial1
53+
#define TXPIN 5 // GPIO 5 => TX for Serial1
54+
55+
uint8_t fifoFullTestCases[] = {120, 20, 5, 1};
56+
// volatile declaration will avoid any compiler optimization when reading variable values
57+
volatile size_t sent_bytes = 0, received_bytes = 0;
58+
59+
60+
void onReceiveFunction(void) {
61+
// This is a callback function that will be activated on UART RX events
62+
size_t available = Serial1.available();
63+
received_bytes += available;
64+
Serial.printf("onReceive Callback:: There are %d bytes available: ", available);
65+
while (available --) {
66+
Serial.print((char)Serial1.read());
67+
}
68+
Serial.println();
69+
}
70+
71+
void setup() {
72+
// UART0 will be used to log information into Serial Monitor
73+
Serial.begin(115200);
74+
75+
// UART1 will have its RX<->TX cross connected
76+
// GPIO4 <--> GPIO5 using external wire
77+
Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3
78+
#if USE_INTERNAL_PIN_LOOPBACK
79+
uart_internal_loopback(TEST_UART, RXPIN);
80+
#endif
81+
82+
83+
for (uint8_t i = 0; i < sizeof(fifoFullTestCases); i++) {
84+
Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1);
85+
// onReceive callback will be called on FIFO Full and RX timeout - default behaviour
86+
testAndReport(fifoFullTestCases[i], false);
87+
}
88+
89+
Serial.printf("\n\n================================\nTest Case #6\n================================\n");
90+
// onReceive callback will be called just on RX timeout - using onlyOnTimeout = true
91+
// FIFO Full parameter (5 bytes) won't matter for the execution of this test case
92+
// because onReceive() uses only RX Timeout to be activated
93+
testAndReport(5, true);
94+
}
95+
96+
void loop() {
97+
}
98+
99+
void testAndReport(uint8_t fifoFull, bool onlyOnTimeOut) {
100+
// Let's send 125 bytes from Serial1 rx<->tx and mesaure time using diferent FIFO Full configurations
101+
received_bytes = 0;
102+
sent_bytes = DATA_SIZE; // 26 characters
103+
104+
uint8_t dataSent[DATA_SIZE + 1];
105+
dataSent[DATA_SIZE] = '\0'; // string null terminator, for easy printing.
106+
107+
// initialize all data
108+
for (uint8_t i = 0; i < DATA_SIZE; i++) {
109+
dataSent[i] = 'A' + i; // fill it with characters A..Z
110+
}
111+
112+
Serial.printf("\nTesting onReceive for receiving %d bytes at %d baud, using RX FIFO Full = %d.\n", sent_bytes, BAUD, fifoFull);
113+
if (onlyOnTimeOut) {
114+
Serial.println("onReceive is called just on RX Timeout!");
115+
} else {
116+
Serial.println("onReceive is called on both FIFO Full and RX Timeout events.");
117+
}
118+
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
119+
Serial1.setRxFIFOFull(fifoFull); // testing diferent result based on FIFO Full setup
120+
Serial1.onReceive(onReceiveFunction, onlyOnTimeOut); // sets a RX callback function for Serial 1
121+
122+
sent_bytes = Serial1.write(dataSent, DATA_SIZE); // ESP32 TX FIFO is about 128 bytes, 125 bytes will fit fine
123+
Serial.printf("\nSent String: %s\n", dataSent);
124+
while (received_bytes < sent_bytes) {
125+
// just wait for receiving all byte in the callback...
126+
}
127+
128+
Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sent_bytes);
129+
Serial.printf("onReceive() has read a total of %d bytes\n", received_bytes);
130+
Serial.println("========================\nFinished!");
131+
132+
Serial1.onReceive(NULL); // resets/disables the RX callback function for Serial 1
133+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
*
3+
* This Sketch demonstrates the effect of changing RX FIFO Full parameter into HardwareSerial Class
4+
* Serial.setRxFIFOFull(byte) is used to change it.
5+
* By default, UART ISR will wait for 120 bytes to arrive into UART before making the data available
6+
* to be read by an Arduino Sketch. It may also release fewer bytes after an RX Timeout equivalent by
7+
* default to 2 UART symbols.
8+
*
9+
* The way we demonstrate the effect of this parameter is by measuring the time the Sketch takes
10+
* to read data using Arduino HardwareSerial API.
11+
*
12+
* The higher RX FIFO Full is, the lower consumption of the core to process and make the data available.
13+
* At the same time, it may take longer for the Sketch to be able to read it, because the data must first
14+
* populate RX UART FIFO.
15+
*
16+
* The lower RX FIFO Full is, the higher consumption of the core to process and make the data available.
17+
* This is because the core will be interrupted often and it will copy data from the RX FIFO to the Arduino
18+
* internal buffer to be read by the sketch. By other hand, the data will be made available to the sketch
19+
* faster, in a close to byte by byte communication.
20+
*
21+
* Therefore, it allows the decision of the architecture to be designed by the developer.
22+
* Some application based on certain protocols may need the sketch to read the Serial Port byte by byte, for example.
23+
*
24+
*/
25+
26+
#include <Arduino.h>
27+
28+
// There are two ways to make this sketch work:
29+
// By physically connecting the pins 4 and 5 and then create a physical UART loopback,
30+
// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the
31+
// same loopback internally.
32+
#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally
33+
34+
#define DATA_SIZE 125 // 125 bytes is a bit higher than the default 120 bytes of RX FIFO FULL
35+
#define BAUD 9600 // Any baudrate from 300 to 115200
36+
#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values
37+
#define RXPIN 4 // GPIO 4 => RX for Serial1
38+
#define TXPIN 5 // GPIO 5 => TX for Serial1
39+
40+
uint8_t fifoFullTestCases[] = {120, 20, 5, 1};
41+
42+
void setup() {
43+
// UART0 will be used to log information into Serial Monitor
44+
Serial.begin(115200);
45+
46+
// UART1 will have its RX<->TX cross connected
47+
// GPIO4 <--> GPIO5 using external wire
48+
Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3
49+
#if USE_INTERNAL_PIN_LOOPBACK
50+
uart_internal_loopback(TEST_UART, RXPIN);
51+
#endif
52+
53+
for (uint8_t i = 0; i < sizeof(fifoFullTestCases); i++) {
54+
Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1);
55+
testAndReport(fifoFullTestCases[i]);
56+
}
57+
58+
}
59+
60+
void loop() {
61+
}
62+
63+
void testAndReport(uint8_t fifoFull) {
64+
// Let's send 125 bytes from Serial1 rx<->tx and mesaure time using diferent FIFO Full configurations
65+
uint8_t bytesReceived = 0;
66+
uint8_t dataSent[DATA_SIZE], dataReceived[DATA_SIZE];
67+
uint32_t timeStamp[DATA_SIZE], bytesJustReceived[DATA_SIZE];
68+
uint8_t i;
69+
// initialize all data
70+
for (i = 0; i < DATA_SIZE; i++) {
71+
dataSent[i] = '0' + (i % 10); // fill it with a repeated sequence of 0..9 characters
72+
dataReceived[i] = 0;
73+
timeStamp[i] = 0;
74+
bytesJustReceived[i] = 0;
75+
}
76+
77+
Serial.printf("Testing the time for receiving %d bytes at %d baud, using RX FIFO Full = %d:", DATA_SIZE, BAUD, fifoFull);
78+
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
79+
Serial1.setRxFIFOFull(fifoFull); // testing diferent result based on FIFO Full setup
80+
81+
size_t sentBytes = Serial1.write(dataSent, sizeof(dataSent)); // ESP32 TX FIFO is about 128 bytes, 125 bytes will fit fine
82+
uint32_t now = millis();
83+
i = 0;
84+
while (bytesReceived < DATA_SIZE) {
85+
bytesReceived += (bytesJustReceived[i] = Serial1.read(dataReceived + bytesReceived, DATA_SIZE));
86+
timeStamp[i] = millis();
87+
if (bytesJustReceived[i] > 0) i++; // next data only when we read something from Serial1
88+
// safety for array limit && timeout... in 5 seconds...
89+
if (i == DATA_SIZE || millis() - now > 5000) break;
90+
}
91+
92+
uint32_t pastTime = millis() - now;
93+
Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sentBytes);
94+
Serial.printf("It took %d milliseconds to read %d bytes\n", pastTime, bytesReceived);
95+
Serial.printf("Per execution Serial.read() number of bytes data and time information:\n");
96+
for (i = 0; i < DATA_SIZE; i++) {
97+
Serial.printf("#%03d - Received %03d bytes after %d ms.\n", i, bytesJustReceived[i], i > 0 ? timeStamp[i] - timeStamp[i - 1] : timeStamp[i] - now);
98+
if (i != DATA_SIZE - 1 && bytesJustReceived[i + 1] == 0) break;
99+
}
100+
101+
Serial.println("========================\nFinished!");
102+
}
103+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
3+
This Sketch demonstrates the effect of changing RX Timeout parameter into HardwareSerial Class
4+
Serial.setRxTimeout(byte) is used to change it.
5+
By default, UART ISR will wait for an RX Timeout equivalent to 2 UART symbols to understand that a flow.
6+
of UART data has ended. For example, if just one byte is received, UART will send about 10 to
7+
11 bits depending of the configuration (parity, number of stopbits). The timeout is measured in
8+
number of UART symbols, with 10 or 11 bits, in the current baudrate.
9+
For 9600 baud, 1 bit takes 1/9600 of a second, equivalent to 104 microseconds, therefore, for 10 bits,
10+
it takes about 1ms. A timeout of 2 UART symbols, with about 20 bits, would take about 2.1 milliseconds
11+
for the ESP32 UART to trigger an IRQ telling the UART driver that the transmission has ended.
12+
Just at this point, the data will be made available to Arduino HardwareSerial API (read(), available(), etc).
13+
14+
The way we demonstrate the effect of this parameter is by measuring the time the Sketch takes
15+
to read data using Arduino HardwareSerial API.
16+
17+
The higher RX Timeout is, the longer it will take to make the data available, when a flow of data ends.
18+
UART driver works copying data from UART FIFO to Arduino internal buffer.
19+
The driver will copy data from FIFO when RX Timeout is detected or when FIFO is full.
20+
ESP32 FIFO has 128 bytes and by default, the driver will copy the data when FIFO reaches 120 bytes.
21+
If UART receives less than 120 bytes, it will wait RX Timeout to understand that the bus is IDLE and
22+
then copy the data from the FIFO to the Arduino internal buffer, making it availble to the Arduino API.
23+
24+
*/
25+
26+
#include <Arduino.h>
27+
28+
// There are two ways to make this sketch work:
29+
// By physically connecting the pins 4 and 5 and then create a physical UART loopback,
30+
// Or by using the internal IO_MUX to connect the TX signal to the RX pin, creating the
31+
// same loopback internally.
32+
#define USE_INTERNAL_PIN_LOOPBACK 1 // 1 uses the internal loopback, 0 for wiring pins 4 and 5 externally
33+
34+
#define DATA_SIZE 10 // 10 bytes is lower than the default 120 bytes of RX FIFO FULL
35+
#define BAUD 9600 // Any baudrate from 300 to 115200
36+
#define TEST_UART 1 // Serial1 will be used for the loopback testing with different RX FIFO FULL values
37+
#define RXPIN 4 // GPIO 4 => RX for Serial1
38+
#define TXPIN 5 // GPIO 5 => TX for Serial1
39+
40+
uint8_t rxTimeoutTestCases[] = {50, 20, 10, 5, 1};
41+
42+
void setup() {
43+
// UART0 will be used to log information into Serial Monitor
44+
Serial.begin(115200);
45+
46+
// UART1 will have its RX<->TX cross connected
47+
// GPIO4 <--> GPIO5 using external wire
48+
Serial1.begin(BAUD, SERIAL_8N1, RXPIN, TXPIN); // Rx = 4, Tx = 5 will work for ESP32, S2, S3 and C3
49+
#if USE_INTERNAL_PIN_LOOPBACK
50+
uart_internal_loopback(TEST_UART, RXPIN);
51+
#endif
52+
53+
for (uint8_t i = 0; i < sizeof(rxTimeoutTestCases); i++) {
54+
Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1);
55+
testAndReport(rxTimeoutTestCases[i]);
56+
}
57+
58+
}
59+
60+
void loop() {
61+
}
62+
63+
void testAndReport(uint8_t rxTimeout) {
64+
// Let's send 10 bytes from Serial1 rx<->tx and mesaure time using diferent Rx Timeout configurations
65+
uint8_t bytesReceived = 0;
66+
uint8_t dataSent[DATA_SIZE], dataReceived[DATA_SIZE];
67+
uint8_t i;
68+
// initialize all data
69+
for (i = 0; i < DATA_SIZE; i++) {
70+
dataSent[i] = '0' + (i % 10); // fill it with a repeated sequence of 0..9 characters
71+
dataReceived[i] = 0;
72+
}
73+
74+
Serial.printf("Testing the time for receiving %d bytes at %d baud, using RX Timeout = %d:", DATA_SIZE, BAUD, rxTimeout);
75+
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
76+
Serial1.setRxTimeout(rxTimeout); // testing diferent results based on Rx Timeout setup
77+
// For baud rates lower or equal to 57600, ESP32 Arduino makes it get byte-by-byte from FIFO, thus we will change it here:
78+
Serial1.setRxFIFOFull(120); // forces it to wait receiving 120 bytes in FIFO before making it availble to Arduino
79+
80+
size_t sentBytes = Serial1.write(dataSent, sizeof(dataSent)); // ESP32 TX FIFO is about 128 bytes, 10 bytes will fit fine
81+
uint32_t now = millis();
82+
while (bytesReceived < DATA_SIZE) {
83+
bytesReceived += Serial1.read(dataReceived, DATA_SIZE);
84+
// safety for array limit && timeout... in 5 seconds...
85+
if (millis() - now > 5000) break;
86+
}
87+
88+
uint32_t pastTime = millis() - now;
89+
Serial.printf("\nIt has sent %d bytes from Serial1 TX to Serial1 RX\n", sentBytes);
90+
Serial.printf("It took %d milliseconds to read %d bytes\n", pastTime, bytesReceived);
91+
Serial.print("Received data: [");
92+
Serial.write(dataReceived, DATA_SIZE);
93+
Serial.println("]");
94+
Serial.println("========================\nFinished!");
95+
}

0 commit comments

Comments
 (0)
Please sign in to comment.