Chapter 136: UART Advanced Features with ESP-IDF

Chapter Objectives

Upon completing this chapter, you will be able to:

  • Understand and implement hardware flow control (RTS/CTS) for UART communication.
  • Describe the principles of software flow control (XON/XOFF).
  • Utilize UART events for asynchronous data handling and status monitoring.
  • Implement UART pattern detection to trigger actions on specific incoming data sequences.
  • Understand how UART can be used to wake up the ESP32 from light sleep.
  • Configure and use UART in RS485 half-duplex mode for multi-drop communication.
  • Recognize differences in advanced UART feature support across various ESP32 variants.

Introduction

In Chapter 135, we explored the basics of UART communication, covering its fundamental principles, configuration, and simple data transmission and reception. While basic UART is sufficient for many applications, more demanding scenarios require advanced features to ensure reliable and efficient communication. These scenarios might involve high data rates, communication with devices that have limited buffering capabilities, or the need for specific data patterns to trigger actions.

This chapter delves into the advanced features of the ESP32 UART peripheral and the ESP-IDF UART driver. We will cover hardware and software flow control mechanisms that prevent data loss, event-driven communication for more responsive applications, pattern detection for identifying specific data sequences, UART-based wakeup from sleep modes, and the specialized RS485 communication mode. Mastering these features will allow you to build more robust and sophisticated serial communication solutions with your ESP32.

Theory

1. Hardware Flow Control (RTS/CTS)

When one device sends data much faster than the other can process it, the receiver’s input buffer can overflow, leading to data loss. Hardware flow control provides a mechanism to prevent this. It uses two additional signal lines:

  • RTS (Request To Send): An output signal from a device (A) that wants to send data. When device A is ready to transmit, it asserts its RTS line (typically active low).
  • CTS (Clear To Send): An input signal to device A. The receiving device (B) asserts its CTS line (typically active low) when it is ready to accept data. If device B’s buffer is nearly full, it de-asserts its CTS line, signaling device A to pause transmission. Device A will wait until CTS is asserted again before resuming.

Operation:

  1. Device A wants to send data, so it checks its CTS input.
  2. If CTS is asserted (e.g., low), Device A starts transmitting data and asserts its own RTS output (signaling to Device B that it is sending).
  3. Device B receives data. If its internal buffer starts to fill up, it de-asserts its RTS output (which is connected to Device A’s CTS input).
  4. Device A sees its CTS input de-asserted and pauses transmission.
  5. Once Device B has processed some data and has space in its buffer, it re-asserts its RTS output.
  6. Device A sees its CTS input asserted again and resumes transmission.

This mechanism ensures that data is only sent when the receiver is capable of handling it, significantly improving reliability in high-throughput applications.

