Skip to content

Commit 5dff15c

Browse files
authored
Fixes inconsistencies and adds extended HardwareSerial examples (espressif#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+
}

0 commit comments

Comments
 (0)