ESP32 Serial Communication

A beginner-friendly, in-depth guide to UART communication between the ESP32 and your computer — the very first step into embedded systems and IoT.

1. Introduction

In this guide, we explore how the ESP32 communicates with a computer using serial communication. This is traditionally the very first topic taught in any embedded systems or IoT course, and for very good reasons which we will unpack below.

Goal of this lesson: Understand what serial communication is, why it is used, how the ESP32's on-board USB interface works, and how to write your first serial program.

2. Why Start With Serial Communication?

When teaching embedded systems, it is tempting to jump straight into flashy features like Wi-Fi or Bluetooth. However, starting with serial communication has several practical advantages:

3. The Basics of Serial Communication

Serial communication is simply the process of sending data one bit at a time, sequentially, over a single wire (per direction), as opposed to parallel communication where multiple bits travel simultaneously over multiple wires.

Definition UART stands for Universal Asynchronous Receiver/Transmitter. It's not a communication protocol like SPI and I2C, but a physical circuit in a microcontroller, or a stand-alone IC. A UART's main purpose is to transmit and receive serial data.

On the ESP32, this communication happens between the microcontroller and a connected device — most commonly a computer, but it is equally applicable to other peripherals such as:

Good to know: The ESP32 has 3 UART peripherals. One of which is hard-wired to the USB-TTL converter on the ESP32 dev board itself, and this is what is used to send data from the ESP32 to a PC over the UART serial port. The other two UARTs can be freely used for your own external devices.

4. The USB-to-UART Bridge Chip

Modern laptops and desktops do not have a native serial (UART) port anymore. So how does the ESP32 talk to your computer over a simple USB cable?

The answer lies in a small chip soldered onto every ESP32 development board — commonly the CP2102 (Silicon Labs) or CH340 chip. This chip converts the USB signals from a computer into UART signals that the ESP32 can understand, and provides a convenient way to program the ESP32 and allows it to communicate with the computer via USB.

Computer (USB Host) USB USB-to-UART Bridge Chip (CP2102 / CH340) TX → RX RX ← TX ESP32 Microcontroller Data flows: Computer ⇆ USB ⇆ Bridge Chip ⇆ UART (TX/RX) ⇆ ESP32
Fig. 1 — How the ESP32 communicates with a computer through the on-board USB-to-UART bridge chip.

Why the TX and RX lines must cross

Notice in the diagram that the transmit (TX) line of one device connects to the receive (RX) line of the other, and vice versa. This is a fundamental UART wiring rule: connect the TX pin on one device to the RX pin on the connected device, and vice versa.

Common mistake: Connecting TX-to-TX and RX-to-RX will result in no communication at all. Always cross the lines — think of it as one person's "mouth" (TX) needing to connect to the other person's "ear" (RX).

5. Asynchronous vs. Synchronous Communication

UART communication is asynchronous. This is one of its most defining — and sometimes confusing — characteristics. Let's break down what that means.

What "asynchronous" really means

In synchronous communication (like SPI), a dedicated clock wire is shared between devices, so both sides know exactly when to sample each bit. In asynchronous communication such as UART, no clock signal is shared between the ESP32 and the computer.

Real-world analogy: Imagine meeting someone from a different country who speaks noticeably faster or slower than you. Until you both agree on a comfortable, matching pace of speech, effective conversation will be difficult — words will blur together, or gaps will feel too long. Serial devices face the exact same problem: without a shared clock, both ends must agree in advance on the exact same "speaking speed" to correctly interpret each bit.

Because there is no shared clock, the receiving UART cannot simply "count" bits arriving on the wire. Instead, the transmitting UART frames each byte of data with special marker bits:

Instead of a clock signal, the transmitting UART adds start and stop bits to the data packet being transferred. These bits define the beginning and end of the data packet so the receiving UART knows when to start reading the bits. When the receiving UART detects a start bit, it starts to read the incoming bits at a specific frequency known as the baud rate.

UART Data Frame (example: sending the letter "A" = 01000001) START 1 0 0 0 0 0 1 0 STOP 8 data bits sit between the START bit (signals beginning) and STOP bit (signals end) Both devices must sample these bits at the same rate — the Baud Rate
Fig. 2 — Anatomy of a UART data frame using start and stop bits instead of a shared clock.

6. Understanding Baud Rate

Baud rate is a measure of the speed of data transfer, expressed in bits per second (bps). It is essentially the "agreed talking speed" between the ESP32 and the computer that we described in the analogy above.

