Skip to content

Fixes inconsistencies and adds extended HardwareSerial examples #7412

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1c6894b
adds extended HardwareSerial examples
SuGlider Oct 31, 2022
9f63cec
Adds new example with Serial RxTimeout
SuGlider Nov 1, 2022
8e31ada
adds and improves Serial onReceive expamples
SuGlider Nov 20, 2022
d5ffa5c
Merge branch 'master' into uart-examples
SuGlider Nov 20, 2022
34d54a2
adjust includes CMake - UART example
SuGlider Nov 20, 2022
d8a6c61
adjust includes CMake - UART example
SuGlider Nov 20, 2022
1855c96
fixes CMake and CI
SuGlider Nov 20, 2022
0b64752
adds ESP/Serial to CMakeList
SuGlider Nov 20, 2022
6d51f10
adds ESP/Serial to CMakeList
SuGlider Nov 20, 2022
06f165f
fixes demo include
SuGlider Nov 20, 2022
71cb33b
fixes BREAK demo
SuGlider Nov 20, 2022
653fa25
fixes onReceive demo
SuGlider Nov 20, 2022
53bddf6
Merge branch 'master' into uart-examples
SuGlider Dec 7, 2022
0dbcf87
Changes FIFO Full criteria
SuGlider Dec 7, 2022
0b05c49
Merge branch 'master' into uart-examples
SuGlider Dec 8, 2022
905b36c
Merge branch 'master' into uart-examples
SuGlider Dec 8, 2022
07ae299
Merge branch 'master' into uart-examples
SuGlider Dec 13, 2022
1f70a3f
example code replacement
SuGlider Dec 13, 2022
87db022
Merge branch 'master' into uart-examples
SuGlider Dec 14, 2022
2efe918
Merge branch 'master' into uart-examples
SuGlider Dec 14, 2022
7bf77bc
Merge branch 'master' into uart-examples
SuGlider Dec 15, 2022
d0601f9
Merge branch 'master' into uart-examples
SuGlider Dec 16, 2022
c91aff8
Merge branch 'master' into uart-examples
SuGlider Dec 19, 2022
1c1765a
Merge branch 'espressif:master' into uart-examples
SuGlider Dec 21, 2022
c745961
replaces functions in hal
SuGlider Dec 21, 2022
bcb6582
Merge branch 'master' into uart-examples
SuGlider Dec 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 45 additions & 7 deletions cores/esp32/HardwareSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}

Expand Down Expand Up @@ -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
}
}
}
Expand Down Expand Up @@ -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();
}

Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion cores/esp32/HardwareSerial.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
53 changes: 53 additions & 0 deletions cores/esp32/esp32-hal-uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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, &currentBaudrate);
// 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);
}
16 changes: 16 additions & 0 deletions cores/esp32/esp32-hal-uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Loading