%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans, sans-serif'}}}%%
sequenceDiagram
    participant DeviceA as Device A (Sender)
    participant DeviceB as Device B (Receiver)

    DeviceA->>DeviceA: Wants to send data
    DeviceA->>DeviceB: Checks B's CTS line (connected to A's RTS output)
    alt B is Ready (CTS Asserted by B)
        DeviceB-->>DeviceA: CTS Asserted (e.g., Low)
        DeviceA->>DeviceB: A asserts its RTS (to B's CTS)
        DeviceA->>DeviceB: Transmits Data
        DeviceB->>DeviceB: Receives Data, Buffer fills
        opt B's Buffer Nearing Full
            DeviceB->>DeviceA: B de-asserts its RTS (to A's CTS)
            DeviceA->>DeviceA: A sees its CTS de-asserted
            DeviceA->>DeviceA: Pauses Transmission
            Note over DeviceA,DeviceB: A waits for CTS to be re-asserted
            DeviceB->>DeviceB: Processes data, buffer space frees up
            DeviceB->>DeviceA: B re-asserts its RTS (to A's CTS)
            DeviceA->>DeviceA: A sees its CTS asserted
            DeviceA->>DeviceB: Resumes Transmission
        end
    else B is Not Ready (CTS De-asserted by B)
        DeviceB-->>DeviceA: CTS De-asserted (e.g., High)
        DeviceA->>DeviceA: Waits for B's CTS to be asserted
        Note right of DeviceA: Transmission Paused
    end

  

ESP-IDF Configuration:

Hardware flow control is enabled in the uart_config_t structure by setting flow_ctrl to UART_HW_FLOWCTRL_CTS_RTS (for both CTS and RTS) or UART_HW_FLOWCTRL_CTS / UART_HW_FLOWCTRL_RTS if only one direction is needed. The corresponding GPIO pins for RTS and CTS must also be assigned using uart_set_pin().

2. Software Flow Control (XON/XOFF)

Software flow control achieves a similar goal to hardware flow control but without requiring dedicated RTS/CTS lines. Instead, it uses special control characters transmitted in-band over the regular TX/RX data lines:

  • XOFF (Transmit Off): A special character (typically DC3, ASCII 19, 0x13) sent by the receiver to the transmitter to request a pause in data transmission.
  • XON (Transmit On): A special character (typically DC1, ASCII 17, 0x11) sent by the receiver to the transmitter to signal that it is ready to receive data again.

Operation:

  1. The transmitter sends data.
  2. If the receiver’s buffer is nearing capacity, it sends an XOFF character to the transmitter.
  3. Upon receiving XOFF, the transmitter stops sending data (except for XON/XOFF characters).
  4. When the receiver has processed data and freed up buffer space, it sends an XON character.
  5. Upon receiving XON, the transmitter resumes data transmission.
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans, sans-serif'}}}%%
sequenceDiagram
    participant Transmitter
    participant Receiver

    Transmitter->>Receiver: Sends Data
    loop Data Transmission
        Transmitter->>Receiver: Data Packet
    end

    alt Receiver Buffer Nearing Full
        Receiver->>Transmitter: Sends XOFF character (e.g., 0x13)
        Transmitter->>Transmitter: Receives XOFF
        Transmitter->>Transmitter: Pauses Data Transmission (except XON/XOFF)
        Note over Transmitter,Receiver: Transmission halted by XOFF
    end

    Receiver->>Receiver: Processes data, buffer space frees up

    alt Receiver Ready for More Data
        Receiver->>Transmitter: Sends XON character (e.g., 0x11)
        Transmitter->>Transmitter: Receives XON
        Transmitter->>Receiver: Resumes Data Transmission
        Note over Transmitter,Receiver: Transmission resumed by XON
    end

Pros & Cons:

  • Pros: Requires only TX and RX lines, saving GPIO pins.
  • Cons:
    • Reduces effective data throughput as control characters are part of the data stream.
    • The control characters (XON/XOFF) cannot appear in the actual data being transmitted unless a special escaping mechanism is used, which adds complexity.
    • Slightly higher latency in responding compared to hardware flow control.
Feature Hardware Flow Control (RTS/CTS) Software Flow Control (XON/XOFF)
Signal Lines Requires 2 dedicated lines (RTS, CTS) in addition to TX, RX, GND. Uses existing TX/RX lines. No extra dedicated lines needed.
Data Integrity Out-of-band signaling; control signals do not interfere with the data stream. In-band signaling; control characters (XON, XOFF) are sent as data. Data stream must not contain these characters or must use an escaping mechanism.
Response Time Generally faster response due to dedicated hardware lines. Slightly higher latency as control characters must be processed by software.
Throughput Full data bandwidth available for application data. Reduces effective data throughput as control characters consume bandwidth.
Complexity Hardware implementation is straightforward if supported by both devices. Software configuration is simple. Can be more complex to implement reliably in application software, especially if data escaping is needed.
GPIO Usage Consumes 2 additional GPIO pins. Saves GPIO pins.
ESP-IDF Support Well-supported by ESP-IDF UART driver (UART_HW_FLOWCTRL_CTS_RTS). Limited direct/automatic support in ESP-IDF driver; typically requires manual application-level implementation. Some config fields exist (sw_flow_ctrl_en) but may need careful handling.
Pros
  • Highly reliable.
  • No impact on data stream content.
  • Fast response.
  • Standardized.
  • No extra pins required.
  • Simpler wiring.
Cons
  • Requires 2 extra GPIO pins.
  • Both devices must support and be wired for it.
  • Control characters can conflict with data.
  • Slower response.
  • Reduced data throughput.
  • Can be more complex to implement robustly.
Preferred Use Cases High-speed data transfer, communication with devices having small buffers, modems, situations requiring maximum reliability. Generally preferred for ESP32 if pins are available. Legacy systems, pin-constrained applications, situations where data stream is known not to contain XON/XOFF characters.

ESP-IDF Support:

The ESP-IDF UART driver has limited built-in support for software flow control. While you can manually implement it by checking for XON/XOFF characters in your application logic, it’s not as integrated as hardware flow control. The uart_config_t structure has sw_flow_ctrl_en and xon_threshold, xoff_threshold fields, but their usage might require deeper driver understanding or custom modifications for full-featured XON/XOFF. For most ESP32 applications requiring flow control, hardware RTS/CTS is preferred due to its robustness and clear separation from the data stream.

3. UART Events and Event Queue

Instead of continuously polling uart_read_bytes() with a timeout, the ESP-IDF UART driver can be configured to use an event queue. This allows your application to react asynchronously to various UART events, making for more efficient and responsive designs.

When installing the UART driver using uart_driver_install(), you can provide a FreeRTOS queue handle. The driver will then post event messages to this queue when specific conditions occur.

Common UART Events (uart_event_type_t):

Event Type (uart_event_type_t) Description Associated Data in uart_event_t
UART_DATA New data has arrived in the RX FIFO and is ready to be read. size: Number of bytes available to read.
UART_BREAK A break condition (TX line held low for longer than a frame duration) has been detected on the RX line. None specific beyond type.
UART_BUFFER_FULL The UART RX software ring buffer is full. This is a warning that data might be lost if not read quickly. The hardware RX FIFO might still have space. None specific beyond type. Indicates immediate read action needed.
UART_FIFO_OVF The hardware RX FIFO has overflowed. Data has been lost because the FIFO was full and new data arrived before old data was moved to the software ring buffer. None specific beyond type. Indicates data loss.
UART_FRAME_ERR A framing error was detected (e.g., the stop bit was not detected at the expected time). None specific beyond type. Indicates a problem with data integrity.
UART_PARITY_ERR A parity error was detected (if parity checking is enabled and the calculated parity does not match the received parity bit). None specific beyond type. Indicates a problem with data integrity.
UART_TX_DONE The requested transmission of data using uart_write_bytes_with_timeout() or similar functions that use the TX buffer has completed. Not typically generated for every byte with uart_write_bytes() if TX buffer is not used or small. size: Number of bytes written.
UART_PATTERN_DET A pre-configured pattern has been detected in the RX data stream. size: May indicate bytes in buffer around pattern. Use uart_pattern_pop_pos() to get pattern position.
UART_EVENT_MAX A marker, not an actual event. Indicates the total number of event types. N/A
UART_WAKEUP (If applicable) UART peripheral caused a wakeup from sleep. (May vary with IDF version/config) None specific beyond type.
UART_TX_BREAK_DONE (If applicable) Transmission of a break signal has completed. None specific beyond type.

Workflow:

  1. Create a FreeRTOS queue: xQueueCreate().
  2. Install the UART driver: uart_driver_install(), passing the queue handle and queue size.
  3. Create a task that waits for messages on this queue: xQueueReceive().
  4. When an event is received, the task inspects the uart_event_t structure to determine the event type and associated data (e.g., size of received data for UART_DATA).
  5. Process the event accordingly (e.g., read data using uart_read_bytes()).
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans, sans-serif'}}}%%
graph TD
    A[Start: Initialize UART with Event Queue] --> B("1- Create FreeRTOS Queue<br><b>xQueueCreate()</b>");
    B --> C("2- Install UART Driver<br><b>uart_driver_install()</b><br><i>Pass queue handle & size</i>");
    C --> D("3- Configure UART Parameters & Pins<br><b>uart_param_config()</b>, <b>uart_set_pin()</b>");
    D --> E("4- Create UART Event Handling Task");

    subgraph "UART Event Handling Task (Loop)"
        direction TB
        F["Wait for Event on Queue<br><b>xQueueReceive(queue, &event, portMAX_DELAY)</b>"] --> G{Event Received};
        G --> H{Switch on event.type};
        H -- UART_DATA --> I["Read Data:<br><b>uart_read_bytes(port, buffer, event.size, timeout)</b><br>Process Data"];
        H -- UART_PATTERN_DET --> J["Pattern Detected:<br>Get position: <b>uart_pattern_pop_pos()</b><br>Read/Process pattern data<br>Perform action"];
        H -- UART_FIFO_OVF / UART_BUFFER_FULL --> K["Buffer Error:<br>Log error<br>Flush input: <b>uart_flush_input()</b><br>Reset queue: <b>xQueueReset()</b> (optional)"];
        H -- UART_FRAME_ERR / UART_PARITY_ERR --> L[Communication Error:<br>Log error];
        H -- Other Events --> M["Handle other specific events<br>(e.g., UART_BREAK, UART_TX_DONE)"];
        I --> F;
        J --> F;
        K --> F;
        L --> F;
        M --> F;
    end
    E --> F;

    P["End: (If UART no longer needed)"] --> Q("Uninstall UART Driver<br><b>uart_driver_delete()</b>");
    Q --> R("Delete Queue<br><b>vQueueDelete()</b>");

    classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    classDef ioNode fill:#E0F2FE,stroke:#0EA5E9,stroke-width:1px,color:#0369A1
    classDef errorNode fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B
    classDef endNode fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46
    classDef stateDefault font-family:'Open Sans, sans-serif'

    class A,R startNode;
    class B,C,D,E,I,J,K,L,M,Q processNode;
    class G,H decisionNode;
    class F ioNode; 
    class P endNode;

This event-driven approach is particularly useful for handling sporadic data or integrating UART communication smoothly into a larger FreeRTOS application.

4. UART Pattern Detection

The ESP32 UART controllers can be configured to detect a specific sequence of up to 3 identical or different characters (a “pattern”) in the incoming RX data stream. When the pattern is detected, the UART peripheral can trigger an interrupt, which the ESP-IDF driver can then translate into a UART_PATTERN_DET event.

Configuration:

  • Define the pattern character(s) and their repetition count. For ESP-IDF v5.x, this is typically done via uart_struct.h definitions or specific registers if accessing low-level. The high-level API for this is uart_enable_pattern_det_baud_intr() or the more general uart_enable_pattern_det_intr() (deprecated in favor of uart_enable_pattern_det_baud_intr or direct register access for more complex patterns in recent IDF versions). More commonly, you set uart_pattern_t struct members:
    • pattern_chr[0-2]: The character(s) forming the pattern.
    • chr_num: Number of characters in the pattern (1 to 3).
    • chr_tout: Timeout for inter-character gap within the pattern.
    • post_idle: Idle time after the pattern.
    • pre_idle: Idle time before the pattern.
  • The function uart_pattern_queue_reset() can be used to clear the pattern detection queue.
  • Enable the pattern detection interrupt.

Use Cases:

  • Command Triggering: Waiting for a specific command string (e.g., “+++”) to enter a configuration mode, similar to Hayes AT commands.
  • Packet Synchronization: Detecting a start-of-packet marker.
  • Wake-up Signal: Using a specific pattern to wake the device from a low-power state.
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans, sans-serif'}}}%%
graph TD
    A[Start: Configure Pattern Detection] --> B("1- Define Pattern<br><b>uart_pattern_t pattern_config;</b><br><i>pattern_config.pattern_chr[0..2] = ...</i><br><i>pattern_config.chr_num = ...</i><br><i>pattern_config.chr_tout = ... (optional)</i>");
    B --> C("2- Enable Pattern Detection Interrupt<br><b>uart_enable_pattern_det_baud_intr()</b> or<br><b>uart_set_rx_full_threshold()</b> / <b>uart_set_rx_timeout()</b> with pattern features");
    C --> D("3- Ensure UART Event Queue is Setup<br><i>(Covered in Event Handling Workflow)</i>");
    D --> E{Incoming RX Data Stream};
    E -- Contains Pattern? --> F[UART Hardware Detects Configured Pattern];
    F --> G[Interrupt Triggered];
    G --> H[Driver Posts <b>UART_PATTERN_DET</b> Event to Queue];
    H --> I(Event Handling Task Receives <b>UART_PATTERN_DET</b>);
    I --> J("1- Get Pattern Position (optional)<br><b>uart_pattern_pop_pos()</b>");
    J --> K("2- Read Data Around Pattern<br><b>uart_read_bytes()</b>");
    K --> L("3- Perform Action Based on Pattern<br>e.g., Enter command mode, sync packet");
    L --> M[Continue or Reset State];
    E -- No Pattern Match --> N[Normal Data Handling / Other Events];
    N --> E;
    M --> E;


    classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    classDef ioNode fill:#E0F2FE,stroke:#0EA5E9,stroke-width:1px,color:#0369A1
    classDef successNode fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46
    classDef stateDefault font-family:'Open Sans, sans-serif'

    class A startNode;
    class B,C,D,F,G,H,I,J,K,L,M,N processNode;
    class E decisionNode;

5. UART Wake-up from Light Sleep

To save power, the ESP32 can enter light sleep mode. UART activity can be configured as one of the sources to wake the ESP32 from this state.

Configuration:

  1. Configure the UART peripheral normally.
  2. Set a wakeup threshold using uart_set_wakeup_threshold(). This defines how many incoming RX pulses (edges) are needed to trigger a wakeup signal. Typically, a threshold of 3 or more is used to avoid spurious wakeups due to noise.
  3. Enable UART as a wakeup source using esp_sleep_enable_uart_wakeup().
  4. When the ESP32 enters light sleep (esp_light_sleep_start()), if the configured number of edges is detected on the RX pin, the system will wake up.
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans, sans-serif'}}}%%
graph TD
    A[Start: Configure UART for Wakeup] --> B("1- Initialize UART Peripheral Normally<br><b>uart_driver_install()</b>, etc.");
    B --> C("2- Set Wakeup Threshold<br><b>uart_set_wakeup_threshold(uart_num, threshold_pulses)</b><br><i>e.g., 3 pulses on RX line</i>");
    C --> D("3- Enable UART as Wakeup Source<br><b>esp_sleep_enable_uart_wakeup(uart_num)</b>");
    D --> E[Application Decides to Enter Light Sleep];
    E --> F{"Enter Light Sleep Mode<br><b>esp_light_sleep_start()</b>"};
    
    subgraph During Light Sleep
        direction LR
        G((ESP32 in<br>Light Sleep))
    end

    F --> G;

    H{UART RX Pin Activity};
    G -.-> H;

    H -- Sufficient Edges Detected? (>= threshold) --> I[Wakeup Signal Triggered by UART];
    I --> J[ESP32 Wakes Up from Light Sleep];
    J --> K["Program Execution Resumes After <b>esp_light_sleep_start()</b>"];
    K --> L["Check Wakeup Cause (Optional)<br><b>esp_sleep_get_wakeup_cause()</b>"];
    L --> M[Process Incoming UART Data or Perform Action];
    M --> E; 

    H -- Insufficient Edges / No Activity --> G; 

    classDef startNode fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6
    classDef processNode fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF
    classDef decisionNode fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E
    classDef sleepNode fill:#6B7280,stroke:#374151,stroke-width:2px,color:#F9FAFB
    classDef successNode fill:#D1FAE5,stroke:#059669,stroke-width:1px,color:#065F46
    classDef stateDefault font-family:'Open Sans, sans-serif'

    class A startNode;
    class B,C,D,E,K,L,M processNode;
    class F,H decisionNode;
    class G sleepNode;
    class I,J successNode;

This feature is useful for battery-powered devices that need to sleep but respond to incoming serial commands or data.

6. RS485 Communication Mode

RS485 is a standard defining the electrical characteristics of drivers and receivers for use in serial communications systems. It’s widely used in industrial environments due to its robustness, support for long distances (up to 1200 meters), and multi-drop capability (multiple devices on the same bus).

Key RS485 Characteristics:

  • Differential Signaling: Uses two wires (A and B) for data transmission. Data is represented by the voltage difference between these lines, making it highly resistant to common-mode noise.
  • Half-Duplex: Typically, communication is half-duplex on a two-wire RS485 bus, meaning a device can either transmit or receive at any given time, but not both simultaneously.
  • Multi-Drop: Allows multiple transceivers (up to 32 unit loads, expandable with repeaters) to share the same bus.

ESP32 and RS485:

%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans, sans-serif'}}}%%
sequenceDiagram
    participant MasterDevice as Master (ESP32)
    participant SlaveDevice as Slave Device
    participant RS485Bus as RS485 Bus
    participant TransceiverM as Master's Transceiver
    participant TransceiverS as Slave's Transceiver

    Note over MasterDevice, SlaveDevice: Initial state: <br>Both devices in Receive Mode <br>(DE Low, /RE Low)

    MasterDevice->>TransceiverM: 1. Master wants to send data
    MasterDevice->>TransceiverM: 2. Assert DE (Driver Enable) HIGH <br>(via RTS pin)
    TransceiverM->>RS485Bus: 3. Transmits Data <br>(Differential Signal A/B)
    activate RS485Bus
    RS485Bus->>TransceiverS: 4. Slave's Transceiver receives <br>differential signal
    deactivate RS485Bus
    TransceiverS->>SlaveDevice: 5. Converts to single-ended, <br>passes to Slave's UART RX
    
    MasterDevice->>TransceiverM: 6. After transmission, <br>de-assert DE LOW <br>(Puts Master Transceiver in High-Z or Receive)
    Note over MasterDevice, SlaveDevice: Master switches to Receive Mode<br> to listen for response

    SlaveDevice->>SlaveDevice: 7. Processes received data
    opt Slave needs to respond
        SlaveDevice->>TransceiverS: 8. Slave wants to send response
        SlaveDevice->>TransceiverS: 9. Slave asserts its DE HIGH
        TransceiverS->>RS485Bus: 10. Transmits Response Data <br>(Differential Signal A/B)
        activate RS485Bus
        RS485Bus->>TransceiverM: 11. Master's Transceiver receives <br>differential signal
        deactivate RS485Bus
        TransceiverM->>MasterDevice: 12. Converts to single-ended, passes to Master's UART RX
        SlaveDevice->>TransceiverS: 13. After transmission, Slave de-asserts its DE LOW
    end

  

The ESP32’s UART peripheral can be configured to support RS485 communication. This usually involves:

  1. External RS485 Transceiver: An external IC (e.g., MAX485, SP3485) is required to convert the ESP32’s single-ended TTL UART signals (TX, RX) to the differential signals (A, B) required by RS485.
  2. Direction Control: For half-duplex communication, the RS485 transceiver needs a direction control signal, often called DE (Driver Enable) and /RE (Receiver Enable, often tied together or inverted from DE). This signal switches the transceiver between transmit and receive mode.
    • The ESP32’s RTS pin of a UART controller is commonly repurposed to provide this direction control signal.
  3. ESP-IDF Configuration:
    • Use uart_set_mode(uart_port, UART_MODE_RS485_HALF_DUPLEX).
    • Configure the RTS pin (used for DE/RE) appropriately. The driver can often automatically control this pin when in RS485 mode, asserting it before transmission and de-asserting it after.
    • The uart_set_rs485_hd_opts_t structure and uart_set_rs485_hd_opts() function can be used to configure delays for RTS assertion/de-assertion if needed for specific transceivers.

Practical Examples

Example 1: UART with Hardware Flow Control (RTS/CTS)

This example demonstrates configuring UART with RTS/CTS hardware flow control. You’ll need to connect the ESP32 to another device (e.g., a PC with a USB-to-Serial adapter that supports RTS/CTS, or another ESP32) that also implements hardware flow control.

Code (main/main.c):

C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define UART_PORT_NUM      UART_NUM_1
#define UART_RX_BUF_SIZE   (1024)
#define UART_TX_BUF_SIZE   (256) // Using a TX buffer

// Define UART pins - CHOOSE PINS APPROPRIATE FOR YOUR BOARD AND AVAILABLE FOR RTS/CTS
#define UART_TXD_PIN  (GPIO_NUM_17)
#define UART_RXD_PIN  (GPIO_NUM_16)
#define UART_RTS_PIN  (GPIO_NUM_18) 
#define UART_CTS_PIN  (GPIO_NUM_19)

static const char *TAG = "UART_HW_FLOW_CTRL";

// Task to send data periodically
static void uart_tx_hwfc_task(void *arg) {
    char *message = "This is a test string with hardware flow control. Sending data periodically... Line 1\r\n";
    char data_buffer[128];
    int count = 0;

    ESP_LOGI(TAG, "UART TX task with HWFC started.");
    while(1) {
        snprintf(data_buffer, sizeof(data_buffer), "Msg %d: %s", count++, message);
        int len = strlen(data_buffer);
        int written = uart_write_bytes(UART_PORT_NUM, data_buffer, len);
        if (written > 0) {
            ESP_LOGI(TAG, "Wrote %d bytes", written);
        } else {
            ESP_LOGW(TAG, "Error writing UART data, written: %d", written);
        }
        vTaskDelay(pdMS_TO_TICKS(500)); // Send data every 500ms
    }
}

// Task to receive data
static void uart_rx_hwfc_task(void *arg) {
    uint8_t* data = (uint8_t*) malloc(UART_RX_BUF_SIZE);
    ESP_LOGI(TAG, "UART RX task with HWFC started. Waiting for data...");
    while (1) {
        int len = uart_read_bytes(UART_PORT_NUM, data, (UART_RX_BUF_SIZE - 1), pdMS_TO_TICKS(200)); // 200ms timeout
        if (len > 0) {
            data[len] = '\0';
            ESP_LOGI(TAG, "Read %d bytes: '%s'", len, (char*)data);
        }
    }
    free(data);
}


void app_main(void) {
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS, // Enable RTS and CTS
        .rx_flow_ctrl_thresh = 122, // Threshold for RX buffer to trigger RTS de-assertion
        .source_clk = UART_SCLK_DEFAULT,
    };

    ESP_LOGI(TAG, "Configuring UART%d with Hardware Flow Control...", UART_PORT_NUM);

    // Install UART driver
    ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_RX_BUF_SIZE * 2, UART_TX_BUF_SIZE, 0, NULL, 0));
    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));

    // Set UART pins
    ESP_LOGI(TAG, "Setting UART%d pins: TXD=%d, RXD=%d, RTS=%d, CTS=%d", 
             UART_PORT_NUM, UART_TXD_PIN, UART_RXD_PIN, UART_RTS_PIN, UART_CTS_PIN);
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TXD_PIN, UART_RXD_PIN, UART_RTS_PIN, UART_CTS_PIN));

    ESP_LOGI(TAG, "UART driver with HWFC installed and configured.");

    xTaskCreate(uart_tx_hwfc_task, "uart_tx_hwfc_task", 2048, NULL, 10, NULL);
    xTaskCreate(uart_rx_hwfc_task, "uart_rx_hwfc_task", 2048, NULL, 10, NULL);
}

