feat(uart): simplifies UART example based on MODBUS standard (#11309)
* feat(uart): simplifies UART example based on MODBUS standard * fix(uart): fixes a uart example typo * feat(uart): replaces UART0 by Serial0 in the code * ci(pre-commit): Apply automatic fixes * fix(uart): typo error message in commentary --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
parent
16fcdeb0be
commit
d63b876f93
1 changed files with 56 additions and 73 deletions
|
|
@ -5,20 +5,20 @@
|
||||||
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false)
|
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false)
|
||||||
|
|
||||||
It is possible to register an UART callback function that will be called
|
It is possible to register an UART callback function that will be called
|
||||||
every time that UART receives data and an associated interrupt is generated.
|
every time that UART receives data and an associated UART interrupt is generated.
|
||||||
|
|
||||||
In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted
|
In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that
|
||||||
using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout().
|
can be adjusted using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout().
|
||||||
|
|
||||||
OnReceive will be called, while receiving a stream of data, when every 120 bytes are received (default FIFO Full),
|
In case that <onlyOnTimeout> is not changed or it is set to <false>, the callback function is
|
||||||
which may not help in case that the application needs to get all data at once before processing it.
|
executed whenever any event happens first (FIFO Full or RX Timeout).
|
||||||
Therefore, a way to make it work is by detecting the end of a stream transmission. This can be based on a protocol
|
OnReceive will be called when every 120 bytes are received(default FIFO Full),
|
||||||
or based on timeout with the UART line in idle (no data received - this is the case of this example).
|
or when RX Timeout occurs after 1 UART symbol by default.
|
||||||
|
|
||||||
In some cases, it is necessary to wait for receiving all the data before processing it and parsing the
|
This example demonstrates a way to create a String with all data received from UART0 only
|
||||||
UART input. This example demonstrates a way to create a String with all data received from UART0 and
|
after RX Timeout. This example uses an RX timeout of about 3.5 Symbols as a way to know
|
||||||
signaling it using a Mutex for another task to process it. This example uses a timeout of 500ms as a way to
|
when the reception of data has finished.
|
||||||
know when the reception of data has finished.
|
In order to achieve it, the sketch sets <onlyOnTimeout> to <true>.
|
||||||
|
|
||||||
The onReceive() callback is called whenever the RX ISR is triggered.
|
The onReceive() callback is called whenever the RX ISR is triggered.
|
||||||
It can occur because of two possible events:
|
It can occur because of two possible events:
|
||||||
|
|
@ -34,90 +34,73 @@
|
||||||
|
|
||||||
2- UART RX Timeout: it happens, based on a timeout equivalent to a number of symbols at
|
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.
|
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)
|
This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout).
|
||||||
|
<rxTimeout> is bound to the clock source.
|
||||||
|
In order to use it properly, ESP32 and ESP32-S2 shall set the UART Clock Source to APB.
|
||||||
|
|
||||||
When any of those two interrupts occur, IDF UART driver will copy FIFO data to its internal
|
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
|
RingBuffer and then Arduino can read such data. At the same time, Arduino Layer will execute
|
||||||
the callback function defined with HardwareSerial::onReceive().
|
the callback function defined with HardwareSerial::onReceive().
|
||||||
|
|
||||||
<bool onlyOnTimeout> parameter (default false) can be used by the application to tell Arduino to
|
<bool onlyOnTimeout> parameter can be used by the application to tell Arduino to only execute
|
||||||
only execute the callback when the second event above happens (Rx Timeout). At this time all
|
the callback when Rx Timeout happens, by setting it to <true>.
|
||||||
received data will be available to be read by the Arduino application. But if the number of
|
At this time all received data will be available to be read by the Arduino application.
|
||||||
received bytes is higher than the FIFO space, it will generate an error of FIFO overflow.
|
The application shall set an appropriate RX buffer size using
|
||||||
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.
|
HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port.
|
||||||
*/
|
|
||||||
|
|
||||||
// this will make UART0 work in any case (using or not USB)
|
MODBUS timeout of 3.5 symbol is based on these documents:
|
||||||
#if ARDUINO_USB_CDC_ON_BOOT
|
https://www.automation.com/en-us/articles/2012-1/introduction-to-modbus
|
||||||
#define UART0 Serial0
|
https://minimalmodbus.readthedocs.io/en/stable/serialcommunication.html
|
||||||
#else
|
*/
|
||||||
#define UART0 Serial
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// global variable to keep the results from onReceive()
|
// global variable to keep the results from onReceive()
|
||||||
String uart_buffer = "";
|
String uart_buffer = "";
|
||||||
// a pause of a half second in the UART transmission is considered the end of transmission.
|
// The Modbus RTU standard prescribes a silent period corresponding to 3.5 characters between each
|
||||||
const uint32_t communicationTimeout_ms = 500;
|
// message, to be able to figure out where one message ends and the next one starts.
|
||||||
|
const uint32_t modbusRxTimeoutLimit = 4;
|
||||||
|
const uint32_t baudrate = 19200;
|
||||||
|
|
||||||
// Create a mutex for the access to uart_buffer
|
// UART_RX_IRQ will be executed as soon as data is received by the UART and an RX Timeout occurs
|
||||||
// only one task can read/write it at a certain time
|
// This is a callback function executed from a high priority monitor task
|
||||||
SemaphoreHandle_t uart_buffer_Mutex = NULL;
|
// All data will be buffered into RX Buffer, which may have its size set to whatever necessary
|
||||||
|
|
||||||
// UART_RX_IRQ will be executed as soon as data is received by the UART
|
|
||||||
// This is a callback function executed from a high priority
|
|
||||||
// task created when onReceive() is used
|
|
||||||
void UART0_RX_CB() {
|
void UART0_RX_CB() {
|
||||||
// take the mutex, waits forever until loop() finishes its processing
|
while (Serial0.available()) {
|
||||||
if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) {
|
uart_buffer += (char)Serial0.read();
|
||||||
uint32_t now = millis(); // tracks timeout
|
|
||||||
while ((millis() - now) < communicationTimeout_ms) {
|
|
||||||
if (UART0.available()) {
|
|
||||||
uart_buffer += (char)UART0.read();
|
|
||||||
now = millis(); // reset the timer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// releases the mutex for data processing
|
|
||||||
xSemaphoreGive(uart_buffer_Mutex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup() and loop() are functions executed by a low priority task
|
// setup() and loop() are functions executed by a low priority task
|
||||||
// Therefore, there are 2 tasks running when using onReceive()
|
// Therefore, there are 2 tasks running when using onReceive()
|
||||||
void setup() {
|
void setup() {
|
||||||
UART0.begin(115200);
|
// Using Serial0 will work in any case (using or not USB CDC on Boot)
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
// creates a mutex object to control access to uart_buffer
|
// UART_CLK_SRC_APB will allow higher values of RX Timeout
|
||||||
uart_buffer_Mutex = xSemaphoreCreateMutex();
|
// default for ESP32 and ESP32-S2 is REF_TICK which limits the RX Timeout to 1
|
||||||
if (uart_buffer_Mutex == NULL) {
|
// setClockSource() must be called before begin()
|
||||||
log_e("Error creating Mutex. Sketch will fail.");
|
Serial0.setClockSource(UART_CLK_SRC_APB);
|
||||||
while (true) {
|
#endif
|
||||||
UART0.println("Mutex error (NULL). Program halted.");
|
// the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes
|
||||||
delay(2000);
|
Serial0.setRxBufferSize(1024); // default is 256 bytes
|
||||||
}
|
Serial0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit)
|
||||||
}
|
// set RX Timeout based on UART symbols ~ 3.5 symbols of 11 bits (MODBUS standard) ~= 2 ms at 19200
|
||||||
|
Serial0.setRxTimeout(modbusRxTimeoutLimit); // 4 symbols at 19200 8N1 is about 2.08 ms (40 bits)
|
||||||
UART0.onReceive(UART0_RX_CB); // sets the callback function
|
// sets the callback function that will be executed only after RX Timeout
|
||||||
UART0.println("Send data to UART0 in order to activate the RX callback");
|
Serial0.onReceive(UART0_RX_CB, true);
|
||||||
|
Serial0.println("Send data using Serial Monitor in order to activate the RX callback");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t counter = 0;
|
uint32_t counter = 0;
|
||||||
void loop() {
|
void loop() {
|
||||||
|
// String <uart_buffer> is filled by the UART Callback whenever data is received and RX Timeout occurs
|
||||||
if (uart_buffer.length() > 0) {
|
if (uart_buffer.length() > 0) {
|
||||||
// signals that the onReceive function shall not change uart_buffer while processing
|
// process the received data from Serial - example, just print it beside a counter
|
||||||
if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) {
|
Serial0.print("[");
|
||||||
// process the received data from UART0 - example, just print it beside a counter
|
Serial0.print(counter++);
|
||||||
UART0.print("[");
|
Serial0.print("] [");
|
||||||
UART0.print(counter++);
|
Serial0.print(uart_buffer.length());
|
||||||
UART0.print("] [");
|
Serial0.print(" bytes] ");
|
||||||
UART0.print(uart_buffer.length());
|
Serial0.println(uart_buffer);
|
||||||
UART0.print(" bytes] ");
|
uart_buffer = ""; // reset uart_buffer for the next UART reading
|
||||||
UART0.println(uart_buffer);
|
|
||||||
uart_buffer = ""; // reset uart_buffer for the next UART reading
|
|
||||||
// releases the mutex for more data to be received
|
|
||||||
xSemaphoreGive(uart_buffer_Mutex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
UART0.println("Sleeping for 1 second...");
|
delay(1);
|
||||||
delay(1000);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue