Phase 4: Connectivity 11 min read

Bluetooth Classic on ESP32 — Serial Over Bluetooth (SPP)

Use ESP32 Bluetooth Classic Serial Port Profile (SPP) to send and receive data wirelessly from Android phones, Windows PCs, and other Bluetooth devices using Serial commands.

Updated June 18, 2026

Bluetooth Serial on ESP32

The BluetoothSerial library makes the ESP32’s Bluetooth Classic radio behave like a serial port — the same read(), print(), and println() methods you already know from Serial. This is perfect for replacing an HC-05/HC-06 module, building a wireless serial monitor, or controlling an ESP32 from an Android app.

Echo Server — Send What You Receive

bt_echo.ino
#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32-Device");   // Bluetooth device name
  Serial.println("Bluetooth started — pair with 'ESP32-Device'");
}

void loop() {
  // Forward USB Serial → Bluetooth
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  // Forward Bluetooth → USB Serial
  if (SerialBT.available()) {
    char c = SerialBT.read();
    Serial.write(c);
    SerialBT.write(c);   // echo back
  }
}

Command Parser

A practical pattern: read a full line of text and parse it as a command:

bt_commands.ino
#include "BluetoothSerial.h"

BluetoothSerial SerialBT;
const int LED = 2;

void handleCommand(String cmd) {
  cmd.trim();
  cmd.toLowerCase();

  if (cmd == "on")  {
    digitalWrite(LED, HIGH);
    SerialBT.println("LED turned ON");
  }
  else if (cmd == "off") {
    digitalWrite(LED, LOW);
    SerialBT.println("LED turned OFF");
  }
  else if (cmd == "status") {
    SerialBT.printf("LED: %s | Uptime: %lus\n",
      digitalRead(LED) ? "ON" : "OFF", millis() / 1000);
  }
  else {
    SerialBT.println("Unknown command. Try: on, off, status");
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  SerialBT.begin("ESP32-Control");
  Serial.println("Bluetooth ready");
}

void loop() {
  if (SerialBT.available()) {
    String line = SerialBT.readStringUntil('\n');
    handleCommand(line);
  }
}

Setting a Pairing PIN

bt_pin.ino
#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.setPin("1234");   // set before begin()
  SerialBT.begin("SecureESP32");
  Serial.println("PIN: 1234");
}

ESP32 as Bluetooth Master (Connect to HC-05)

bt_master.ino
#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

// Replace with your HC-05 MAC address (printed on module or found via scan)
uint8_t target_mac[6] = {0x00, 0x21, 0x09, 0x00, 0xBE, 0xEF};

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32-Master", true);   // true = master mode

  Serial.println("Connecting to HC-05...");
  bool connected = SerialBT.connect(target_mac);
  if (connected) {
    Serial.println("Connected!");
  } else {
    Serial.println("Failed — check MAC and pairing");
  }
}

void loop() {
  if (SerialBT.available()) Serial.write(SerialBT.read());
  if (Serial.available())   SerialBT.write(Serial.read());
}

Connection Event Callback

bt_events.ino
void btCallback(esp_spp_cb_event_t event, esp_spp_cb_param_t* param) {
  if (event == ESP_SPP_SRV_OPEN_EVT) {
    Serial.println("Client connected");
    SerialBT.println("Welcome to ESP32!");
  }
  if (event == ESP_SPP_CLOSE_EVT) {
    Serial.println("Client disconnected");
  }
}

// In setup(), before SerialBT.begin():
SerialBT.register_callback(btCallback);

Android Apps for Testing

  • Serial Bluetooth Terminal (Google Play) — clean terminal, supports newline modes, timestamps
  • Bluetooth Serial Monitor — shows raw bytes and ASCII side-by-side
  • MIT App Inventor — build a custom app with buttons that send commands over BT Classic

Transitioning to BLE

If you need iOS support, lower power consumption, or compatibility with modern wearables, migrate to the BLE guide which uses GATT services instead of SPP.

Frequently Asked Questions

Bluetooth Classic (BR/EDR) supports the Serial Port Profile (SPP) for high-throughput streaming data — like connecting to Android apps via the Bluetooth Serial terminal. BLE (Bluetooth Low Energy) uses GATT profiles, consumes far less power, and is preferred for wearables, beacons, and iOS devices.
No. Apple removed Bluetooth Classic SPP support from iOS. Use BLE with the NimBLE or ArduinoBLE library for iOS connectivity.
No. The ESP32 has built-in Bluetooth hardware — both Classic and BLE. You do not need an external HC-05 or HC-06 module. The BluetoothSerial library accesses the built-in radio directly.
Open Settings → Bluetooth on Android, scan for devices, and tap your ESP32 name (e.g. "ESP32-BT"). Accept the pairing request. Then use a terminal app like Serial Bluetooth Terminal to send and receive data.
Yes. Call SerialBT.connect("HC-05") to initiate a master connection to another device by name, or SerialBT.connect(address) to connect by MAC address. This lets ESP32 talk to HC-05/HC-06 modules and Bluetooth keyboards.
SPP theoretical throughput is around 1 Mbps, with real-world rates of 100–300 Kbps depending on Bluetooth version and interference. This is much higher than BLE (27 Kbps effective for BLE 4.2) making Classic better for audio or bulk data.
Yes. The ESP32 has a co-existence system that time-multiplexes the 2.4 GHz radio between Wi-Fi and Bluetooth. Performance of both is reduced slightly when both are active.
Call SerialBT.setPin("1234") before SerialBT.begin() to set a 4-digit PIN. Without this call, most Android versions pair without a PIN confirmation.
Bluetooth Classic supports only one active connection at a time. If a second device tries to connect while one is already connected, it will either be rejected or the first connection will be dropped.
The ESP32-S2 chip does not include a Bluetooth radio — it is Wi-Fi only. Use an ESP32, ESP32-S3, or ESP32-C3 for Bluetooth. The S3 supports both Classic and BLE; the C3 supports only BLE.

Projects to Build

Put this knowledge to work — try one of these hands-on projects.