Build and Run:

  1. Hardware Connection:
    • Connect ESP32 TXD (GPIO17) to the other device’s RXD.
    • Connect ESP32 RXD (GPIO16) to the other device’s TXD.
    • Connect ESP32 RTS (GPIO18) to the other device’s CTS.
    • Connect ESP32 CTS (GPIO19) to the other device’s RTS.
    • Connect GND.
    • Ensure the other device is also configured for 115200 baud, 8N1, and hardware RTS/CTS flow control.
  2. Build, flash, and monitor.
  3. Observe: The ESP32 will send data periodically. Try to make the receiving device stop asserting its RTS (e.g., by pausing the serial terminal or filling its buffer). You should observe that the ESP32 pauses its transmission (or uart_write_bytes blocks/returns less) until the receiver is ready again. Similarly, if you send a large amount of data to the ESP32, it should de-assert its RTS if its buffer gets full.

Example 2: UART Event Handling (Data and Pattern Detection)

This example demonstrates using the UART event queue to handle incoming data and detect a specific pattern (“+++”).

Code (main/main.c):

C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define UART_PORT_NUM      UART_NUM_1
#define UART_RX_BUF_SIZE   (1024)
#define UART_TX_BUF_SIZE   (0) // No TX buffer for simplicity in this example

#define UART_TXD_PIN  (GPIO_NUM_17)
#define UART_RXD_PIN  (GPIO_NUM_16)
#define UART_RTS_PIN  (GPIO_NUM_NC) 
#define UART_CTS_PIN  (GPIO_NUM_NC)

