📡 ESP32 Serial Communication

A complete beginner-to-intermediate guide covering the Serial buffer, reading & writing data, ASCII encoding, and using the Serial Monitor for debugging.

1. Introduction

Serial communication is one of the very first skills you need on the ESP32. It lets your board talk to your computer (or another device) over a simple two-wire protocol — one wire to transmit (TX) and one to receive (RX).

In this guide we build a small program step by step: the ESP32 will listen for a character sent from the computer, store it, and echo it back after a short delay. Along the way we explain why each function works the way it does — not just how to use it.

Computer
(Serial Monitor)
ESP32
RX Buffer
Your Sketch
(loop function)
Back to Computer
(TX)

2. Getting Started: Serial.begin()

Before any serial communication can happen, you must "open" the communication channel and tell the ESP32 how fast to talk. This is done inside setup() with a baud rate — the number of bits transmitted per second.

void setup() {
  Serial.begin(9600);   // open serial port at 9600 baud
}
Why 9600? It is one of the most common, reliable default speeds supported by nearly every board and terminal program. Both sides (ESP32 and Serial Monitor) must use the same baud rate or the data will look like garbage.

A short delay is often added right after Serial.begin() so the port has time to fully initialize before you start sending or receiving data — especially useful on boards where the USB-to-serial chip needs a moment to enumerate.

3. Understanding the Serial Buffer

Whenever a byte is sent to the ESP32 over serial, it doesn't go straight into your variables. It is first stored in a small area of memory called the serial (RX) buffer — a temporary holding area, like a mailbox, that queues up incoming bytes until your program reads them.

H
E
L
L
O
·
·
·
·
·

Incoming bytes fill the RX buffer left to right. Your program later drains it using Serial.read().

The size of this buffer depends on the microcontroller/board you are using:

Board / PlatformDefault RX Buffer Size
Classic Arduino (Uno / ATmega328P)64 characters (63 usable)
ESP32 (Arduino core)256 bytes
This is why if you repeat a large-data-send experiment on an ESP32, you will receive the complete data because the ESP32's buffer is 256 bytes, whereas a classic Arduino with a much smaller buffer might drop bytes if it's busy doing something else.

If you don't read from the buffer, the data just sits there. If you try to read when the buffer is empty, you'll get nothing useful back — which is exactly the problem the next function solves.

4. Checking for Data: Serial.available()

Since we can't blindly read from an empty buffer, we need a way to ask: "Has anything arrived yet?" That is exactly what Serial.available() does.

Serial.available() returns the number of bytes available in the RX buffer, or 0 if no data is available.

if (Serial.available() > 0) {
  // at least one byte has been received — safe to read
}

The logic is simple: if the returned value is greater than zero, it means at least one character has been received and is waiting in the buffer, so the code inside the if block is safe to execute.

5. Reading the Data: Serial.read()

Once we know data is available, we pull it out of the buffer with Serial.read(). This reads a single byte from the Serial port and returns the byte read (0–255), or -1 if no data is available.

char incomingByte;

void loop() {
  if (Serial.available() > 0) {
    incomingByte = Serial.read();   // pull one byte out of the buffer
  }
}
Important: Always check Serial.available() before calling Serial.read(). Reading an empty buffer wastes a read cycle and returns a meaningless value (-1).

6. Why Everything is a Number: ASCII

Here's a detail that trips up many beginners: when you type a character into the Serial Monitor and hit send, the ESP32 does not receive the character itself — it receives its ASCII code, a number that represents that character.

You TypeASCII Value Actually Sent
'0'48
'A'65
'a'97

This matters a lot when you decide how to send data back to the computer, which is exactly what the next section covers.

7. Sending Data Back: print() vs. write()

The ESP32's Serial object gives you two very different ways to send bytes back out, and mixing them up is a common source of confusion.

Serial.print() — human-readable output