Critical rule: Both UARTs must operate at about the same baud rate — the baud rate between the transmitting and receiving UARTs can only differ by about 3% before the timing of bits gets too far off. If the two ends use different baud rates, the received data will look like garbage characters on the Serial Monitor.

Common Baud Rates

Baud RateTypical Use Case
9600Classic default rate for many legacy sensors and modules
19200Slightly faster, used for some GSM/GPS modules
38400Mid-range speed for certain peripherals
115200Most common default for the ESP32 Arduino core and Serial Monitor
230400 and aboveHigh-speed data logging / debugging

On the ESP32 specifically: when you use Serial.begin(115200) you are initializing a serial communication using UART0 at a 115200 baud rate.

7. Full-Duplex Communication (TX and RX Lines)

UART uses two separate wires — one for transmitting (TX) and one for receiving (RX). UART is a simple, two-wire communication protocol that uses a TX (transmit) and RX (receive) line for full-duplex communication. This means data can technically flow in both directions simultaneously, because each direction has its own dedicated wire.

ESP32 Device TX → RX RX ← TX
Full-Duplex: both directions active at once, each on its own wire.
ESP32 Device Shared line (one direction at a time)
Half-Duplex: devices take turns, common in shared-bus setups like RS-485.

In practice, most UART-based communication with the ESP32 (including talking to the Serial Monitor) works as full-duplex, but many applications use it in a half-duplex, "send-then-wait-for-reply" style for simplicity.

8. Writing Your First Serial Program on the ESP32

Now let's put the theory into practice. The Arduino Serial library gives us simple functions to send and receive data over UART0 — the same UART wired to the on-board USB bridge chip.

Example 1 — Basic "Hello World" over Serial

void setup() {
  Serial.begin(115200);        // Start UART0 at 115200 baud
}

void loop() {
  Serial.println("Hello, ESP32!");  // Send text to the Serial Monitor
  delay(1000);                       // Wait 1 second
}

Example 2 — Two-Way Communication (Computer ⇆ ESP32)

This example listens for incoming characters typed into the Serial Monitor and echoes back what the ESP32 received: void setup() { Serial.begin(9600); pinMode(LED_BUILTIN, OUTPUT); } void loop() { if (Serial.available()) { String command = Serial.readStringUntil('\n'); if (command == "ON") { digitalWrite(LED_BUILTIN, HIGH); Serial.println("Turn LED ON"); } else if (command == "OFF") { digitalWrite(LED_BUILTIN, LOW); Serial.println("Turn LED OFF"); } } }

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');

    if (command == "ON") {
      digitalWrite(LED_BUILTIN, HIGH);
      Serial.println("Turn LED ON");
    }
    else if (command == "OFF") {
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("Turn LED OFF");
    }
  }
}

How to Upload and Test This Code

  1. Open Arduino IDE
  2. Tools → Board → select your ESP32 board
  3. Tools → Port → select the correct COM port
  4. Copy and paste the code above into a new sketch
  5. Click the Upload button (right arrow icon)
  6. Wait for "Done uploading" message
  7. Tools → Serial Monitor
  8. Set baud rate to match Serial.begin() value
  9. Type ON or OFF and press Send
Reminder: The baud rate selected in the Serial Monitor dropdown must match the value used in Serial.begin() in your code, otherwise you will only see garbled characters.

Using Additional UARTs

The ESP32 also supports extra hardware UARTs (UART1, UART2) for talking to external modules like GPS or GSM without interfering with the USB debug console: There are three serial ports on the ESP32 known as U0UXD, U1UXD and U2UXD, all working at 3.3V TTL level, corresponding to UART0, UART1, and UART2.

#define RXD2 16
#define TXD2 17

HardwareSerial mySerial(2);

void setup() {
  Serial.begin(115200);                       // UART0: USB debug console
  mySerial.begin(9600, SERIAL_8N1, RXD2, TXD2); // UART2: external device
}

void loop() {
  while (mySerial.available()) {
    Serial.write(mySerial.read());   // Forward UART2 data to USB console
  }
}

9. Key Takeaways

Next steps: With this foundation of serial communication in place, you are ready to explore more advanced ESP32 topics such as Wi-Fi, Bluetooth, I2C, SPI, and interrupt handling — all of which build upon the same core ideas of data framing and synchronization introduced here.