#define PATTERN_CHR_NUM (3)         // Number of characters in the pattern ("+++")
#define PATTERN_CHAR    ('+')

static const char *TAG = "UART_EVENTS";
static QueueHandle_t uart_event_queue;

static void uart_event_task(void *pvParameters) {
    uart_event_t event;
    uint8_t* dtmp = (uint8_t*) malloc(UART_RX_BUF_SIZE);
    if (dtmp == NULL) {
        ESP_LOGE(TAG, "Failed to allocate buffer for UART event task");
        vTaskDelete(NULL);
        return;
    }

    ESP_LOGI(TAG, "UART event task started.");

    for(;;) {
        //Waiting for UART event.
        if(xQueueReceive(uart_event_queue, (void * )&event, (TickType_t)portMAX_DELAY)) {
            ESP_LOGI(TAG, "uart[%d] event:", UART_PORT_NUM);
            switch(event.type) {
                case UART_DATA:
                    ESP_LOGI(TAG, "[UART_DATA]: %d", event.size);
                    uart_read_bytes(UART_PORT_NUM, dtmp, event.size, portMAX_DELAY);
                    dtmp[event.size] = '\0'; // Null-terminate
                    ESP_LOGI(TAG, "[DATA EVT]: %s", (char*)dtmp);
                    // Echo back received data
                    uart_write_bytes(UART_PORT_NUM, (const char*) dtmp, event.size);
                    break;
                case UART_FIFO_OVF:
                    ESP_LOGW(TAG, "hw fifo overflow");
                    uart_flush_input(UART_PORT_NUM);
                    xQueueReset(uart_event_queue);
                    break;
                case UART_BUFFER_FULL:
                    ESP_LOGW(TAG, "ring buffer full");
                    uart_flush_input(UART_PORT_NUM);
                    xQueueReset(uart_event_queue);
                    break;
                case UART_BREAK:
                    ESP_LOGI(TAG, "uart rx break");
                    break;
                case UART_PARITY_ERR:
                    ESP_LOGE(TAG, "uart parity error");
                    break;
                case UART_FRAME_ERR:
                    ESP_LOGE(TAG, "uart frame error");
                    break;
                case UART_PATTERN_DET:
                    uart_get_buffered_data_len(UART_PORT_NUM, &event.size);
                    int pos = uart_pattern_pop_pos(UART_PORT_NUM);
                    ESP_LOGI(TAG, "[UART_PATTERN_DET]: Pattern detected, pos: %d, buffered size: %d", pos, event.size);
                    if (pos != -1) {
                        // Read the data up to and including the pattern
                        // Note: Depending on pattern config, data might be before/after pattern in buffer
                        int read_len = uart_read_bytes(UART_PORT_NUM, dtmp, pos + PATTERN_CHR_NUM, pdMS_TO_TICKS(100));
                        dtmp[read_len] = '\0';
                        ESP_LOGI(TAG, "Pattern data: %s", (char*)dtmp);
                        // You might want to flush the rest of the buffer or handle it
                        uart_flush_input(UART_PORT_NUM); 
                    } else {
                         ESP_LOGW(TAG, "Pattern queue is empty after detection event!");
                         // This case might happen if the pattern detection logic has specific behaviors
                         // or if data is read by another mechanism before this event is processed.
                         // Reading all available data might be a fallback.
                         int len = uart_read_bytes(UART_PORT_NUM, dtmp, event.size, pdMS_TO_TICKS(100));
                         if (len > 0) {
                            dtmp[len] = '\0';
                            ESP_LOGI(TAG, "Data after pattern event (pos -1): %s", (char*)dtmp);
                         }
                    }
                    ESP_LOGI(TAG, "Pattern '+++' detected!");
                    // Perform action for pattern detection
                    const char *response = "Pattern ACK\r\n";
                    uart_write_bytes(UART_PORT_NUM, response, strlen(response));
                    break;
                default:
                    ESP_LOGI(TAG, "uart event type: %d", event.type);
                    break;
            }
        }
    }
    free(dtmp);
    dtmp = NULL;
    vTaskDelete(NULL);
}

