Mastering ESP32 UART | Complete Guide for Sending and Receiving Data


This comprehensive guide explores the fundamentals of ESP32 UART (Universal Asynchronous Receiver Transmitter), how to set up UART on ESP32, and provides practical examples of data transmission. Whether you’re a beginner or an experienced developer, mastering UART communication can greatly enhance your projects.

ESP32 UART

Introduction

UART is a hardware-based protocol used for serial communication between microcontrollers and other devices. It uses two wires: TX (transmit) and RX (receive). Data is transmitted one bit at a time on these two lines, and UART communication is asynchronous, meaning it doesn’t require a shared clock signal to control data transmission.

UART

UART communication uses two main lines:

  • TX (Transmit): Sends data to another device.
  • RX (Receive): Receives data from another device.

UART communication is asynchronous, meaning there is no need for a shared clock. Instead, both devices agree on a baud rate (the number of bits transmitted per second) to ensure proper data transfer.

ESP32 UART Hardware Features

We’ll use the UART port on the ESP32 for serial communication. Here’s the basic wiring setup:

  • TX Pin: Connect the ESP32 TX pin to the RX pin of the receiving device.
  • RX Pin: Connect the ESP32 RX pin to the TX pin of the sending device.
  • GND Pin: Connect the ESP32 GND pin to the GND of the external device.

Wiring Diagram:

ESP32 (TX) ----> External Device (RX)
ESP32 (RX) <---- External Device (TX)
GND -------------------- GND

Using the ESP32-PICO-KIT

Hardware Background:

  • GPIO16 and GPIO17 are the default pins for UART0 on the ESP32 module, which are connected to the USB-to-UART interface for programming and serial monitoring.
  • These pins are used internally and cannot be used for external UART communication.

Alternative Method:

  • Use GPIO21 (TX) and GPIO22 (RX) as the UART2 transmission and reception pins. These are general-purpose pins that can be used for UART or other peripheral communications, and are freely available on the ESP32-PICO-KIT.

Code for ESP32 UART

To initialize UART on the ESP32, we need to set up the baud rate, data bits, stop bits, and other parameters. The ESP32 supports up to three UART ports, each of which can be configured with different settings.

#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"

static const char *TAG = "uart_test";

// UART configuration
#define UART_NUM UART_NUM_2        // Using UART2
#define UART_TX_PIN GPIO_NUM_21    // TX Pin
#define UART_RX_PIN GPIO_NUM_22    // RX Pin
#define UART_BAUD_RATE 115200      // Baud rate
#define BUF_SIZE 256               // Buffer size

// UART initialization function
static void uart_init() {
    // Check if UART driver is already installed, and remove it if necessary
    if (uart_is_driver_installed(UART_NUM)) {
        uart_driver_delete(UART_NUM);
        vTaskDelay(pdMS_TO_TICKS(100));  // Allow time for proper cleanup
    }

    // UART configuration structure
    uart_config_t uart_config = {
        .baud_rate = UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .rx_flow_ctrl_thresh = 122,
        .source_clk = UART_SCLK_APB,
    };

    // Configure UART parameters
    esp_err_t err = uart_param_config(UART_NUM, &uart_config);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "UART parameter config failed with error: %d", err);
        return;
    }

    // Set UART pins
    err = uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "UART set pin failed with error: %d", err);
        return;
    }

    // Install UART driver
    err = uart_driver_install(UART_NUM, BUF_SIZE, BUF_SIZE, 0, NULL, 0);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "UART driver install failed with error: %d", err);
        return;
    }

    ESP_LOGI(TAG, "UART initialized successfully");
}

// UART transmission task
void uart_tx_task(void *arg) {
    const char* test_str = "Hi UART\n";
    while(1) {
        // Send test string
        if(uart_write_bytes(UART_NUM, test_str, strlen(test_str)) > 0) {
            ESP_LOGI(TAG, "Sent: %s", test_str);
        } else {
            ESP_LOGE(TAG, "Failed to send data");
        }
        vTaskDelay(pdMS_TO_TICKS(2000));  // Send data every 2 seconds
    }
}

// UART reception task
void uart_rx_task(void *arg) {
    uint8_t data[BUF_SIZE];  // Buffer to store received data
    const char* welcome_msg = "Hello, welcome!\n";

    while(1) {
        // Read data from UART
        int len = uart_read_bytes(UART_NUM, data, sizeof(data) - 1, pdMS_TO_TICKS(100));
        if(len > 0) {
            data[len] = '\0';  // Null-terminate the received string
            ESP_LOGI(TAG, "Received %d bytes: %s", len, data);

            // Check if received string matches "Hi UART"
            if (strncmp((char*)data, "Hi UART", 7) == 0) {
                uart_write_bytes(UART_NUM, welcome_msg, strlen(welcome_msg));  // Send welcome message
                ESP_LOGI(TAG, "Sent welcome message");
            }
        }
        vTaskDelay(pdMS_TO_TICKS(50));  // Add delay to avoid task overload
    }
}

void app_main(void)
{
    // Wait for the system to stabilize
    vTaskDelay(pdMS_TO_TICKS(1000));

    // Initialize UART
    uart_init();

    // Allow additional time for initialization to complete
    vTaskDelay(pdMS_TO_TICKS(100));

    // Create tasks for sending and receiving
    xTaskCreate(uart_tx_task, "uart_tx_task", 2048, NULL, 5, NULL);
    xTaskCreate(uart_rx_task, "uart_rx_task", 2048, NULL, 5, NULL);

    ESP_LOGI(TAG, "UART tasks created");
}

Code Explanation

The code configures the ESP32-PICO-KIT V4 to use UART2, with GPIO21 as the TX pin and GPIO22 as the RX pin. It implements a simple communication system with the following tasks:

  • ESP32 UART Initialization: The uart_init function configures the UART parameters, such as baud rate, data bits, stop bits, and flow control. It also sets the TX and RX pins (GPIO21 and GPIO22) and installs the UART driver.
  • ESP32 UART Transmission Task (uart_tx_task): This task sends the string “Hi UART” every 2 seconds. If the transmission is successful, it logs the sent message; if it fails, it logs an error.
  • ESP32 UART Reception Task (uart_rx_task): This task continuously listens for incoming data on UART2. If the received message is “Hi UART”, it responds with the string “Hello, welcome!”.

Output Explanation

ESP32 UART Initialization:

After successfully initializing the UART, the system logs the configuration of the TX pin (GPIO21) and RX pin (GPIO22). If there is any error during configuration, it will log the corresponding error message.

ESP32 UART Transmission Task (uart_tx_task):

The transmission task sends the string “Hi UART” every 2 seconds. If successful, it logs the following:

I (698413) uart_test: Sent: Hi UART

ESP32 UART Reception Task (uart_rx_task):

The reception task listens for incoming data. When it receives “Hi UART”, it sends back “Hello, welcome!” and logs:

I (722413) uart_test: Received 7 bytes: Hi UART
I (726413) uart_test: Sent welcome message

Conclusion

Mastering UART communication on the ESP32 opens up numerous possibilities for your projects. By understanding the basics and implementing the examples in this guide, you can integrate reliable and efficient serial communication into your designs. Whether for simple data transfer or more complex applications, UART is a versatile and valuable tool in the embedded systems world.

The ESP32 provides robust hardware support with multiple UART ports, making it an ideal choice for projects requiring serial communication. We hope this guide helps you get started quickly and successfully implement UART functionality in your next ESP32 project!