Use this when you want the Serial Monitor to display the character exactly as you'd expect a human to read it. If you received the ASCII value for the letter "E" and want to echo back the letter "E" (not the raw number), use:

Serial.print(incomingByte);   // prints the character itself, e.g. "E"
Serial.println();             // moves to a new line

Serial.write() — raw byte output

This sends the raw numeric byte value instead of a formatted, human-readable string. For example, if you write the number 65 with Serial.write(), the Serial Monitor will display the letter "A" (its ASCII character), rather than the digits "65".

Serial.write(65);   // sends the raw byte 65 → appears as 'A' on the monitor
Rule of thumb: Use Serial.print() for readable text/numbers in the monitor. Use Serial.write() when you need to send raw binary/byte values to another device or protocol.

8. Putting It All Together: A Simple Echo Program

Now let's combine everything above into one working sketch. The ESP32 waits for a character, waits 3 seconds (to make the behavior visible), then echoes the character back to the Serial Monitor.

char incomingByte;

void setup() {
  Serial.begin(9600);
  delay(500);
  Serial.println("ESP32 Serial Echo — Ready!");
}

void loop() {
  if (Serial.available() > 0) {
    incomingByte = Serial.read();   // grab the byte from the buffer

    delay(3000);                    // simulate some processing time

    Serial.print(incomingByte);     // echo the character back
    Serial.println();               // newline for a clean display
  }
}

Try sending the capital letter E from the Serial Monitor — after about 3 seconds, you should see E echoed back. Send 0 and you'll get 0 back, because Serial.print() is printing the character representation, not the raw ASCII number.

9. Using the Serial Monitor

Follow these steps to test the program above:

1. Connect the ESP32 to your computer via USB
2. Select the correct board and COM port in the Arduino IDE
3. Click Upload
4. Open Tools → Serial Monitor
5. Set the baud rate dropdown to 9600
6. Type a character, e.g. E
7. Click Send
8. Wait ~3 seconds for the echoed reply

Newline Behavior Matters

By default, the Serial Monitor automatically appends a newline character every time you click "Send." This means an extra byte is transmitted along with your intended character. If you don't want this, change the line-ending dropdown (often labeled "No line ending") in the monitor settings.

If you send the digit 5 with the newline option enabled, the ESP32 actually receives two bytes: the character '5' and a newline character. If your code isn't expecting that, you may see unexpected extra output or an extra empty line.

10. Serial as a Debugging Tool

Beyond talking to sensors or other boards, one of the biggest practical benefits of serial communication is debugging. As your embedded programs grow more advanced, it becomes hard to know:

The simplest fix is to sprinkle Serial.print() statements ("print traces") at key points in your code:

void loop() {
  Serial.println("Entering loop...");

  if (Serial.available() > 0) {
    Serial.println("Data detected in buffer!");
    char c = Serial.read();
    Serial.print("Received: ");
    Serial.println(c);
  }
}
This technique — printing simple markers like "checkpoint 1", "checkpoint 2" — is one of the most effective, low-cost ways to trace program flow and catch bugs early, especially before you have more advanced debugging tools set up.

11. Quick Reference: Core Serial Functions

FunctionPurpose
Serial.begin(baud)Opens the serial port at the given baud rate (must be called first, in setup())
Serial.available()Returns the number of bytes available in the RX buffer, or 0 if no data is available
Serial.read()Reads a single byte from the Serial port; returns the byte read (0–255), or -1 if no data is available
Serial.print(x)Sends data as human-readable text/characters
Serial.println(x)Same as print(), but adds a newline afterward
Serial.write(x)Sends a raw byte value directly
Serial.flush()Waits for outgoing data to finish transmitting
Serial.setRxBufferSize(size)Must be called before begin() to take effect; default RX buffer size is 256 bytes on ESP32
Espressif's official Serial (UART) API documentation lists every available function with its parameters, return values, and examples — an excellent reference once you're comfortable with these basics and want to explore advanced features like callbacks, custom pins, or multiple UART ports.