|
5 | 5 | void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false)
|
6 | 6 |
|
7 | 7 | It is possible to register an UART callback function that will be called
|
8 |
| - every time that UART receives data and an associated interrupt is generated. |
| 8 | + every time that UART receives data and an associated UART interrupt is generated. |
9 | 9 |
|
10 |
| - In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted |
11 |
| - using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout(). |
| 10 | + In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that |
| 11 | + can be adjusted using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout(). |
12 | 12 |
|
13 |
| - OnReceive will be called, while receiving a stream of data, when every 120 bytes are received (default FIFO Full), |
14 |
| - which may not help in case that the application needs to get all data at once before processing it. |
15 |
| - Therefore, a way to make it work is by detecting the end of a stream transmission. This can be based on a protocol |
16 |
| - or based on timeout with the UART line in idle (no data received - this is the case of this example). |
| 13 | + In case that <onlyOnTimeout> is not changed or it is set to <false>, the callback function is |
| 14 | + executed whenever any event happens first (FIFO Full or RX Timeout). |
| 15 | + OnReceive will be called when every 120 bytes are received(default FIFO Full), |
| 16 | + or when RX Timeout occurs after 1 UART symbol by default. |
17 | 17 |
|
18 |
| - In some cases, it is necessary to wait for receiving all the data before processing it and parsing the |
19 |
| - UART input. This example demonstrates a way to create a String with all data received from UART0 and |
20 |
| - signaling it using a Mutex for another task to process it. This example uses a timeout of 500ms as a way to |
21 |
| - know when the reception of data has finished. |
| 18 | + This example demonstrates a way to create a String with all data received from UART0 only |
| 19 | + after RX Timeout. This example uses an RX timeout of about 3.5 Symbols as a way to know |
| 20 | + when the reception of data has finished. |
| 21 | + In order to achieve it, the sketch sets <onlyOnTimeout> to <true>. |
22 | 22 |
|
23 | 23 | The onReceive() callback is called whenever the RX ISR is triggered.
|
24 | 24 | It can occur because of two possible events:
|
|
34 | 34 |
|
35 | 35 | 2- UART RX Timeout: it happens, based on a timeout equivalent to a number of symbols at
|
36 | 36 | the current baud rate. If the UART line is idle for this timeout, it will raise an interrupt.
|
37 |
| - This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout) |
| 37 | + This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout). |
| 38 | + <rxTimeout> is bound to the clock source. |
| 39 | + In order to use it properly, ESP32 and ESP32-S2 shall set the UART Clock Source to APB. |
38 | 40 |
|
39 | 41 | When any of those two interrupts occur, IDF UART driver will copy FIFO data to its internal
|
40 | 42 | RingBuffer and then Arduino can read such data. At the same time, Arduino Layer will execute
|
41 | 43 | the callback function defined with HardwareSerial::onReceive().
|
42 | 44 |
|
43 |
| - <bool onlyOnTimeout> parameter (default false) can be used by the application to tell Arduino to |
44 |
| - only execute the callback when the second event above happens (Rx Timeout). At this time all |
45 |
| - received data will be available to be read by the Arduino application. But if the number of |
46 |
| - received bytes is higher than the FIFO space, it will generate an error of FIFO overflow. |
47 |
| - In order to avoid such problem, the application shall set an appropriate RX buffer size using |
| 45 | + <bool onlyOnTimeout> parameter can be used by the application to tell Arduino to only execute |
| 46 | + the callback when Rx Timeout happens, by setting it to <true>. |
| 47 | + At this time all received data will be available to be read by the Arduino application. |
| 48 | + The application shall set an appropriate RX buffer size using |
48 | 49 | HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port.
|
49 |
| -*/ |
50 | 50 |
|
51 |
| -// this will make UART0 work in any case (using or not USB) |
52 |
| -#if ARDUINO_USB_CDC_ON_BOOT |
53 |
| -#define UART0 Serial0 |
54 |
| -#else |
55 |
| -#define UART0 Serial |
56 |
| -#endif |
| 51 | + MODBUS timeout of 3.5 symbol is based on these documents: |
| 52 | + https://www.automation.com/en-us/articles/2012-1/introduction-to-modbus |
| 53 | + https://minimalmodbus.readthedocs.io/en/stable/serialcommunication.html |
| 54 | +*/ |
57 | 55 |
|
58 | 56 | // global variable to keep the results from onReceive()
|
59 | 57 | String uart_buffer = "";
|
60 |
| -// a pause of a half second in the UART transmission is considered the end of transmission. |
61 |
| -const uint32_t communicationTimeout_ms = 500; |
62 |
| - |
63 |
| -// Create a mutex for the access to uart_buffer |
64 |
| -// only one task can read/write it at a certain time |
65 |
| -SemaphoreHandle_t uart_buffer_Mutex = NULL; |
66 |
| - |
67 |
| -// UART_RX_IRQ will be executed as soon as data is received by the UART |
68 |
| -// This is a callback function executed from a high priority |
69 |
| -// task created when onReceive() is used |
| 58 | +// The Modbus RTU standard prescribes a silent period corresponding to 3.5 characters between each |
| 59 | +// message, to be able to figure out where one message ends and the next one starts. |
| 60 | +const uint32_t modbusRxTimeoutLimit = 4; |
| 61 | +const uint32_t baudrate = 19200; |
| 62 | + |
| 63 | +// UART_RX_IRQ will be executed as soon as data is received by the UART and an RX Timeout occurs |
| 64 | +// This is a callback function executed from a high priority monitor task |
| 65 | +// All data will be buffered into RX Buffer, which may have its size set to whatever necessary |
70 | 66 | void UART0_RX_CB() {
|
71 |
| - // take the mutex, waits forever until loop() finishes its processing |
72 |
| - if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { |
73 |
| - uint32_t now = millis(); // tracks timeout |
74 |
| - while ((millis() - now) < communicationTimeout_ms) { |
75 |
| - if (UART0.available()) { |
76 |
| - uart_buffer += (char)UART0.read(); |
77 |
| - now = millis(); // reset the timer |
78 |
| - } |
79 |
| - } |
80 |
| - // releases the mutex for data processing |
81 |
| - xSemaphoreGive(uart_buffer_Mutex); |
| 67 | + while (Serial0.available()) { |
| 68 | + uart_buffer += (char)Serial0.read(); |
82 | 69 | }
|
83 | 70 | }
|
84 | 71 |
|
85 | 72 | // setup() and loop() are functions executed by a low priority task
|
86 | 73 | // Therefore, there are 2 tasks running when using onReceive()
|
87 | 74 | void setup() {
|
88 |
| - UART0.begin(115200); |
89 |
| - |
90 |
| - // creates a mutex object to control access to uart_buffer |
91 |
| - uart_buffer_Mutex = xSemaphoreCreateMutex(); |
92 |
| - if (uart_buffer_Mutex == NULL) { |
93 |
| - log_e("Error creating Mutex. Sketch will fail."); |
94 |
| - while (true) { |
95 |
| - UART0.println("Mutex error (NULL). Program halted."); |
96 |
| - delay(2000); |
97 |
| - } |
98 |
| - } |
99 |
| - |
100 |
| - UART0.onReceive(UART0_RX_CB); // sets the callback function |
101 |
| - UART0.println("Send data to UART0 in order to activate the RX callback"); |
| 75 | + // Using Serial0 will work in any case (using or not USB CDC on Boot) |
| 76 | +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 |
| 77 | + // UART_CLK_SRC_APB will allow higher values of RX Timeout |
| 78 | + // default for ESP32 and ESP32-S2 is REF_TICK which limits the RX Timeout to 1 |
| 79 | + // setClockSource() must be called before begin() |
| 80 | + Serial0.setClockSource(UART_CLK_SRC_APB); |
| 81 | +#endif |
| 82 | + // the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes |
| 83 | + Serial0.setRxBufferSize(1024); // default is 256 bytes |
| 84 | + Serial0.begin(baudrate); // default pins and default mode 8N1 (8 bits data, no parity bit, 1 stopbit) |
| 85 | + // set RX Timeout based on UART symbols ~ 3.5 symbols of 11 bits (MODBUS standard) ~= 2 ms at 19200 |
| 86 | + Serial0.setRxTimeout(modbusRxTimeoutLimit); // 4 symbols at 19200 8N1 is about 2.08 ms (40 bits) |
| 87 | + // sets the callback function that will be executed only after RX Timeout |
| 88 | + Serial0.onReceive(UART0_RX_CB, true); |
| 89 | + Serial0.println("Send data using Serial Monitor in order to activate the RX callback"); |
102 | 90 | }
|
103 | 91 |
|
104 | 92 | uint32_t counter = 0;
|
105 | 93 | void loop() {
|
| 94 | + // String <uart_buffer> is filled by the UART Callback whenever data is received and RX Timeout occurs |
106 | 95 | if (uart_buffer.length() > 0) {
|
107 |
| - // signals that the onReceive function shall not change uart_buffer while processing |
108 |
| - if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { |
109 |
| - // process the received data from UART0 - example, just print it beside a counter |
110 |
| - UART0.print("["); |
111 |
| - UART0.print(counter++); |
112 |
| - UART0.print("] ["); |
113 |
| - UART0.print(uart_buffer.length()); |
114 |
| - UART0.print(" bytes] "); |
115 |
| - UART0.println(uart_buffer); |
116 |
| - uart_buffer = ""; // reset uart_buffer for the next UART reading |
117 |
| - // releases the mutex for more data to be received |
118 |
| - xSemaphoreGive(uart_buffer_Mutex); |
119 |
| - } |
| 96 | + // process the received data from Serial - example, just print it beside a counter |
| 97 | + Serial0.print("["); |
| 98 | + Serial0.print(counter++); |
| 99 | + Serial0.print("] ["); |
| 100 | + Serial0.print(uart_buffer.length()); |
| 101 | + Serial0.print(" bytes] "); |
| 102 | + Serial0.println(uart_buffer); |
| 103 | + uart_buffer = ""; // reset uart_buffer for the next UART reading |
120 | 104 | }
|
121 |
| - UART0.println("Sleeping for 1 second..."); |
122 |
| - delay(1000); |
| 105 | + delay(1); |
123 | 106 | }
|
0 commit comments