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
This commit is contained in:
parent
a95ce27ad7
commit
5dff15ce9d
8 changed files with 609 additions and 8 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ protected:
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <Arduino.h>
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -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().
|
||||
|
||||
<bool onlyOnTimeout> 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 <Arduino.h>
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -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 <Arduino.h>
|
||||
|
||||
// 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!");
|
||||
}
|
||||
|
||||
|
|
@ -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 <Arduino.h>
|
||||
|
||||
// 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!");
|
||||
}
|
||||
Loading…
Reference in a new issue