void app_main(void) {
    uart_config_t uart_config = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };

    ESP_LOGI(TAG, "Configuring UART%d with Event Queue & Pattern Detection...", UART_PORT_NUM);

    //Install UART driver, and get the queue.
    ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_RX_BUF_SIZE * 2, 0, 20, &uart_event_queue, 0));
    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));

    //Set UART pins
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TXD_PIN, UART_RXD_PIN, UART_RTS_PIN, UART_CTS_PIN));

    //Set pattern interrupt, one pattern is +++, the timeout is 100ms between chars
    // In ESP-IDF v5.x, uart_enable_pattern_det_baud_intr is preferred
    // For simple identical character patterns:
    uart_pattern_t pattern_config = {
        .pattern_chr[0] = PATTERN_CHAR,
        .pattern_chr[1] = PATTERN_CHAR,
        .pattern_chr[2] = PATTERN_CHAR,
        .chr_num = PATTERN_CHR_NUM,
        .chr_tout = 9, // Max ticks between pattern characters (at current baud rate)
        .post_idle = 0, // Ticks of idle after pattern
        .pre_idle = 0,  // Ticks of idle before pattern
    };
    ESP_ERROR_CHECK(uart_enable_pattern_det_baud_intr(UART_PORT_NUM, PATTERN_CHAR, PATTERN_CHR_NUM, 9, 0, 0));


    //Reset an pattern queue after each interrupt by writing an EOF character.
    //In ESP-IDF older versions: uart_pattern_queue_reset(UART_PORT_NUM, 20);
    // For v5.x, the driver often handles this, or you manage the queue via uart_pattern_pop_pos
    // For this example, we explicitly flush input after pattern detection.

    ESP_LOGI(TAG, "UART driver with event queue installed.");
    xTaskCreate(uart_event_task, "uart_event_task", 2048*2, NULL, 12, NULL);
}

