This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
@@ -0,0 +1,36 @@
/*
This Sketch demonstrates how to detect and set the baud rate when the UART0 is connected to
some port that is sending data. It can be used with the Arduino IDE Serial Monitor to send the data.
Serial.begin(0) will start the baud rate detection. Valid range is 300 to 230400 baud.
It will try to detect for 20 seconds, by default, while reading RX.
This timeout of 20 seconds can be changed in the begin() function through <<timeout_ms>> parameter:
void HardwareSerial::begin(baud, config, rxPin, txPin, invert, <<timeout_ms>>, rxfifo_full_thrhd)
It is necessary that the other end sends some data within <<timeout_ms>>, otherwise the detection won't work.
IMPORTANT NOTE: baud rate detection seem to only work with ESP32 and ESP32-S2.
In other other SoCs, it doesn't work.
*/
// Open the Serial Monitor with testing baud start typing and sending characters
void setup() {
Serial.begin(0); // it will try to detect the baud rate for 20 seconds
Serial.print("\n==>The baud rate is ");
Serial.println(Serial.baudRate());
//after 20 seconds timeout, when not detected, it will return zero - in this case, we set it back to 115200.
if (Serial.baudRate() == 0) {
// Trying to set Serial to a safe state at 115200
Serial.end();
Serial.begin(115200);
Serial.setDebugOutput(true);
delay(1000);
log_e("Baud rate detection failed.");
}
}
void loop() {}
@@ -0,0 +1,265 @@
/*
Hardware Flow Control Demo for ESP32
This sketch demonstrates UART hardware flow control using RTS (Request To Send)
and CTS (Clear To Send) signals with UART1 (HardwareSerial Serial1).
CONFIGURATION:
==============
Set USE_INTERNAL_MATRIX_PIN_LOOPBACK to 1 for internal GPIO matrix connections
(no external wires needed). Set to 0 to use external wire connections.
PIN CONNECTIONS (when USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0):
============================================================
For basic loopback with hardware flow control:
- Connect GPIO2 (RTS1) to GPIO4 (CTS1) - Flow control loopback
- Connect TX1 pin to RX1 pin - Data loopback
For GPIO-controlled flow control demonstration:
- Connect TX1 pin to RX1 pin - Data loopback
- Connect GPIO2 (RTS1) to GPIO5 (GPIO_RTS_MONITOR) - Monitor RTS state
- Connect GPIO4 (CTS1) to GPIO13 (GPIO_CTS_CTRL) - Control CTS signal
- Use GPIO13 to manually control CTS signal (LOW = allow, HIGH = block)
HARDWARE FLOW CONTROL EXPLANATION:
===================================
RTS (Request To Send):
- Output signal from UART (GPIO2 in this example)
- Asserted LOW when UART is ready to receive data (RX buffer has space)
- De-asserted HIGH when RX buffer is getting full (threshold reached)
CTS (Clear To Send):
- Input signal to UART (GPIO4 in this example)
- UART will only transmit when CTS is LOW (asserted)
- UART will pause transmission when CTS is HIGH (de-asserted)
OPERATION:
==========
The sketch demonstrates:
- Periodic transmission of messages every second
- Automatic flow control when USE_GPIO_CONTROL = false
- Manual CTS control when USE_GPIO_CONTROL = true
- Loopback reception of transmitted data
- Status monitoring of RTS/CTS pin states
NOTE: When USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1, no external connections
are needed as the ESP32 GPIO matrix handles the loopback internally.
*/
// setting it to 1 will allow internal matrix pin connection for RX1<->TX1 and RTS1<->CTS1
// otherwise it needs a wire for cross connecting the pins
#define USE_INTERNAL_MATRIX_PIN_LOOPBACK 1
// Pin definitions for UART1
#define UART1_RX_PIN RX1 // Default GPIO - UART1 RX pin
#define UART1_TX_PIN TX1 // Default GPIO - UART1 TX pin
#define UART1_RTS_PIN 2 // GPIO2 - UART1 RTS pin (output from UART)
#define UART1_CTS_PIN 4 // GPIO4 - UART1 CTS pin (input to UART)
// Optional: GPIO pins for manual flow control demonstration
// If using GPIO-controlled flow control, connect:
// - RTS1 to GPIO_RTS_MONITOR (to monitor RTS state)
// - CTS1 to GPIO_CTS_CTRL (to control CTS signal)
#define GPIO_RTS_MONITOR 5 // GPIO5 - Monitor RTS signal (connect RTS1 to this)
#define GPIO_CTS_CTRL 13 // GPIO13 - Control CTS signal (connect CTS1 to this)
// Set to true to use GPIO-controlled flow control, false for simple loopback
// Note: this control will be overridden by USE_INTERNAL_MATRIX_PIN_LOOPBACK when it is 1
#define USE_GPIO_CONTROL false
// Variables for demonstration
unsigned long lastSendTime = 0;
unsigned long lastStatusTime = 0;
const unsigned long sendInterval = 1000; // Send data every 1 second
const unsigned long statusInterval = 2000; // Print status every 2 seconds
int sendCounter = 0;
void printPinStatus() {
Serial.println("\n=== UART1 Pin Status ===");
Serial.printf("RX Pin (GPIO%d): Receiving data\n", UART1_RX_PIN);
Serial.printf("TX Pin (GPIO%d): Transmitting data\n", UART1_TX_PIN);
if (USE_GPIO_CONTROL) {
// Read RTS state from monitor GPIO (connected to RTS1)
bool rtsState = digitalRead(GPIO_RTS_MONITOR);
Serial.printf("RTS Pin (GPIO%d): %s (LOW = ready to receive)\n", UART1_RTS_PIN, rtsState == LOW ? "LOW (Ready)" : "HIGH (Busy)");
// Read CTS state from control GPIO (connected to CTS1)
bool ctsState = digitalRead(GPIO_CTS_CTRL);
Serial.printf("CTS Pin (GPIO%d): %s (LOW = can transmit)\n", UART1_CTS_PIN, ctsState == LOW ? "LOW (Clear)" : "HIGH (Blocked)");
} else {
Serial.printf("RTS Pin (GPIO%d): Hardware controlled (LOW = ready to receive)\n", UART1_RTS_PIN);
Serial.printf("CTS Pin (GPIO%d): Hardware controlled (LOW = can transmit)\n", UART1_CTS_PIN);
Serial.println("Note: RTS/CTS pins are hardware-controlled. Connect RTS1 to CTS1 for loopback.");
}
Serial.printf("Available for write: %d bytes\n", Serial1.availableForWrite());
Serial.printf("Available to read: %d bytes\n", Serial1.available());
Serial.println("========================\n");
}
void setup() {
// Initialize Serial (USB) for debugging
Serial.begin(115200);
delay(1000);
Serial.println("\n\n========================================");
Serial.println("ESP32 Hardware Flow Control Demo");
Serial.println("========================================\n");
// Configure GPIOs for flow control (only if using GPIO-controlled mode)
if (USE_GPIO_CONTROL) {
// Configure CTS control GPIO - this will control the CTS signal
pinMode(GPIO_CTS_CTRL, OUTPUT);
digitalWrite(GPIO_CTS_CTRL, LOW); // Start with CTS LOW (clear to send)
// Configure RTS monitor GPIO - this will monitor the RTS signal
pinMode(GPIO_RTS_MONITOR, INPUT);
Serial.println("Using GPIO-controlled flow control mode");
} else {
Serial.println("Using hardware-controlled flow control (simple loopback)");
}
// Initialize UART1 with hardware flow control
Serial.println("Initializing UART1...");
// Begin UART1 with 115200 baud, 8N1 configuration
Serial1.begin(115200);
// Set all pins for UART1
if (!Serial1.setPins(UART1_RX_PIN, UART1_TX_PIN, UART1_CTS_PIN, UART1_RTS_PIN)) {
Serial.println("ERROR: Failed to set CTS and RTS UART1 pins!");
while (1) {
delay(1000);
}
}
Serial.println("Enabling hardware flow control...");
if (!Serial1.setHwFlowCtrlMode(UART_HW_FLOWCTRL_CTS_RTS, 64)) {
Serial.println("ERROR: Failed to enable hardware flow control!");
while (1) {
delay(1000);
}
}
#if USE_INTERNAL_MATRIX_PIN_LOOPBACK
uart_internal_loopback(1, UART1_RX_PIN);
uart_internal_hw_flow_ctrl_loopback(1, UART1_CTS_PIN);
#endif
// Diagnostic: Check initial state after enabling flow control
Serial.println("\nPost-initialization diagnostics:");
Serial.printf(" Serial1.available(): %d bytes\n", Serial1.available());
Serial.printf(" Serial1.availableForWrite(): %d bytes\n", Serial1.availableForWrite());
if (USE_GPIO_CONTROL) {
Serial.printf(" GPIO%d (CTS control): %s\n", GPIO_CTS_CTRL, digitalRead(GPIO_CTS_CTRL) == LOW ? "LOW" : "HIGH");
Serial.printf(" GPIO%d (RTS monitor): %s\n", GPIO_RTS_MONITOR, digitalRead(GPIO_RTS_MONITOR) == LOW ? "LOW" : "HIGH");
}
Serial.println();
Serial.println("UART1 initialized successfully!");
Serial.println("Hardware flow control: ENABLED (RTS + CTS)");
Serial.printf("RX Pin: GPIO%d\n", UART1_RX_PIN);
Serial.printf("TX Pin: GPIO%d\n", UART1_TX_PIN);
Serial.printf("RTS Pin: GPIO%d (output from UART)\n", UART1_RTS_PIN);
Serial.printf("CTS Pin: GPIO%d (input to UART)\n", UART1_CTS_PIN);
#if USE_INTERNAL_MATRIX_PIN_LOOPBACK
Serial.println("\nNO EXTERNAL PIN CONNECTIONS ARE REQUIRED:");
Serial.println("-------------------------");
Serial.println("Internal GPIO Matrix connection with flow control mode:");
Serial.printf(" 1. Automatic Internal Connection of GPIO%d (TX1) to GPIO%d (RX1) - Loopback\n", UART1_TX_PIN, UART1_RX_PIN);
Serial.printf(" 2. Automatic Internal Connection of GPIO%d (RTS1) to GPIO%d (CTS1) - Flow control loopback\n", UART1_RTS_PIN, UART1_CTS_PIN);
Serial.println("\n Note: In this mode, RTS/CTS are automatically controlled by hardware.");
Serial.println(" RTS goes LOW when ready to receive, HIGH when buffer is full.");
Serial.println(" CTS must be LOW for transmission to proceed.");
#else
Serial.println("\nPIN CONNECTIONS REQUIRED:");
Serial.println("-------------------------");
if (USE_GPIO_CONTROL) {
Serial.println("GPIO-controlled flow control mode:");
Serial.printf(" 1. Connect GPIO%d (TX1) to GPIO%d (RX1) - Loopback\n", UART1_TX_PIN, UART1_RX_PIN);
Serial.printf(" 2. Connect GPIO%d (RTS1) to GPIO%d - Monitor RTS state\n", UART1_RTS_PIN, GPIO_RTS_MONITOR);
Serial.printf(" 3. Connect GPIO%d (CTS1) to GPIO%d - Control CTS signal\n", UART1_CTS_PIN, GPIO_CTS_CTRL);
} else {
Serial.println("Hardware-controlled flow control (simple loopback):");
Serial.printf(" 1. Connect GPIO%d (TX1) to GPIO%d (RX1) - Loopback\n", UART1_TX_PIN, UART1_RX_PIN);
Serial.printf(" 2. Connect GPIO%d (RTS1) to GPIO%d (CTS1) - Flow control loopback\n", UART1_RTS_PIN, UART1_CTS_PIN);
Serial.println("\n Note: In this mode, RTS/CTS are automatically controlled by hardware.");
Serial.println(" RTS goes LOW when ready to receive, HIGH when buffer is full.");
Serial.println(" CTS must be LOW for transmission to proceed.");
}
#endif
Serial.println("\nStarting demonstration in 2 seconds...\n");
delay(2000);
}
void loop() {
unsigned long currentTime = millis();
// Print status periodically
if (currentTime - lastStatusTime >= statusInterval) {
lastStatusTime = currentTime;
printPinStatus();
// Demonstrate flow control by toggling CTS (only in GPIO-controlled mode)
if (USE_GPIO_CONTROL) {
static bool ctsState = false;
ctsState = !ctsState;
if (ctsState) {
Serial.println(">>> Blocking transmission (CTS HIGH)...");
digitalWrite(GPIO_CTS_CTRL, HIGH); // Block transmission
} else {
Serial.println(">>> Allowing transmission (CTS LOW)...");
digitalWrite(GPIO_CTS_CTRL, LOW); // Allow transmission
}
}
}
// Send data periodically
if (currentTime - lastSendTime >= sendInterval) {
lastSendTime = currentTime;
sendCounter++;
// Check if we can transmit (CTS must be LOW)
// In GPIO-controlled mode, check the control GPIO; otherwise hardware handles it
bool canTransmit = true;
if (USE_GPIO_CONTROL) {
canTransmit = (digitalRead(GPIO_CTS_CTRL) == LOW);
}
if (canTransmit) {
char message[64];
snprintf(message, sizeof(message), "Message #%d: Hello from UART1! Time: %lu ms\r\n", sendCounter, currentTime);
Serial.print("Sending: ");
Serial.print(message);
size_t bytesWritten = Serial1.write((const uint8_t *)message, strlen(message));
Serial.printf(" -> Written: %d bytes\n", bytesWritten);
// Flush to ensure data is sent
Serial1.flush();
} else {
Serial.println("!!! Transmission blocked - CTS is HIGH !!!");
}
}
// Read and echo received data
if (Serial1.available()) {
Serial.print("Received: ");
while (Serial1.available()) {
char c = Serial1.read();
Serial.write(c);
}
Serial.println();
}
// Small delay to prevent tight loop
delay(10);
}
@@ -0,0 +1,366 @@
# Hardware Flow Control Demo
This example demonstrates UART hardware flow control using RTS (Request To Send) and CTS (Clear To Send) signals with ESP32's HardwareSerial (UART1).
## Overview
Hardware flow control is a mechanism that prevents data loss by controlling when data can be transmitted and received. It uses two additional signals:
- **RTS (Request To Send)**: Output signal from the UART indicating it's ready to receive data
- **CTS (Clear To Send)**: Input signal to the UART that controls when transmission is allowed
## Configuration Options
The sketch supports two configuration options:
### USE_INTERNAL_MATRIX_PIN_LOOPBACK
**Location in code:** Line 55 in `HardwareFlowControl_Demo.ino`
```cpp
#define USE_INTERNAL_MATRIX_PIN_LOOPBACK 1 // Set to 1 for internal loopback, 0 for external wires
```
- **`USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`** (Default): Uses ESP32's internal GPIO matrix to create loopback connections. **No external wires needed!**
- **`USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`**: Requires external wire connections (see Pin Connections section below)
### USE_GPIO_CONTROL
**Location in code:** Line 72 in `HardwareFlowControl_Demo.ino`
```cpp
#define USE_GPIO_CONTROL false // Set to true or false
```
**Note:** When `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`, the `USE_GPIO_CONTROL` setting is overridden and hardware-controlled mode is used.
## Pin Connections
**Important:** Pin connections are only needed when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`.
### Default Pin Assignments
- **RX1**: Uses board default (`RX1` constant - typically GPIO26 for ESP32, varies by board)
- **TX1**: Uses board default (`TX1` constant - typically GPIO27 for ESP32, varies by board)
- **RTS1**: GPIO2 (configurable via `UART1_RTS_PIN`)
- **CTS1**: GPIO4 (configurable via `UART1_CTS_PIN`)
- **GPIO_RTS_MONITOR**: GPIO5 (for GPIO-controlled mode, configurable via `GPIO_RTS_MONITOR`)
- **GPIO_CTS_CTRL**: GPIO13 (for GPIO-controlled mode, configurable via `GPIO_CTS_CTRL`)
**Note:** RX1 and TX1 pin numbers are board-specific. Check your board's pin definitions or use the serial output to see the actual GPIO numbers being used.
### Option 1: Simple Loopback (`USE_GPIO_CONTROL = false`)
For a basic loopback test with automatic flow control:
```
ESP32 Pin Connections:
- TX1 ────┐
├──> RX1 (Data loopback)
- GPIO2 (RTS1) ────┐
├──> GPIO4 (CTS1) (Flow control loopback)
```
**Physical Connections (when USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0):**
1. Connect TX1 pin to RX1 pin with a jumper wire - read the console serial output to know which pins are the default ones
2. Connect GPIO2 (RTS1) to GPIO4 (CTS1) with a jumper wire
### Option 2: GPIO-Controlled Flow Control (`USE_GPIO_CONTROL = true`)
For manual control of flow control signals:
```
ESP32 Pin Connections:
- TX1 ────> RX1 (Data loopback)
- GPIO2 (RTS1) ────> GPIO5 (RTS Monitor) (Monitor RTS state)
- GPIO4 (CTS1) <─── GPIO13 (CTS Control) (Control CTS signal)
```
**Physical Connections (when USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0):**
1. Connect TX1 pin to RX1 pin with a jumper wire
2. Connect GPIO2 (RTS1) to GPIO5 with a jumper wire
3. Connect GPIO4 (CTS1) to GPIO13 with a jumper wire
## How Hardware Flow Control Works
### RTS (Request To Send)
- **Output signal** from the UART
- **LOW (0 V)**: UART is ready to receive data (RX buffer has space)
- **HIGH (3.3 V)**: UART RX buffer is getting full, cannot receive more data
### CTS (Clear To Send)
- **Input signal** to the UART
- **LOW (0 V)**: UART is allowed to transmit data
- **HIGH (3.3 V)**: UART must pause transmission (transmission is blocked)
### Flow Control Behavior
1. **Receiving Data (RTS)**:
- When UART1's RX buffer has space, RTS1 is driven LOW
- When RX buffer fills up (threshold reached), RTS1 is driven HIGH
- This signals the sender to stop transmitting
2. **Transmitting Data (CTS)**:
- UART1 checks CTS1 before transmitting
- If CTS1 is LOW, transmission proceeds normally
- If CTS1 is HIGH, UART1 pauses transmission until CTS1 goes LOW again
### Understanding the Two Modes
**`USE_GPIO_CONTROL = false` (Default - Simple Loopback)**
- Use this mode when you connect RTS1 directly to CTS1 (hardware loopback)
- Flow control operates automatically - no software intervention needed
- RTS/CTS signals are controlled entirely by the UART hardware
- Best for: Basic testing and understanding automatic flow control behavior
- **Wiring (when USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0):** Connect GPIO2 (RTS1) → GPIO4 (CTS1)
**`USE_GPIO_CONTROL = true` (GPIO-Controlled Mode)**
- Use this mode when you want to manually control or monitor flow control signals
- Software can read RTS state and control CTS signal via GPIO pins
- Demonstrates explicit flow control blocking behavior
- Best for: Testing flow control behavior, interfacing with external logic, or learning how external devices control UART transmission
- **Wiring (when USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0):**
- Connect GPIO2 (RTS1) → GPIO5 (to monitor RTS)
- Connect GPIO4 (CTS1) → GPIO13 (to control CTS)
- **Note:** This mode is overridden when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`
### How to Configure
1. Open `HardwareFlowControl_Demo.ino` in Arduino IDE
2. **For internal loopback (no wires):** Set `USE_INTERNAL_MATRIX_PIN_LOOPBACK` to `1` (default)
3. **For external wires:** Set `USE_INTERNAL_MATRIX_PIN_LOOPBACK` to `0` and configure `USE_GPIO_CONTROL`:
- `false` for hardware-controlled mode
- `true` for GPIO-controlled mode
4. Make sure your physical wiring matches the selected mode (only if `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`)
5. Upload the sketch
**Important:**
- When `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`, no external connections are needed
- When `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`, the wiring configuration must match the `USE_GPIO_CONTROL` setting
## Code Explanation
### Key Functions Used
1. **`begin(baudrate)`**
- Initializes the UART with the specified baud rate
- Must be called before setting pins and enabling hardware flow control
2. **`setPins(rxPin, txPin, ctsPin, rtsPin)`**
- Configures the UART pins
- Note: The order `begin()` then `setPins()` is important for proper initialization
3. **`uart_internal_loopback(uartNum, rxPin)`** (when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`)
- Creates internal GPIO matrix connection for TX→RX loopback
- No external wires needed
4. **`uart_internal_hw_flow_ctrl_loopback(uartNum, ctsPin)`** (when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`)
- Creates internal GPIO matrix connection for RTS→CTS flow control loopback
- No external wires needed
5. **`setHwFlowCtrlMode(mode, threshold)`**
- Enables hardware flow control
- Modes:
- `UART_HW_FLOWCTRL_DISABLE`: Disable flow control
- `UART_HW_FLOWCTRL_RTS`: Enable RX flow control only
- `UART_HW_FLOWCTRL_CTS`: Enable TX flow control only
- `UART_HW_FLOWCTRL_CTS_RTS`: Enable full flow control (default)
- Threshold: Number of bytes in RX FIFO before RTS is asserted (default: 64)
### Example Behavior
The sketch demonstrates:
- Periodic transmission of messages
- Flow control blocking when CTS is HIGH
- Monitoring of RTS/CTS pin states
- Loopback reception of transmitted data
## Expected Output and Behavior
The sketch behavior differs depending on the `USE_GPIO_CONTROL` setting (see Configuration section above for details).
### Mode 1: Hardware-Controlled Flow Control (`USE_GPIO_CONTROL = false`)
**Behavior:**
- RTS and CTS signals are automatically controlled by the UART hardware
- RTS1 is directly connected to CTS1 (hardware loopback)
- Flow control operates automatically without software intervention
- RTS goes LOW when ready to receive, HIGH when buffer is full
- CTS must be LOW for transmission to proceed (automatically controlled by RTS)
**Expected Output:**
```
========================================
ESP32 Hardware Flow Control Demo
========================================
Initializing UART1...
Using hardware-controlled flow control (simple loopback)
UART1 initialized successfully!
Hardware flow control: ENABLED (RTS + CTS)
RX Pin: GPIO26 (for the ESP32 RX1 or board-specific RX1 default)
TX Pin: GPIO27 (for the ESP32 TX1 or board-specific TX1 default)
RTS Pin: GPIO2 (output from UART)
CTS Pin: GPIO4 (input to UART)
NO EXTERNAL PIN CONNECTIONS ARE REQUIRED:
-------------------------
Internal GPIO Matrix connection with flow control mode:
1. Automatic Internal Connection of TX1 to RX1 - Loopback (via GPIO matrix)
2. Automatic Internal Connection of GPIO2 (RTS1) to GPIO4 (CTS1) - Flow control loopback
Note: In this mode, RTS/CTS are automatically controlled by hardware.
RTS goes LOW when ready to receive, HIGH when buffer is full.
CTS must be LOW for transmission to proceed.
Starting demonstration in 2 seconds...
=== UART1 Pin Status ===
RX Pin (GPIO26): ESP32 Receiving data (or board-specific RX1)
TX Pin (GPIO27): ESP32 Transmitting data (or board-specific TX1)
RTS Pin (GPIO2): Hardware controlled (LOW = ready to receive)
CTS Pin (GPIO4): Hardware controlled (LOW = can transmit)
Note: RTS/CTS pins are hardware-controlled. Connect RTS1 to CTS1 for loopback.
Available for write: 128 bytes
Available to read: 0 bytes
========================
Sending: Message #1: Hello from UART1! Time: 1000 ms
-> Written: 45 bytes
Received: Message #1: Hello from UART1! Time: 1000 ms
=== UART1 Pin Status ===
RX Pin (GPIO26): ESP32 Receiving data (or board-specific RX1)
TX Pin (GPIO27): ESP32 Transmitting data (or board-specific TX1)
RTS Pin (GPIO2): Hardware controlled (LOW = ready to receive)
CTS Pin (GPIO4): Hardware controlled (LOW = can transmit)
Note: RTS/CTS pins are hardware-controlled. Connect RTS1 to CTS1 for loopback.
Available for write: 128 bytes
Available to read: 45 bytes
========================
Sending: Message #2: Hello from UART1! Time: 2000 ms
-> Written: 45 bytes
Received: Message #2: Hello from UART1! Time: 2000 ms
```
**Key Characteristics:**
- No manual CTS toggling messages
- Flow control happens automatically based on RX buffer state
- RTS/CTS states are not directly readable (hardware-controlled)
- Transmission is always allowed (CTS follows RTS automatically)
### Mode 2: GPIO-Controlled Flow Control (`USE_GPIO_CONTROL = true`)
**Behavior:**
- RTS signal is monitored via GPIO5 (connected to RTS1)
- CTS signal is controlled via GPIO13 (connected to CTS1)
- Software can manually block/allow transmission by controlling GPIO13
- Demonstrates explicit flow control blocking behavior
- Shows how external devices can control UART transmission
- **Note:** This mode only works when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`
**Expected Output:**
```
========================================
ESP32 Hardware Flow Control Demo
========================================
Initializing UART1...
Using GPIO-controlled flow control mode
UART1 initialized successfully!
Hardware flow control: ENABLED (RTS + CTS)
RX Pin: GPIO26 (or board-specific RX1 default)
TX Pin: GPIO27 (or board-specific TX1 default)
RTS Pin: GPIO2 (output from UART)
CTS Pin: GPIO4 (input to UART)
PIN CONNECTIONS REQUIRED:
-------------------------
GPIO-controlled flow control mode:
1. Connect TX1 to RX1 - Loopback (board-specific pins)
2. Connect GPIO2 (RTS1) to GPIO5 - Monitor RTS state
3. Connect GPIO4 (CTS1) to GPIO13 - Control CTS signal
Starting demonstration in 2 seconds...
=== UART1 Pin Status ===
RX Pin (GPIO26): Receiving data (or board-specific RX1)
TX Pin (GPIO27): Transmitting data (or board-specific TX1)
RTS Pin (GPIO2): LOW (Ready) (LOW = ready to receive)
CTS Pin (GPIO4): LOW (Clear) (LOW = can transmit)
Available for write: 128 bytes
Available to read: 0 bytes
========================
Sending: Message #1: Hello from UART1! Time: 1000 ms
-> Written: 45 bytes
Received: Message #1: Hello from UART1! Time: 1000 ms
=== UART1 Pin Status ===
RX Pin (GPIO26): Receiving data (or board-specific RX1)
TX Pin (GPIO27): Transmitting data (or board-specific TX1)
RTS Pin (GPIO2): LOW (Ready) (LOW = ready to receive)
CTS Pin (GPIO4): LOW (Clear) (LOW = can transmit)
Available for write: 128 bytes
Available to read: 45 bytes
========================
>>> Allowing transmission (CTS LOW)...
Sending: Message #2: Hello from UART1! Time: 2000 ms
-> Written: 45 bytes
Received: Message #2: Hello from UART1! Time: 2000 ms
=== UART1 Pin Status ===
RX Pin (GPIO26): Receiving data (or board-specific RX1)
TX Pin (GPIO27): Transmitting data (or board-specific TX1)
RTS Pin (GPIO2): LOW (Ready) (LOW = ready to receive)
CTS Pin (GPIO4): HIGH (Blocked) (LOW = can transmit)
Available for write: 128 bytes
Available to read: 45 bytes
========================
>>> Blocking transmission (CTS HIGH)...
!!! Transmission blocked - CTS is HIGH !!!
=== UART1 Pin Status ===
RX Pin (GPIO26): Receiving data (or board-specific RX1)
TX Pin (GPIO27): Transmitting data (or board-specific TX1)
RTS Pin (GPIO2): LOW (Ready) (LOW = ready to receive)
CTS Pin (GPIO4): HIGH (Blocked) (LOW = can transmit)
Available for write: 128 bytes
Available to read: 45 bytes
========================
>>> Allowing transmission (CTS LOW)...
Sending: Message #3: Hello from UART1! Time: 3000 ms
-> Written: 45 bytes
Received: Message #3: Hello from UART1! Time: 3000 ms
```
**Key Characteristics:**
- Explicit messages showing CTS state changes ("Allowing transmission" / "Blocking transmission")
- Transmission blocking messages when CTS is HIGH
- RTS/CTS pin states are readable via GPIO5 and GPIO13
- Demonstrates manual flow control
- Useful for testing flow control behavior or interfacing with external flow control logic
- Only works when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`
## Troubleshooting
1. **No data received**:
- If `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`: Check that the internal loopback functions are being called
- If `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`: Check that TX1 is connected to RX1
2. **Transmission always blocked**: Verify CTS pin connection and state (only applies when `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`)
3. **RTS always HIGH**: RX buffer may be full, try reading data
4. **Compilation errors**: Ensure you're using ESP32 Arduino Core 2.0.0 or later
5. **GPIO-controlled mode not working**: Make sure `USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0` (internal loopback overrides GPIO control)
## Notes
- **Internal Loopback Mode** (`USE_INTERNAL_MATRIX_PIN_LOOPBACK = 1`): No external connections needed! The ESP32 GPIO matrix handles all connections internally. This is the easiest way to test hardware flow control.
- **External Wire Mode** (`USE_INTERNAL_MATRIX_PIN_LOOPBACK = 0`): Requires physical connections between RTS and CTS pins (and TX/RX for data loopback)
- The threshold parameter controls when RTS is asserted (default: 64 bytes = half of 128-byte FIFO)
- Flow control is most useful when communicating with devices that support it (modems, some sensors, etc.)
- For simple point-to-point communication without flow control support, you can disable it
@@ -0,0 +1,158 @@
/*
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
every time 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 first, 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 occurs, it will raise the UART error interrupt.
This sketch produces BREAK UART error in the beginning 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 = received_bytes + available;
Serial.printf("onReceive Callback:: There are %zu bytes available: {", available);
while (available--) {
char c = Serial1.read();
Serial.printf("0x%x='%c'", c, c);
if (available) {
Serial.print(" ");
}
}
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 BEGINNING\n================================\n", i + 1);
// Now sending BREAK at the beginning 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 different 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 %zu 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 %zu bytes\n", sent_bytes);
} else {
Serial.printf("BREAK event will be sent at the BEGINNING of the %zu bytes\n", sent_bytes);
}
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
Serial1.setRxFIFOFull(fifoFull); // testing different 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 %zu bytes from Serial1 TX to Serial1 RX\n", sent_bytes);
Serial.printf("onReceive() has read a total of %zu 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
}
@@ -0,0 +1,130 @@
/*
This Sketch demonstrates how to use onReceive(callbackFunc) with HardwareSerial
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false)
It is possible to register a UART callback function that will be called
every time that UART receives data and an associated interrupt is generated.
The receiving data interrupt can occur because of two possible events:
1- UART FIFO FULL: it happens when internal UART FIFO reaches a certain number of bytes.
Its full capacity is 127 bytes. The FIFO Full threshold for the interrupt can be changed
using HardwareSerial::setRxFIFOFull(uint8_t fifoFull).
Default FIFO Full Threshold is set at the UART initialization using HardwareSerial::begin()
This will depend on the baud rate set with when begin() is executed.
For a baudrate of 115200 or lower, it it just 1 byte, mimicking original Arduino UART driver.
For a baudrate over 115200 it will be 120 bytes for higher performance.
Anyway it can be changed by the application at anytime.
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.
This time can be changed by HardwareSerial::setRxTimeout(uint8_t rxTimeout)
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
the callback function defined with HardwareSerial::onReceive().
<bool onlyOnTimeout> parameter (default false) can be used by the application to tell Arduino to
only execute the callback when the second event above happens (Rx Timeout). At this time all
received data will be available to be read by the Arduino application. But if the number of
received bytes is higher than the FIFO space, it will generate an error of FIFO overflow.
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.
In summary, HardwareSerial::onReceive() works like an RX Interrupt callback, that can be adjusted
using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout().
*/
#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
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;
void onReceiveFunction(void) {
// This is a callback function that will be activated on UART RX events
size_t available = Serial1.available();
received_bytes = received_bytes + available;
Serial.printf("onReceive Callback:: There are %zu 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\n================================\n", i + 1);
// onReceive callback will be called on FIFO Full and RX timeout - default behavior
testAndReport(fifoFullTestCases[i], false);
}
Serial.printf("\n\n================================\nTest Case #6\n================================\n");
// onReceive callback will be called just on RX timeout - using onlyOnTimeout = true
// FIFO Full parameter (5 bytes) won't matter for the execution of this test case
// because onReceive() uses only RX Timeout to be activated
testAndReport(5, true);
}
void loop() {}
void testAndReport(uint8_t fifoFull, bool onlyOnTimeOut) {
// Let's send 125 bytes from Serial1 rx<->tx and mesaure time using different 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 %zu bytes at %d baud, using RX FIFO Full = %d.\n", sent_bytes, BAUD, fifoFull);
if (onlyOnTimeOut) {
Serial.println("onReceive is called just on RX Timeout!");
} else {
Serial.println("onReceive is called on both FIFO Full and RX Timeout events.");
}
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
Serial1.setRxFIFOFull(fifoFull); // testing different result based on FIFO Full setup
Serial1.onReceive(onReceiveFunction, onlyOnTimeOut); // sets a RX callback function for Serial 1
sent_bytes = Serial1.write(dataSent, DATA_SIZE); // ESP32 TX FIFO is about 128 bytes, 125 bytes will fit fine
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 %zu bytes from Serial1 TX to Serial1 RX\n", sent_bytes);
Serial.printf("onReceive() has read a total of %zu bytes\n", received_bytes);
Serial.println("========================\nFinished!");
Serial1.onReceive(NULL); // resets/disables the RX callback function for Serial 1
}
@@ -0,0 +1,49 @@
/*
This Sketch demonstrates how to use the Hardware Serial peripheral to communicate over an RS485 bus.
Data received on the primary serial port is relayed to the bus acting as an RS485 interface and vice versa.
UART to RS485 translation hardware (e.g., MAX485, MAX33046E, ADM483) is assumed to be configured in half-duplex
mode with collision detection as described in
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#circuit-a-collision-detection-circuit
To use the script open the Arduino serial monitor (or alternative serial monitor on the Arduino port). Then,
using an RS485 tranciver, connect another serial monitor to the RS485 port. Entering data on one terminal
should be displayed on the other terminal.
*/
#include "hal/uart_types.h"
#define RS485_RX_PIN 16
#define RS485_TX_PIN 5
#define RS485_RTS_PIN 4
#define RS485 Serial1
void setup() {
Serial.begin(115200);
RS485.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
while (!RS485) {
delay(10);
}
if (!RS485.setPins(-1, -1, -1, RS485_RTS_PIN)) {
Serial.print("Failed to set RS485 pins");
}
// Certain versions of Arduino core don't define MODE_RS485_HALF_DUPLEX and so fail to compile.
// By using UART_MODE_RS485_HALF_DUPLEX defined in hal/uart_types.h we work around this problem.
// If using a newer IDF and Arduino core you can omit including hal/uart_types.h and use MODE_RS485_HALF_DUPLEX
// defined in esp32-hal-uart.h (included during other build steps) instead.
if (!RS485.setMode(UART_MODE_RS485_HALF_DUPLEX)) {
Serial.print("Failed to set RS485 mode");
}
}
void loop() {
if (RS485.available()) {
Serial.write(RS485.read());
}
if (Serial.available()) {
RS485.write(Serial.read());
}
}
@@ -0,0 +1,106 @@
/*
*
* This Sketch demonstrates the effect of changing RX FIFO Full parameter into HardwareSerial Class
* Serial.setRxFIFOFull(byte) is used to change it.
* By default, UART ISR will wait for 120 bytes to arrive into UART before making the data available
* to be read by an Arduino Sketch. It may also release fewer bytes after an RX Timeout equivalent by
* default to 2 UART symbols.
*
* The way we demonstrate the effect of this parameter is by measuring the time the Sketch takes
* to read data using Arduino HardwareSerial API.
*
* The higher RX FIFO Full is, the lower consumption of the core to process and make the data available.
* At the same time, it may take longer for the Sketch to be able to read it, because the data must first
* populate RX UART FIFO.
*
* The lower RX FIFO Full is, the higher consumption of the core to process and make the data available.
* This is because the core will be interrupted often and it will copy data from the RX FIFO to the Arduino
* internal buffer to be read by the sketch. By other hand, the data will be made available to the sketch
* faster, in a close to byte by byte communication.
*
* Therefore, it allows the decision of the architecture to be designed by the developer.
* Some application based on certain protocols may need the sketch to read the Serial Port byte by byte, for example.
*
*/
#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 125 // 125 bytes is a bit higher than the default 120 bytes of RX FIFO FULL
#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
uint8_t fifoFullTestCases[] = {120, 20, 5, 1};
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\n================================\n", i + 1);
testAndReport(fifoFullTestCases[i]);
}
}
void loop() {}
void testAndReport(uint8_t fifoFull) {
// Let's send 125 bytes from Serial1 rx<->tx and mesaure time using different FIFO Full configurations
uint8_t bytesReceived = 0;
uint8_t dataSent[DATA_SIZE], dataReceived[DATA_SIZE];
uint32_t timeStamp[DATA_SIZE], bytesJustReceived[DATA_SIZE];
uint8_t i;
// initialize all data
for (i = 0; i < DATA_SIZE; i++) {
dataSent[i] = '0' + (i % 10); // fill it with a repeated sequence of 0..9 characters
dataReceived[i] = 0;
timeStamp[i] = 0;
bytesJustReceived[i] = 0;
}
Serial.printf("Testing the time for receiving %d bytes at %d baud, using RX FIFO Full = %d:", DATA_SIZE, BAUD, fifoFull);
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
Serial1.setRxFIFOFull(fifoFull); // testing different result based on FIFO Full setup
size_t sentBytes = Serial1.write(dataSent, sizeof(dataSent)); // ESP32 TX FIFO is about 128 bytes, 125 bytes will fit fine
uint32_t now = millis();
i = 0;
while (bytesReceived < DATA_SIZE) {
bytesReceived += (bytesJustReceived[i] = Serial1.read(dataReceived + bytesReceived, DATA_SIZE));
timeStamp[i] = millis();
if (bytesJustReceived[i] > 0) {
i++; // next data only when we read something from Serial1
}
// safety for array limit && timeout... in 5 seconds...
if (i == DATA_SIZE || millis() - now > 5000) {
break;
}
}
uint32_t pastTime = millis() - now; // codespell:ignore pasttime
Serial.printf("\nIt has sent %zu bytes from Serial1 TX to Serial1 RX\n", sentBytes);
Serial.printf("It took %lu milliseconds to read %d bytes\n", pastTime, bytesReceived); // codespell:ignore pasttime
Serial.printf("Per execution Serial.read() number of bytes data and time information:\n");
for (i = 0; i < DATA_SIZE; i++) {
Serial.printf("#%03d - Received %03lu bytes after %lu ms.\n", i, bytesJustReceived[i], i > 0 ? timeStamp[i] - timeStamp[i - 1] : timeStamp[i] - now);
if (i != DATA_SIZE - 1 && bytesJustReceived[i + 1] == 0) {
break;
}
}
Serial.println("========================\nFinished!");
}
@@ -0,0 +1,110 @@
/*
This Sketch demonstrates the effect of changing RX Timeout parameter into HardwareSerial Class
Serial.setRxTimeout(byte) is used to change it.
By default, UART ISR will wait for an RX Timeout equivalent to 2 UART symbols to understand that a flow.
of UART data has ended. For example, if just one byte is received, UART will send about 10 to
11 bits depending of the configuration (parity, number of stopbits). The timeout is measured in
number of UART symbols, with 10 or 11 bits, in the current baudrate.
For 9600 baud, 1 bit takes 1/9600 of a second, equivalent to 104 microseconds, therefore, for 10 bits,
it takes about 1ms. A timeout of 2 UART symbols, with about 20 bits, would take about 2.1 milliseconds
for the ESP32 UART to trigger an IRQ telling the UART driver that the transmission has ended.
Just at this point, the data will be made available to Arduino HardwareSerial API (read(), available(), etc).
The way we demonstrate the effect of this parameter is by measuring the time the Sketch takes
to read data using Arduino HardwareSerial API.
The higher RX Timeout is, the longer it will take to make the data available, when a flow of data ends.
UART driver works copying data from UART FIFO to Arduino internal buffer.
The driver will copy data from FIFO when RX Timeout is detected or when FIFO is full.
ESP32 FIFO has 128 bytes and by default, the driver will copy the data when FIFO reaches 120 bytes.
If UART receives less than 120 bytes, it will wait RX Timeout to understand that the bus is IDLE and
then copy the data from the FIFO to the Arduino internal buffer, making it available to the Arduino API.
There is an important detail about how HardwareSerial works using ESP32 and ESP32-S2:
If the baud rate is lower than 250,000, it will select REF_TICK as clock source in order to avoid that
the baud rate may change when the CPU Frequency is changed. Default UART clock source is APB, which changes
when CPU clock source is also changed. But when it selects REF_TICK as UART clock source, RX Timeout is limited to 1.
Therefore, in order to change the ESP32/ESP32-S2 RX Timeout it is necessary to fix the UART Clock Source to APB.
In the case of the other SoC, such as ESP32-S3, C3, C6, H2 and P4, there is no such RX Timeout limitation.
Those will set the UART Source Clock as XTAL, which allows the baud rate to be high and it is steady, not
changing with the CPU Frequency.
*/
#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 10 // 10 bytes is lower than the default 120 bytes of RX FIFO FULL
#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
uint8_t rxTimeoutTestCases[] = {50, 20, 10, 5, 1};
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
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
// UART_CLK_SRC_APB will allow higher values of RX Timeout
// default for ESP32 and ESP32-S2 is REF_TICK which limits the RX Timeout to 1
// setClockSource() must be called before begin()
Serial1.setClockSource(UART_CLK_SRC_APB);
#endif
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(rxTimeoutTestCases); i++) {
Serial.printf("\n\n================================\nTest Case #%d\n================================\n", i + 1);
testAndReport(rxTimeoutTestCases[i]);
}
}
void loop() {}
void testAndReport(uint8_t rxTimeout) {
// Let's send 10 bytes from Serial1 rx<->tx and mesaure time using different Rx Timeout configurations
uint8_t bytesReceived = 0;
uint8_t dataSent[DATA_SIZE], dataReceived[DATA_SIZE];
uint8_t i;
// initialize all data
for (i = 0; i < DATA_SIZE; i++) {
dataSent[i] = '0' + (i % 10); // fill it with a repeated sequence of 0..9 characters
dataReceived[i] = 0;
}
Serial.printf("Testing the time for receiving %d bytes at %d baud, using RX Timeout = %d:", DATA_SIZE, BAUD, rxTimeout);
Serial.flush(); // wait Serial FIFO to be empty and then spend almost no time processing it
Serial1.setRxTimeout(rxTimeout); // testing different results based on Rx Timeout setup
// For baud rates lower or equal to 57600, ESP32 Arduino makes it get byte-by-byte from FIFO, thus we will change it here:
Serial1.setRxFIFOFull(120); // forces it to wait receiving 120 bytes in FIFO before making it available to Arduino
size_t sentBytes = Serial1.write(dataSent, sizeof(dataSent)); // ESP32 TX FIFO is about 128 bytes, 10 bytes will fit fine
uint32_t now = millis();
while (bytesReceived < DATA_SIZE) {
bytesReceived += Serial1.read(dataReceived, DATA_SIZE);
// safety for array limit && timeout... in 5 seconds...
if (millis() - now > 5000) {
break;
}
}
uint32_t pastTime = millis() - now; // codespell:ignore pasttime
Serial.printf("\nIt has sent %zu bytes from Serial1 TX to Serial1 RX\n", sentBytes);
Serial.printf("It took %lu milliseconds to read %d bytes\n", pastTime, bytesReceived); // codespell:ignore pasttime
Serial.print("Received data: [");
Serial.write(dataReceived, DATA_SIZE);
Serial.println("]");
Serial.println("========================\nFinished!");
}
@@ -0,0 +1,75 @@
/*
Simple Sketch for testing HardwareSerial with different CPU Frequencies
Changing the CPU Frequency may affect peripherals and Wireless functionality
In ESP32 Arduino, UART shall work correctly in order to let the user see DGB info
and other application messages.
CPU Frequency is usually lowered in sleep modes
and some other Low Power configurations
*/
int cpufreqs[6] = {240, 160, 80, 40, 20, 10};
#define NUM_CPU_FREQS (sizeof(cpufreqs) / sizeof(int))
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n Starting...\n");
Serial.flush();
// initial information
uint32_t Freq = getCpuFrequencyMhz();
Serial.print("CPU Freq = ");
Serial.print(Freq);
Serial.println(" MHz");
Freq = getXtalFrequencyMhz();
Serial.print("XTAL Freq = ");
Serial.print(Freq);
Serial.println(" MHz");
Freq = getApbFrequency();
Serial.print("APB Freq = ");
Serial.print(Freq);
Serial.println(" Hz");
delay(500);
// ESP32-C3 and other RISC-V target may not support 240MHz
#ifdef CONFIG_IDF_TARGET_ESP32C3
uint8_t firstFreq = 1;
#else
uint8_t firstFreq = 0;
#endif
// testing HardwareSerial for all possible CPU/APB Frequencies
for (uint8_t i = firstFreq; i < NUM_CPU_FREQS; i++) {
Serial.printf("\n------- Trying CPU Freq = %d ---------\n", cpufreqs[i]);
Serial.flush(); // wait to empty the UART FIFO before changing the CPU Freq.
setCpuFrequencyMhz(cpufreqs[i]);
Serial.updateBaudRate(115200);
Freq = getCpuFrequencyMhz();
Serial.print("CPU Freq = ");
Serial.print(Freq);
Serial.println(" MHz");
Freq = getXtalFrequencyMhz();
Serial.print("XTAL Freq = ");
Serial.print(Freq);
Serial.println(" MHz");
Freq = getApbFrequency();
Serial.print("APB Freq = ");
Serial.print(Freq);
Serial.println(" Hz");
if (i < NUM_CPU_FREQS - 1) {
Serial.println("Moving to the next frequency after a pause of 2 seconds.");
delay(2000);
}
}
Serial.println("\n-------------------\n");
Serial.println("End of testing...");
Serial.println("\n-------------------\n");
}
void loop() {
// Nothing here so far
}
@@ -0,0 +1,123 @@
/*
* This is C++ example that demonstrates the usage of a std::function as OnReceive Callback function to all the UARTs
* It basically defines a general onReceive function that receives an extra parameter, the Serial pointer that is
* executing the callback.
*
* For each HardwareSerial object (Serial, Serial1, Serial2), it is necessary to set the callback with
* the respective Serial pointer. It is done using lambda expression as a std::function.
* Example:
* Serial1.onReceive([]() { processOnReceiving(&Serial1); });
*
*/
// soc/soc_caps.h has information about each SoC target
// in this example, we use SOC_UART_HP_NUM that goes from 1 to 3,
// depending on the number of available UARTs in the ESP32xx
// This makes the code transparent to what SoC is used.
#include "soc/soc_caps.h"
// This example shall use UART1 or UART2 for testing and UART0 for console messages
// If UART0 is used for testing, it is necessary to manually send data to it, using the Serial Monitor/Terminal
// In case that USB CDC is available, it may be used as console for messages.
#define TEST_UART 1 // Serial# (0, 1 or 2) will be used for the loopback
#define RXPIN 4 // GPIO 4 => RX for Serial1 or Serial2
#define TXPIN 5 // GPIO 5 => TX for Serial1 or Serial2
// declare testingSerial (as reference) related to TEST_UART number defined above (only for Serial1 and Serial2)
#if SOC_UART_HP_NUM > 1 && TEST_UART == 1
HardwareSerial &testingSerial = Serial1;
#elif SOC_UART_HP_NUM > 2 && TEST_UART == 2
HardwareSerial &testingSerial = Serial2;
#endif
// General callback function for any UART -- used with a lambda std::function within HardwareSerial::onReceive()
void processOnReceiving(HardwareSerial &mySerial) {
// detects which Serial# is being used here
int8_t uart_num = -1;
if (&mySerial == &Serial0) {
uart_num = 0;
#if SOC_UART_HP_NUM > 1
} else if (&mySerial == &Serial1) {
uart_num = 1;
#endif
#if SOC_UART_HP_NUM > 2
} else if (&mySerial == &Serial2) {
uart_num = 2;
#endif
}
//Prints some information on the current Serial (UART0 or USB CDC)
if (uart_num == -1) {
Serial.println("This is not a know Arduino Serial# object...");
return;
}
Serial.printf("\nOnReceive Callback --> Received Data from UART%d\n", uart_num);
Serial.printf("Received %d bytes\n", mySerial.available());
Serial.printf("First byte is '%c' [0x%02x]\n", mySerial.peek(), mySerial.peek());
uint8_t charPerLine = 0;
while (mySerial.available()) {
char c = mySerial.read();
Serial.printf("'%c' [0x%02x] ", c, c);
if (++charPerLine == 10) {
charPerLine = 0;
Serial.println();
}
}
}
void setup() {
// Serial can be the USB or UART0, depending on the settings and which SoC is used
Serial.begin(115200);
// when data is received from UART0, it will call the general function
// passing Serial0 as parameter for processing
#if TEST_UART == 0
Serial0.begin(115200); // keeps default GPIOs
Serial0.onReceive([]() {
processOnReceiving(Serial0);
});
#else
// and so on for the other UARTs (Serial1 and Serial2)
// Rx = 4, Tx = 5 will work for ESP32, S2, S3, C3, C6 and H2
testingSerial.begin(115200, SERIAL_8N1, RXPIN, TXPIN);
testingSerial.onReceive([]() {
processOnReceiving(testingSerial);
});
#endif
// this helper function will connect TX pin (from TEST_UART number) to its RX pin
// creating a loopback that will allow to write to TEST_UART number
// and send it to RX with no need to physically connect both pins
#if TEST_UART > 0
uart_internal_loopback(TEST_UART, RXPIN);
#else
// when UART0 is used for testing, it is necessary to send data using the Serial Monitor/Terminal
// Data must be sent by the CP2102, manually using the Serial Monitor/Terminal
#endif
delay(500);
Serial.printf("\nSend bytes to UART%d in order to\n", TEST_UART);
Serial.println("see a single processing function display information about");
Serial.println("the received data.\n");
}
void loop() {
// All done by the UART callback functions
// just write a random number of bytes into the testing UART
char serial_data[24];
size_t len = random(sizeof(serial_data) - 1) + 1; // at least 1 byte will be sent
for (uint8_t i = 0; i < len; i++) {
serial_data[i] = 'A' + i;
}
#if TEST_UART > 0
Serial.println("\n\n==================================");
Serial.printf("Sending %zu bytes to UART%d...\n", len, TEST_UART);
testingSerial.write(serial_data, len);
#else
// when UART0 is used for testing, it is necessary to send data using the Serial Monitor/Terminal
Serial.println("Use the Serial Monitor/Terminal to send data to UART0");
#endif
Serial.println("pausing for 15 seconds.");
delay(15000);
}
@@ -0,0 +1,106 @@
/*
This Sketch demonstrates how to use onReceive(callbackFunc) with HardwareSerial
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout = false)
It is possible to register an UART callback function that will be called
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 using HardwareSerial::setRxFIFOFull() and HardwareSerial::setRxTimeout().
In case that <onlyOnTimeout> is not changed or it is set to <false>, the callback function is
executed whenever any event happens first (FIFO Full or RX Timeout).
OnReceive will be called when every 120 bytes are received(default FIFO Full),
or when RX Timeout occurs after 1 UART symbol by default.
This example demonstrates a way to create a String with all data received from UART0 only
after RX Timeout. This example uses an RX timeout of about 3.5 Symbols as a way to 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.
It can occur because of two possible events:
1- UART FIFO FULL: it happens when internal UART FIFO reaches a certain number of bytes.
Its full capacity is 127 bytes. The FIFO Full threshold for the interrupt can be changed
using HardwareSerial::setRxFIFOFull(uint8_t fifoFull).
Default FIFO Full Threshold is set in the UART initialization using HardwareSerial::begin()
This will depend on the baud rate used when begin() is executed.
For a baud rate of 115200 or lower, it it just 1 byte, mimicking original Arduino UART driver.
For a baud rate over 115200 it will be 120 bytes for higher performance.
Anyway, it can be changed by the application at any time.
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.
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
RingBuffer and then Arduino can read such data. At the same time, Arduino Layer will execute
the callback function defined with HardwareSerial::onReceive().
<bool onlyOnTimeout> parameter can be used by the application to tell Arduino to only execute
the callback when Rx Timeout happens, by setting it to <true>.
At this time all received data will be available to be read by the Arduino application.
The application shall set an appropriate RX buffer size using
HardwareSerial::setRxBufferSize(size_t new_size) before executing begin() for the Serial port.
MODBUS timeout of 3.5 symbol is based on these documents:
https://www.automation.com/en-us/articles/2012-1/introduction-to-modbus
https://minimalmodbus.readthedocs.io/en/stable/serialcommunication.html
*/
// global variable to keep the results from onReceive()
String uart_buffer = "";
// The Modbus RTU standard prescribes a silent period corresponding to 3.5 characters between each
// 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;
// UART_RX_IRQ will be executed as soon as data is received by the UART and an RX Timeout occurs
// This is a callback function executed from a high priority monitor task
// All data will be buffered into RX Buffer, which may have its size set to whatever necessary
void UART0_RX_CB() {
while (Serial0.available()) {
uart_buffer += (char)Serial0.read();
}
}
// setup() and loop() are functions executed by a low priority task
// Therefore, there are 2 tasks running when using onReceive()
void setup() {
// Using Serial0 will work in any case (using or not USB CDC on Boot)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
// UART_CLK_SRC_APB will allow higher values of RX Timeout
// default for ESP32 and ESP32-S2 is REF_TICK which limits the RX Timeout to 1
// setClockSource() must be called before begin()
Serial0.setClockSource(UART_CLK_SRC_APB);
#endif
// the amount of data received or waiting to be proessed shall not exceed this limit of 1024 bytes
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)
// sets the callback function that will be executed only after RX Timeout
Serial0.onReceive(UART0_RX_CB, true);
Serial0.println("Send data using Serial Monitor in order to activate the RX callback");
}
uint32_t counter = 0;
void loop() {
// String <uart_buffer> is filled by the UART Callback whenever data is received and RX Timeout occurs
if (uart_buffer.length() > 0) {
// process the received data from Serial - example, just print it beside a counter
Serial0.print("[");
Serial0.print(counter++);
Serial0.print("] [");
Serial0.print(uart_buffer.length());
Serial0.print(" bytes] ");
Serial0.println(uart_buffer);
uart_buffer = ""; // reset uart_buffer for the next UART reading
}
delay(1);
}