While BLE (Bluetooth Low Energy) gets most of the attention today, ESP32 Bluetooth Classic SPP (Serial Port Profile) remains indispensable for applications that need higher throughput, compatibility with older Bluetooth devices, or seamless serial-over-Bluetooth communication. This tutorial covers everything you need to know about using Bluetooth Classic SPP on the ESP32, from the basics of the BluetoothSerial library to advanced pairing, data framing, and real-world project examples.
Bluetooth Classic vs BLE: When to Use Which
The ESP32 is one of the few microcontrollers that supports both Bluetooth Classic (BR/EDR — Basic Rate/Enhanced Data Rate) and Bluetooth Low Energy in the same chip. Understanding when to use each is the first step to a successful design.
| Aspect | Bluetooth Classic (SPP) | Bluetooth Low Energy |
|---|---|---|
| Throughput | Up to 3 Mbps | Up to 2 Mbps (BLE 5) |
| Power Use | Higher (always on radio) | Lower (duty cycled) |
| Connection Setup | Slower (pairing required) | Faster |
| Legacy Device Compat. | Excellent | Poor (older devices) |
| Best Use Case | Serial data streams, audio | Sensor data, beacons |
| iOS Support | No (Apple restricts it) | Yes |
Choose Bluetooth Classic SPP when: You need to stream continuous data (GPS NMEA sentences, UART logs), connect to legacy hardware (HC-05/HC-06 bluetooth modules, OBD-II adapters), or build Android apps that need a simple serial interface without a custom BLE GATT profile.
Important note for 2026: Apple iOS/iPadOS does NOT support Bluetooth Classic SPP — it’s restricted to MFi-certified accessories. If you need iOS support, use BLE instead. Android supports SPP natively.
Ai Thinker NodeMCU-32S-ESP32 Development Board – IPEX Version
The original ESP32 dual-core with both Bluetooth Classic and BLE support — essential for SPP serial communication projects.
ESP32 SPP Basics: BluetoothSerial Library
The Arduino framework for ESP32 includes the BluetoothSerial library which implements the SPP profile. It exposes a Serial-compatible API, making it trivially easy to port existing UART code to Bluetooth.
Important Note on ESP32 Variants
Bluetooth Classic is only supported on the original ESP32 (dual-core). The ESP32-C3, ESP32-S2, ESP32-C6, and ESP32-H2 do NOT support Bluetooth Classic — they only have BLE. Check your board before proceeding!
Hello World: BluetoothSerial
#include "BluetoothSerial.h"
// Verify BT Classic is available
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth not enabled in menuconfig!
#endif
BluetoothSerial SerialBT;
void setup() {
Serial.begin(115200);
// Start Bluetooth with device name
SerialBT.begin("ESP32-Device");
Serial.println("Bluetooth started! Pair with 'ESP32-Device'");
}
void loop() {
// Forward USB Serial to Bluetooth
if (Serial.available()) {
SerialBT.write(Serial.read());
}
// Forward Bluetooth to USB Serial
if (SerialBT.available()) {
Serial.write(SerialBT.read());
}
delay(20);
}
After uploading, go to your Android phone’s Bluetooth settings, scan for devices, and you’ll see “ESP32-Device”. Pair with it (no PIN by default), then open the “Serial Bluetooth Terminal” app, connect, and start sending/receiving data.
Reading Full Lines
void loop() {
if (SerialBT.available()) {
String line = SerialBT.readStringUntil('n');
line.trim();
if (line == "LED_ON") {
digitalWrite(LED_BUILTIN, HIGH);
SerialBT.println("LED turned ON");
} else if (line == "LED_OFF") {
digitalWrite(LED_BUILTIN, LOW);
SerialBT.println("LED turned OFF");
} else if (line == "TEMP") {
float t = dht.readTemperature();
SerialBT.printf("Temperature: %.1f Cn", t);
} else {
SerialBT.println("Unknown command: " + line);
}
}
}
Connecting from Android with Serial Bluetooth Terminal
For testing and prototyping, “Serial Bluetooth Terminal” by Kai Morich (available on Google Play Store, free) is the best tool. It supports multiple message delimiters, a command history, and macro buttons for sending common commands.
Programmatic Android Connection (Java/Kotlin)
For building your own Android app to communicate with the ESP32 over SPP:
// Android Java — connect to ESP32 SPP
private static final UUID SPP_UUID =
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = btAdapter.getRemoteDevice("XX:XX:XX:XX:XX:XX");
// Connect in background thread
new Thread(() -> {
try {
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(SPP_UUID);
socket.connect(); // Blocking call
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// Send command
out.write("TEMPn".getBytes());
// Read response
byte[] buffer = new byte[1024];
int bytes = in.read(buffer);
String response = new String(buffer, 0, bytes);
Log.d("BT", "Response: " + response);
} catch (IOException e) {
Log.e("BT", "Connection failed", e);
}
}).start();
The UUID 00001101-0000-1000-8000-00805F9B34FB is the standard SPP UUID — use this for all ESP32 SPP connections from any platform.
Designing a Reliable Data Protocol
Raw SPP is a byte stream with no packet framing — data can arrive in chunks of any size. Always design a protocol layer on top of the raw bytes:
Approach 1: Newline-Delimited JSON
Simple and human-readable. Each message is a JSON object terminated by n. Works great for command-and-response use cases:
// Send sensor reading as JSON
void sendSensorData() {
JsonDocument doc;
doc["type"] = "sensor";
doc["temp"] = dht.readTemperature();
doc["hum"] = dht.readHumidity();
doc["ts"] = millis();
String output;
serializeJson(doc, output);
SerialBT.println(output); // println adds n delimiter
}
// Receive and parse
void receiveCommand() {
if (!SerialBT.available()) return;
String line = SerialBT.readStringUntil('n');
JsonDocument doc;
DeserializationError err = deserializeJson(doc, line);
if (err) return;
const char* cmd = doc["cmd"];
if (strcmp(cmd, "get_temp") == 0) sendSensorData();
}
Approach 2: Binary Framed Protocol
For high-throughput applications, a binary protocol is more efficient:
// Frame format: [STX][LEN_H][LEN_L][TYPE][DATA...][CRC]
#define STX 0x02
void sendFrame(uint8_t type, const uint8_t* data, uint16_t len) {
uint8_t header[4] = {STX, (uint8_t)(len >> 8), (uint8_t)(len & 0xFF), type};
SerialBT.write(header, 4);
SerialBT.write(data, len);
// Simple XOR checksum
uint8_t crc = type;
for (uint16_t i = 0; i < len; i++) crc ^= data[i];
SerialBT.write(&crc, 1);
}
Ai Thinker ESP32 CAM Development Board WiFi+Bluetooth
The ESP32-CAM combines a camera with Bluetooth Classic — stream camera data over WiFi while sending metadata or control commands over SPP simultaneously.
Secure Pairing with PIN Authentication
By default, ESP32 SPP uses Bluetooth’s “Just Works” pairing (no PIN, no confirmation) — convenient but insecure. For projects where you need to prevent unauthorised Bluetooth connections, implement PIN authentication:
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
bool btAuthenticated = false;
void btAuthCallback(const uint8_t* device_addr, uint32_t pin) {
Serial.printf("PIN request from device, displaying: %06dn", pin);
// In a real device: display PIN on OLED or LED matrix
SerialBT.confirm_reply(true); // Confirm the PIN
}
void setup() {
Serial.begin(115200);
// Enable Secure Simple Pairing
SerialBT.enableSSP();
SerialBT.onConfirmRequest(btAuthCallback);
// Or use legacy PIN authentication:
SerialBT.setPin("1234"); // 4-digit PIN
SerialBT.begin("ESP32-Secure");
}
void loop() {
if (SerialBT.hasClient()) {
// Client is connected and paired
if (SerialBT.available()) {
// Handle authenticated commands
}
}
}
For application-level authentication (beyond Bluetooth pairing), implement a challenge-response handshake: after connecting, require the client to send a shared secret within 10 seconds, or disconnect.
Real-World Project Examples
1. Bluetooth Serial Monitor / Logger
Replace USB cable with Bluetooth for remote firmware debugging. Route Serial output to SerialBT simultaneously using a custom Print class that duplicates output to both interfaces. Useful for IoT devices installed in hard-to-reach locations.
2. Wireless Robot Controller
Use an Android app (or MIT App Inventor) to send directional commands over SPP to an ESP32-controlled robot. SPP’s lower latency compared to BLE makes it better for real-time motor control where every millisecond of lag counts.
3. OBD-II Vehicle Data Logger
The ESP32 can connect to a vehicle’s OBD-II port via a Bluetooth OBD adapter (which uses SPP) and forward data to WiFi/MQTT. The ESP32 acts as a Bluetooth master, scanning for and connecting to OBD adapters using SerialBT.connect(macAddress).
4. Wireless Oscilloscope Companion
Stream ADC samples (up to ~50 kSPS at 12-bit resolution) over Bluetooth to a PC running a Python client. At SPP’s practical throughput of ~300-400 KB/s, this supports useful oscilloscope functionality for audio-frequency signals.
2 x 18650 Lithium Battery Shield for ESP32/ESP8266
Bluetooth Classic keeps the ESP32 radio active continuously — pair it with this dual 18650 battery shield for portable Bluetooth projects that run for hours.
DHT20 SIP Packaged Temperature and Humidity Sensor
Stream live environmental data over Bluetooth Classic SPP from this accurate I2C temperature and humidity sensor — great for portable monitoring projects.
Frequently Asked Questions
Does the ESP32-C3 support Bluetooth Classic SPP?
No. The ESP32-C3, ESP32-S2, ESP32-C6, ESP32-H2, and ESP32-P4 all support only Bluetooth Low Energy (BLE), not Bluetooth Classic. Bluetooth Classic (including SPP, A2DP, HFP profiles) is exclusive to the original ESP32 (ESP32-D0WD, ESP32-WROOM, ESP32-WROVER, etc.). Always check the datasheet before choosing your ESP32 variant for a Bluetooth Classic project.
What is the actual data throughput of ESP32 Bluetooth Classic SPP?
Theoretical Bluetooth Classic throughput is up to 3 Mbps, but the SPP profile over the Arduino BluetoothSerial library typically achieves 100-400 KB/s in practice, depending on packet size and ACK handling. Sending data in larger chunks (512-1024 bytes) rather than byte-by-byte significantly improves throughput. Avoid calling SerialBT.write() in tight loops with single bytes.
Can the ESP32 be a Bluetooth master (connect to other devices)?
Yes. Use SerialBT.connect("DeviceName") or SerialBT.connect(macAddress) to initiate connections to other SPP devices (HC-05, OBD-II adapters, etc.). The device being connected to must be in discoverable mode and accept SPP connections. Master mode scanning requires the remote device to be discoverable.
How do I handle Bluetooth reconnection after power loss?
Implement a reconnection loop in your code. For slave mode (ESP32 waits for connections), reconnection is automatic — the Android app just needs to reconnect. For master mode, poll SerialBT.hasClient() and call SerialBT.connect() again when the connection drops. Use an exponential backoff strategy to avoid hammering the connection.
Can I use WiFi and Bluetooth Classic simultaneously on ESP32?
Yes, with caveats. Both WiFi and Bluetooth Classic share the 2.4 GHz radio on the ESP32, and Espressif has implemented coexistence scheduling between them. Performance of both will be slightly reduced compared to using either alone. The ESP32 uses time-division multiplexing to switch between WiFi and Bluetooth. In practice, for most IoT projects, the coexistence works well enough.
Power Your Bluetooth Projects with Zbotic
Get genuine ESP32 development boards, sensors, and power modules for your Bluetooth Classic and BLE projects at Zbotic — India’s trusted electronics store with fast pan-India delivery.
Add comment