Build and Run:

  1. Connect ESP32 UART1 (TXD=GPIO17, RXD=GPIO16) to your PC via a USB-to-Serial adapter.
  2. Build, flash, and monitor.
  3. Open a serial terminal (115200, 8N1).
  4. Observe:
    • Type any characters; they should be echoed back due to the UART_DATA event handling.
    • Type “+++”. You should see the UART_PATTERN_DET event logged, and “Pattern ACK” sent back. The log will also show the position of the detected pattern.

Example 3: Basic RS485 Echo (Conceptual)

This example sets up UART for RS485 half-duplex mode. It requires an external RS485 transceiver IC (like MAX485). The RTS pin is used for DE/RE control.

Code (main/main.c):

C
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define UART_PORT_NUM      UART_NUM_2 // Using UART2 for RS485
#define UART_RX_BUF_SIZE   (1024)

// Define UART pins - CHOOSE PINS APPROPRIATE FOR YOUR BOARD
// RTS pin will be used for RS485 DE/RE control
#define RS485_TXD_PIN  (GPIO_NUM_25) 
#define RS485_RXD_PIN  (GPIO_NUM_26)
#define RS485_RTS_PIN  (GPIO_NUM_27) // This pin controls DE/RE on the transceiver
#define RS485_CTS_PIN  (UART_PIN_NO_CHANGE) // Not used for RS485 half-duplex

static const char *TAG = "RS485_ECHO";

// This task will listen for data on RS485 and echo it back
static void rs485_echo_task(void *arg) {
    uint8_t* data = (uint8_t*) malloc(UART_RX_BUF_SIZE);
    ESP_LOGI(TAG, "RS485 echo task started. Waiting for data on UART%d...", UART_PORT_NUM);

    while (1) {
        int len = uart_read_bytes(UART_PORT_NUM, data, (UART_RX_BUF_SIZE - 1), pdMS_TO_TICKS(200));
        if (len > 0) {
            data[len] = '\0';
            ESP_LOGI(TAG, "RS485 Read %d bytes: '%s'", len, (char*)data);
            
            // Echo data back
            // The driver in RS485 mode should handle RTS (DE/RE) toggling automatically
            // around uart_write_bytes.
            uart_write_bytes(UART_PORT_NUM, (const char*)data, len);
            ESP_LOGI(TAG, "RS485 Echoed %d bytes.", len);
        }
    }
    free(data);
}

