ESP32 Serial Communication

Understanding the Arduino Serial Library Functions with Practical Examples

1. Introduction

Now that we have a basic understanding of serial communication, it's time to explore the different library functions the ESP32 (Arduino core) provides for performing serial communication between the microcontroller and a host computer or another device.

The Arduino Serial library is extensive, but only a handful of functions are used in most day-to-day projects. This guide focuses on the most commonly used functions, explains what they do, and demonstrates them with real code you can upload to your ESP32.

For a complete reference of every function available in the Serial library, you can always check the official Arduino documentation, which lists all supported methods, overloads, and parameters in detail.

2. Commonly Used Serial Library Functions

Below is a summary table of the core functions you will use constantly when working with serial communication on the ESP32.

Function Purpose Typical Use
Serial.begin(baud) Initializes the serial port and starts communication at a specified baud rate. Called once, inside setup()
Serial.write(byte) Sends a single raw byte of data over the serial connection. Sending binary data, not human-readable text
Serial.print(data) Converts data (number, character, string, variable) to ASCII text and sends it. Displaying readable text/values on Serial Monitor
Serial.println(data) Same as print(), but also sends a newline character afterward. Printing lines of output, one per line
Serial.available() Checks whether there is incoming data waiting to be read in the serial buffer. Before attempting to read incoming data
Serial.read() Reads a single byte of incoming serial data from the buffer. Reading data sent from the computer to ESP32

2.1 Serial.begin() — Starting the Communication

Serial is the class name and begin() is the function used to start the communication. You need to specify the baud rate — the speed at which data will be transmitted. A very common value used is 9600.

Serial.begin(9600);
It is good practice to add a small delay (10–100 ms) right after Serial.begin() to allow the serial module to fully initialize before sending any data.

2.2 Serial.write() — Sending a Single Byte

Serial.write() is used to send a single raw byte from the microcontroller to another connected device. Unlike print(), it does not convert the value to readable text — it sends the exact numeric byte value.

2.3 Serial.print() / Serial.println() — Sending Readable Data

Serial.print() is a very flexible function — you can pass it a single byte, a character, a string, or even a variable. It automatically converts whatever you pass into ASCII text before sending it to the other device, making it human-readable.

Serial.println() works exactly the same way, but it additionally sends a newline character at the end, so the next output appears on a fresh line.

2.4 Serial.available() and Serial.read() — Receiving Data

Serial.available() is used to check whether there is data waiting on the serial line to be received. Serial.read() is then used to actually read that incoming data, one byte at a time.

3. Building the Program Step by Step

Let's write a simple program that demonstrates Serial.begin() and Serial.println() in action.

3.1 Step-by-Step Construction

1
Open a new blank sketch in the Arduino IDE.
2
Call Serial.begin(9600) inside setup() to initialize communication.
3
Add a short delay (e.g., 100 ms) to let the serial module initialize.
4
Use Serial.println() to print a welcome message.
5
Add a 2-second delay using the delay() function.
6
Save the sketch (e.g., name it "Serial") in an accessible location.

3.2 Complete Code

// Basic Serial Communication Example - ESP32

void setup() {
  Serial.begin(9600);        // Start serial communication at 9600 baud
  delay(100);                // Small delay to let Serial module initialize
}

void loop() {
  Serial.println("Welcome to ESP32 Serial Communication");
  delay(2000);                // Wait 2 seconds before repeating
}
        

3.3 Printing a Variable

Let's extend the example by declaring a global variable and printing its value alongside our welcome message, incrementing it each time through the loop.

// Serial Communication with a Counter Variable

int i = 0;   // Global variable

void setup() {
  Serial.begin(9600);
  delay(100);
}

void loop() {
  Serial.print("Welcome to Serial Communication, i = ");
  Serial.println(i);   // print value and move to new line
  i++;                  // increment the counter
  delay(2000);
}
        
Each time the board resets, the counter i starts again from 0, then increases as 1, 2, 3, 4... and so on, printed once every 2 seconds.

4. Uploading the Code & Using the Serial Monitor

4.1 Upload Procedure

Follow these commands one at a time in the Arduino IDE:

Tools → Board → (Select your ESP32 board) Tools → Port → (Select the correct COM port) Sketch → Upload (or press Ctrl+U)
Upload Process Flow

Write Code Compile Connect Board Upload Run on ESP32
Make sure the board is physically connected to your computer via USB before uploading. The IDE first compiles the sketch, then transfers the compiled binary to the board.

4.2 Opening the Serial Monitor

To view the output being sent from the ESP32, we need a terminal-like tool capable of reading serial ports — either physical UART ports or the virtual USB-serial ports created by boards like the ESP32. The Arduino IDE has this built in.

Tools → Serial Monitor (or click the Serial Monitor icon, top-right corner)

4.3 Matching the Baud Rate

The Serial Monitor has its own baud rate setting, usually found at the bottom-right of the monitor window. This value must match the baud rate you set in Serial.begin() in your code.

Baud Rate Mismatch Effect

Wrong Baud Rate Garbage Characters

Correct Baud Rate (9600) Readable Text Output
If the baud rate selected in the Serial Monitor does not match the one used in Serial.begin(), the computer will not be able to correctly interpret the incoming bits, resulting in garbled/garbage characters on screen. Selecting the same rate (e.g., 9600) in both places fixes this immediately.

5. Understanding ASCII: print() vs write()

This is one of the most important — and most commonly misunderstood — concepts for beginners working with serial communication.

5.1 The Core Difference

Function What It Sends Example: value 0
Serial.print(0) / println(0) Converts the number to its text (ASCII) representation Sends the character '0' (ASCII decimal 48)
Serial.write(0) Sends the raw byte value as-is, no conversion Sends the raw byte 0 (ASCII NULL character)
The decimal value 0 is not ASCII "zero" by default — it's the ASCII NULL character. Serial.print()/println() will correctly convert a numeric 0 into the printable character '0'. But Serial.write() sends the raw byte, which for value 0 is a non-printable NULL character, not the digit zero.

5.2 A Quick Look at the ASCII Table

Characters below decimal value 32 are non-printable control characters. Printable characters begin around decimal 33 ('!') and continue through numbers, letters, and symbols. Digits '0'–'9' correspond to decimal values 48–57.

0NULL
33!
480
491
502
513
524
535
546
557
568
579

5.3 Demonstration: write() vs println()

// Demonstrating raw byte write() vs ASCII println()

int i = 0;

void setup() {
  Serial.begin(9600);
  delay(100);
}

void loop() {
  Serial.write(i);     // sends RAW byte value of i (not text)
  i++;
  delay(500);
}
        

When this code runs, the Serial Monitor will show mostly blank/garbage output for values below 33, since those are non-printable control characters. Once i reaches 33 ('!'), printable characters begin appearing. By the time it reaches 48–57, you will see the actual digit characters '0' through '9' appear as raw bytes coincidentally match their ASCII digit codes.

Rule of Thumb: Use Serial.print() / Serial.println() whenever you want to display human-readable values (numbers, text, variables) on the Serial Monitor. Only use Serial.write() when you specifically need to send raw binary byte values, such as for custom communication protocols.

6. Summary & Key Takeaways

Experiment with these functions on your own ESP32 board, try printing different data types, and observe how the Serial Monitor interprets each one. Understanding this distinction between raw bytes and ASCII text is essential before moving on to reading incoming serial data in the next stage of learning.