|
| 1 | +/* |
| 2 | +
|
| 3 | + This Sketch demonstrates how to use onReceive(callbackFunc) with HardwareSerial |
| 4 | +
|
| 5 | + void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false) |
| 6 | +
|
| 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. |
| 9 | +
|
| 10 | + In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted |
| 11 | + using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout(). |
| 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). |
| 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. |
| 22 | +
|
| 23 | + The onReceive() callback is called whenever the RX ISR is triggered. |
| 24 | + It can occur because of two possible events: |
| 25 | +
|
| 26 | + 1- UART FIFO FULL: it happens when internal UART FIFO reaches a certain number of bytes. |
| 27 | + Its full capacity is 127 bytes. The FIFO Full threshold for the interrupt can be changed |
| 28 | + using HardwareSerial::setRxFIFOFull(uint8_t fifoFull). |
| 29 | + Default FIFO Full Threshold is set in the UART initialization using HardwareSerial::begin() |
| 30 | + This will depend on the baud rate used when begin() is executed. |
| 31 | + For a baud rate of 115200 or lower, it it just 1 byte, mimicking original Arduino UART driver. |
| 32 | + For a baud rate over 115200 it will be 120 bytes for higher performance. |
| 33 | + Anyway, it can be changed by the application at any time. |
| 34 | +
|
| 35 | + 2- UART RX Timeout: it happens, based on a timeout equivalent to a number of symbols at |
| 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) |
| 38 | +
|
| 39 | + When any of those two interrupts occur, IDF UART driver will copy FIFO data to its internal |
| 40 | + RingBuffer and then Arduino can read such data. At the same time, Arduino Layer will execute |
| 41 | + the callback function defined with HardwareSerial::onReceive(). |
| 42 | +
|
| 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 |
| 48 | + HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port. |
| 49 | +*/ |
| 50 | + |
| 51 | + |
| 52 | +// this will make UART0 work in any case (using or not USB) |
| 53 | +#if ARDUINO_USB_CDC_ON_BOOT |
| 54 | +#define UART0 Serial0 |
| 55 | +#else |
| 56 | +#define UART0 Serial |
| 57 | +#endif |
| 58 | + |
| 59 | +// global variable to keep the results from onReceive() |
| 60 | +String uart_buffer = ""; |
| 61 | +// a pause of a half second in the UART transmission is considered the end of transmission. |
| 62 | +const uint32_t communicationTimeout_ms = 500; |
| 63 | + |
| 64 | +// Create a mutex for the access to uart_buffer |
| 65 | +// only one task can read/write it at a certain time |
| 66 | +SemaphoreHandle_t uart_buffer_Mutex = NULL; |
| 67 | + |
| 68 | +// UART_RX_IRQ will be executed as soon as data is received by the UART |
| 69 | +// This is a callback function executed from a high priority |
| 70 | +// task created when onReceive() is used |
| 71 | +void UART0_RX_CB() { |
| 72 | + // take the mutex, waits forever until loop() finishes its processing |
| 73 | + if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { |
| 74 | + uint32_t now = millis(); // tracks timeout |
| 75 | + while ((millis() - now) < communicationTimeout_ms) { |
| 76 | + if (UART0.available()) { |
| 77 | + uart_buffer += (char) UART0.read(); |
| 78 | + now = millis(); // reset the timer |
| 79 | + } |
| 80 | + } |
| 81 | + // releases the mutex for data processing |
| 82 | + xSemaphoreGive(uart_buffer_Mutex); |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +// setup() and loop() are functions executed by a low priority task |
| 87 | +// Therefore, there are 2 tasks running when using onReceive() |
| 88 | +void setup() { |
| 89 | + UART0.begin(115200); |
| 90 | + |
| 91 | + // creates a mutex object to control access to uart_buffer |
| 92 | + uart_buffer_Mutex = xSemaphoreCreateMutex(); |
| 93 | + if(uart_buffer_Mutex == NULL) { |
| 94 | + log_e("Error creating Mutex. Sketch will fail."); |
| 95 | + while(true) { |
| 96 | + UART0.println("Mutex error (NULL). Program halted."); |
| 97 | + delay(2000); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + UART0.onReceive(UART0_RX_CB); // sets the callback function |
| 102 | + UART0.println("Send data to UART0 in order to activate the RX callback"); |
| 103 | +} |
| 104 | + |
| 105 | +uint32_t counter = 0; |
| 106 | +void loop() { |
| 107 | + if (uart_buffer.length() > 0) { |
| 108 | + // signals that the onReceive function shall not change uart_buffer while processing |
| 109 | + if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { |
| 110 | + // process the received data from UART0 - example, just print it beside a counter |
| 111 | + UART0.print("["); |
| 112 | + UART0.print(counter++); |
| 113 | + UART0.print("] ["); |
| 114 | + UART0.print(uart_buffer.length()); |
| 115 | + UART0.print(" bytes] "); |
| 116 | + UART0.println(uart_buffer); |
| 117 | + uart_buffer = ""; // reset uart_buffer for the next UART reading |
| 118 | + // releases the mutex for more data to be received |
| 119 | + xSemaphoreGive(uart_buffer_Mutex); |
| 120 | + } |
| 121 | + } |
| 122 | + UART0.println("Sleeping for 1 second..."); |
| 123 | + delay(1000); |
| 124 | +} |
0 commit comments