void app_main(void) {
    uart_config_t uart_config = {
        .baud_rate = 9600, // Common baud rate for RS485 devices
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, // Flow control not typically used with RS485 half-duplex this way
        .source_clk = UART_SCLK_DEFAULT,
    };

    ESP_LOGI(TAG, "Configuring UART%d for RS485 Half-Duplex mode...", UART_PORT_NUM);

    ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0));
    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));

    ESP_LOGI(TAG, "Setting RS485 pins: TXD=%d, RXD=%d, RTS(DE/RE)=%d", 
             RS485_TXD_PIN, RS485_RXD_PIN, RS485_RTS_PIN);
    // For RS485, RTS is used for direction control. CTS is not used.
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, RS485_TXD_PIN, RS485_RXD_PIN, RS485_RTS_PIN, UART_PIN_NO_CHANGE));
    
    // Set RS485 half-duplex mode
    ESP_ERROR_CHECK(uart_set_mode(UART_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));

    // Optional: Configure RS485 timing if needed (e.g., for specific transceivers)
    // uart_rs485_hd_opts_t rs485_hd_opts = {
    //     .tx_delay_ms = 1, // Adjust as needed
    //     .rx_delay_ms = 1, // Adjust as needed
    // };
    // ESP_ERROR_CHECK(uart_set_rs485_hd_opts(UART_PORT_NUM, &rs485_hd_opts));


    ESP_LOGI(TAG, "UART%d configured for RS485.", UART_PORT_NUM);
    xTaskCreate(rs485_echo_task, "rs485_echo_task", 2048*2, NULL, 10, NULL);
}

Build and Run:

  1. Hardware Connection:
    • Connect ESP32 RS485_TXD_PIN to DI (Driver Input) of your RS485 transceiver.
    • Connect ESP32 RS485_RXD_PIN to RO (Receiver Output) of your RS485 transceiver.
    • Connect ESP32 RS485_RTS_PIN to DE (Driver Enable) and /RE (Receiver Enable) pins of the transceiver. (Often DE and /RE are jumpered or controlled together; DE high for transmit, /RE low for receive).
    • Connect the A and B lines of the transceiver to your RS485 bus.
    • Power the transceiver and connect its GND to ESP32 GND.
    • You’ll need another RS485 device or a USB-to-RS485 adapter connected to your PC to communicate.
  2. Build, flash, and monitor.
  3. Observe: Send data from your PC (via USB-to-RS485 adapter) to the ESP32. The ESP32 should receive it and echo it back over the RS485 bus. The RTS pin should automatically toggle to control transmit/receive mode on the transceiver.

Variant Notes

While the core UART functionalities are similar across ESP32 variants, there can be subtle differences:

  • Number of UART Controllers:
    • ESP32: 3 UARTs
    • ESP32-S2: 2 UARTs
    • ESP32-S3: 3 UARTs
    • ESP32-C3: 2 UARTs
    • ESP32-C6: 2 UARTs
    • ESP32-H2: 2 UARTsThis impacts how many independent advanced UART configurations you can have.
  • RS485 Support: All variants with UART controllers generally support RS485 mode configuration through the driver. The key is the availability of a suitable GPIO for RTS (DE/RE) control alongside TX and RX.
  • Flow Control Pins: Ensure the chosen RTS/CTS pins are not conflicting with other peripherals and are correctly mapped. The GPIO matrix provides flexibility.
  • Pattern Detection Capabilities: The underlying hardware capabilities for pattern detection (number of pattern units, complexity of patterns) might have slight variations. However, the ESP-IDF driver abstracts most of this. The uart_pattern_t structure and related functions are generally consistent.
  • Wakeup Sources: UART wakeup from light sleep is a common feature, but always verify specific wakeup capabilities in the variant’s datasheet and Technical Reference Manual (TRM).
  • IO MUX and GPIO Matrix: All modern ESP32 variants use the GPIO matrix for flexible pin assignment, so UART signals (including RTS/CTS) can usually be routed to many different GPIO pins. However, some pins might have default functions or strapping requirements to be aware of.

Tip: Always consult the latest ESP-IDF documentation and the specific datasheet/TRM for your chosen ESP32 variant to confirm pin capabilities and peripheral details.

Common Mistakes & Troubleshooting Tips

Feature Area Mistake / Issue Symptom(s) Troubleshooting / Solution
Hardware Flow Control (RTS/CTS) Incorrect RTS/CTS Wiring Communication stalls, works unreliably (especially under load), or data loss. uart_write_bytes may block or return unexpected values. Verify wiring:
  • ESP32 RTS pin → Other device’s CTS pin.
  • ESP32 CTS pin → Other device’s RTS pin.
  • Ensure common GND.
Confirm configuration: Both devices must have hardware flow control enabled with compatible settings (e.g., UART_HW_FLOWCTRL_CTS_RTS). Check rx_flow_ctrl_thresh.
Hardware Flow Control (RTS/CTS) One Device Lacks Flow Control Support/Configuration Data overrun on the device expecting flow control if the sender doesn’t honor CTS. Or, sender stalls if receiver never asserts CTS. Ensure both ends support and are configured for RTS/CTS. If one end cannot support it, disable hardware flow control on the ESP32 (UART_HW_FLOWCTRL_DISABLE) and implement other strategies (e.g., larger buffers, slower baud rate, application-level ACKs) if data loss is an issue.
Software Flow Control (XON/XOFF) XON/XOFF Characters in Data Stream Communication pauses unexpectedly or data is corrupted if XON/XOFF characters (ASCII 17/19) appear as part of the actual data and no escaping mechanism is used. Implement data escaping: If XON/XOFF must be part of the data, use a character-stuffing or escaping protocol.
Choose different control characters if the protocol allows.
Ensure application logic correctly identifies and acts upon XON/XOFF, and doesn’t treat them as regular data.
Software Flow Control (XON/XOFF) No or Incorrect XON/XOFF Handling Flow control doesn’t work; data loss occurs if receiver sends XOFF but transmitter ignores it, or transmitter waits indefinitely if XON is missed. Verify application logic: Ensure the receiving side correctly sends XOFF when its buffer is nearly full and XON when space is available. Ensure the transmitting side correctly pauses on XOFF and resumes on XON.
The ESP-IDF’s built-in software flow control support via sw_flow_ctrl_en might be limited; robust implementation often requires significant application-level code.
UART Events & Event Queue Event Queue Not Configured or Incorrectly Sized No events received by the handling task (xQueueReceive blocks indefinitely). Events might be lost if queue is too small and fills up. Pass a valid queue handle and non-zero queue size to uart_driver_install().
Size the queue appropriately for the expected event rate.
UART Events & Event Queue Not Reading Data After UART_DATA Event RX buffer fills up, leading to UART_BUFFER_FULL or UART_FIFO_OVF events and data loss, even though UART_DATA events were received. In the event handling task, upon receiving a UART_DATA event, promptly call uart_read_bytes() to read the amount of data indicated by event.size.
UART Events & Event Queue Blocking Operations in Event Handler Event task becomes unresponsive, delaying handling of subsequent UART events, potentially leading to buffer overflows. Keep event handling logic non-blocking or very short. Offload lengthy processing to other tasks if necessary.
UART Pattern Detection Pattern Not Detected The UART_PATTERN_DET event is never triggered despite sending the pattern. Verify pattern configuration: Check uart_pattern_t settings (pattern_chr[], chr_num).
Ensure uart_enable_pattern_det_baud_intr() (or equivalent) is called correctly.
Check chr_tout if the pattern characters are sent with delays.
Ensure the baud rate matches the sender.
UART Pattern Detection Incorrect Data Handling After Pattern Detection Data before, including, or after the pattern is misinterpreted or lost. uart_pattern_pop_pos() returns -1 unexpectedly. Use uart_pattern_pop_pos() to get the offset of the detected pattern in the RX buffer.
Read the appropriate amount of data using uart_read_bytes().
Be aware that other data might be in the buffer before or after the pattern.
Consider flushing input (uart_flush_input()) after processing the pattern if subsequent data is not needed or to reset state.
UART Wake-up from Light Sleep ESP32 Fails to Wake Up on UART Activity Device remains in light sleep despite data being sent to its RX pin. Verify configuration:
  • uart_set_wakeup_threshold() called with an appropriate value (e.g., 3 or more pulses).
  • esp_sleep_enable_uart_wakeup() called for the correct UART port.
  • Ensure the UART RX pin is correctly configured and not floating.
  • The signal edges must be clean and meet voltage level requirements.
UART Wake-up from Light Sleep Spurious Wakeups from UART Device wakes up from light sleep frequently without valid UART communication. Increase wakeup threshold: Use a higher value in uart_set_wakeup_threshold() to make it less sensitive to noise.
Check for noise on the RX line: Ensure proper shielding or add a pull-up resistor if the line is susceptible to noise.
RS485 Communication Mode No Communication or Data Corruption Data sent is not received, or received data is garbled. Only one-way communication might work. Verify RS485 transceiver wiring:
  • ESP32 TXD → Transceiver DI.
  • ESP32 RXD → Transceiver RO.
  • ESP32 RTS (or other GPIO) → Transceiver DE/RE (ensure correct logic for transmit/receive enable).
  • Transceiver A/B lines connected correctly to the bus.
  • Common GND between ESP32 and transceiver.
Check bus termination: Ensure 120-ohm termination resistors are at both ends of the main RS485 bus trunk (not on every node).
Ensure uart_set_mode(uart_port, UART_MODE_RS485_HALF_DUPLEX) is called.
Verify baud rate and other UART parameters match across all devices on the bus.
RS485 Communication Mode DE/RE Control Issues Transceiver stuck in transmit or receive mode. Data collisions if multiple devices transmit simultaneously. Ensure the GPIO pin used for DE/RE control (typically RTS) is correctly configured and that the ESP-IDF driver (UART_MODE_RS485_HALF_DUPLEX) is managing its state automatically.
Some transceivers might require specific delays for DE/RE assertion/de-assertion; check uart_set_rs485_hd_opts() if needed.
Implement proper bus arbitration/master-slave protocol at the application level to prevent collisions.

Exercises

  1. Hardware Flow Controlled Data Logger:
    • Set up UART1 with hardware flow control (RTS/CTS).
    • Write a task that simulates generating sensor data (e.g., an incrementing counter or random numbers) at a high rate (e.g., every 10ms).
    • Transmit this data over UART1.
    • Connect UART1 to a PC via a USB-to-Serial adapter that supports RTS/CTS.
    • In your serial terminal software, find a way to temporarily stop receiving data or de-assert its RTS signal (some terminals allow manual RTS/CTS control, or you can quickly close and reopen the port).
    • Observe if the ESP32 pauses its data transmission when flow control is asserted by the PC, and resumes when flow control is de-asserted. Log flow control status changes if possible.
  2. UART Command Parser with Event-Driven Pattern Detection:
    • Configure UART1 with an event queue.
    • Enable pattern detection for three distinct command patterns: “CMD_LED_ON\r”, “CMD_LED_OFF\r”, and “CMD_STATUS\r” (Note: \r is carriage return, ASCII 13. You might need to configure multiple pattern detectors or handle this in application logic after a simpler pattern).
    • When “CMD_LED_ON\r” is detected, turn on an LED.
    • When “CMD_LED_OFF\r” is detected, turn off the LED.
    • When “CMD_STATUS\r” is detected, send back the current LED status (“LED is ON\r\n” or “LED is OFF\r\n”) via UART.
    • Handle other incoming data (not matching patterns) by logging it or echoing it.
  3. RS485 Master-Slave Communication (ESP32 as Master):
    • Configure UART2 on your ESP32 for RS485 half-duplex mode.
    • Assume you have an RS485 slave device (this could be another ESP32 configured as a slave, or a commercial RS485 device like a Modbus sensor/actuator).
    • Define a simple command protocol (e.g., Master sends “GET_TEMP”, Slave responds with “TEMP:25.5”).
    • Implement the master logic on your ESP32:
      • Periodically send a command (e.g., “GET_DATA”) to a specific slave address (if your protocol supports addressing).
      • After sending, switch to receive mode and wait for a response from the slave with a timeout.
      • Log the received response.
    • Focus: The ESP32 master’s UART configuration, DE/RE control (which should be automatic by the driver in RS485 mode), and the send/receive logic. You don’t need to implement the slave for this exercise if you don’t have one, but design the master to interact with a hypothetical slave.

Summary

  • Hardware Flow Control (RTS/CTS) uses dedicated lines to prevent buffer overflows and data loss, crucial for high-speed or unreliable links.
  • Software Flow Control (XON/XOFF) uses in-band characters, saving pins but with some overhead and data restrictions.
  • UART Event Queues enable asynchronous, efficient handling of data reception, errors, and specific conditions like pattern detection.
  • Pattern Detection allows the UART to identify pre-configured character sequences in the RX stream, useful for command parsing or packet synchronization.
  • UART activity can be configured to wake the ESP32 from light sleep, enabling power-saving applications that still need to be responsive to serial input.
  • RS485 Mode configures the UART for half-duplex, differential communication, suitable for noisy industrial environments and multi-drop networks, requiring an external transceiver and often using the RTS pin for direction control.
  • Advanced UART features are generally well-supported across ESP32 variants, with the main differences being the number of available UART controllers.

Further Reading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top