If you are trying to set up a standard bluetooth terminal for esp32 dev module on arduino, the direct answer is that you must use Bluetooth Classic SPP (Serial Port Profile). Standard serial terminal apps on Android (like Serial Bluetooth Terminal) rely on the SPP stack to emulate a virtual COM port. If you attempt to use Bluetooth Low Energy (BLE), your phone's terminal app will not see the device, because BLE requires custom GATT (Generic Attribute Profile) services and a dedicated BLE scanner app. For a plug-and-play serial bridge, Bluetooth Classic SPP via the ESP32's internal stack is the only correct path.

Bench Note: Apple iOS devices do not support Bluetooth Classic SPP for third-party apps due to MFi (Made for iPhone) licensing restrictions. If your target phone is an iPhone, you are forced to use BLE with a custom GATT UART service (like the Nordic UART Service).

Bus Mechanics: UART Physical Layer vs RF Stack

To debug communication issues, you must separate the physical UART bus (which moves data inside the chip or to external pins) from the wireless RF bus (which moves data through the air). Below is the mechanical breakdown of the layers involved when bridging a serial terminal to an ESP32.

Protocol LayerWires / ChannelsMax SpeedAddressingMax Distance
UART (Physical)2 (TX, RX) + GND~3 Mbps (practical 115200)None (Point-to-Point)~15 meters (copper)
BT Classic SPP (RF)1 (2.4 GHz ISM)~2.1 Mbps (EDR)MAC Address / PIN~10m (Class 2 radio)
BLE 4.2+ (RF)1 (2.4 GHz ISM)~2 Mbps (PHY)UUID / GATT Handles~40m (with PA/LNA)

The ESP32-WROOM-32 module integrates the baseband and RF front-end directly on the silicon. When you call SerialBT.print() in Arduino, the MCU routes the payload over an internal memory bus to the Wi-Fi/BT co-processor, which then packetizes it into SPP frames over the 2.4 GHz air interface.

Physical Wiring and Pull-Up Requirements

While the Bluetooth stack itself is wireless, the physical debugging and sensor buses on your ESP32 dev module require strict electrical discipline. The most common ESP32 dev boards (like the 30-pin DevKit V1) expose UART0 on GPIO 1 (TX) and GPIO 3 (RX). These are hardwired to the onboard CP2102 or CH340 USB-to-Serial chip.

Logic Level Translation

The ESP32 operates at 3.3V logic. If you are bridging an external 5V microcontroller (like an Arduino Uno) to the ESP32 via UART to pass Bluetooth data, you must use a logic level converter. Feeding 5V into GPIO 3 will permanently degrade or destroy the ESP32's RX pin. Use a bidirectional BSS138 MOSFET logic level converter module (roughly $2 on Amazon) rather than a simple resistor divider, as the MOSFET preserves the sharp rise/fall times needed for 115200 baud.

I2C Pull-Up Requirements (For Sensor Expansion)

If you are adding I2C sensors (like a BME280) to the same ESP32 to transmit data over your Bluetooth terminal, the physical I2C bus requires pull-up resistors. The internal ESP32 pull-ups (typically 45kΩ) are too weak for reliable I2C communication at 400kHz. You must solder or wire 4.7kΩ external pull-up resistors from SDA (GPIO 21) and SCL (GPIO 22) to the 3.3V rail.

Minimal Working Exchange: Serial to Bluetooth SPP

Below is the exact wiring and code required to bridge the hardware UART serial monitor to a Bluetooth Classic SPP terminal app.

Safety & Code Check: Ensure your Arduino IDE ESP32 Board Manager package is updated to version 2.0.x or 3.0.x. The BluetoothSerial.h library is deprecated in ESP-IDF v5.0+ (ESP32 core v3.x) in favor of NimBLE, but remains functional in v2.x for Classic SPP.

Wiring Checklist

  • Connect ESP32 Dev Board to PC via USB (UART0).
  • No external BT module (HC-05/HC-06) is needed; the internal radio is used.
  • Power the board via USB or the 5V/VIN pin (ensure adequate 500mA+ supply to prevent brownouts during RF transmission).

Arduino Code

#include 'BluetoothSerial.h'

// Check if Bluetooth is available on this specific ESP32 variant
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error 'Bluetooth is not enabled! Please run make menuconfig to enable it'
#endif

BluetoothSerial SerialBT;

void setup() {
  // Initialize hardware UART for PC debugging
  Serial.begin(115200);
  
  // Initialize Bluetooth Classic SPP with a visible device name
  SerialBT.begin('ESP32_Flux_Terminal'); 
  Serial.println('The device started, now you can pair it with bluetooth!');
}

void loop() {
  // Bridge PC Serial Monitor to Bluetooth Terminal App
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20); // Prevent watchdog timer resets
}

Classic Failures and How to Sniff the Bus

When your terminal app fails to connect or outputs garbage, the issue almost always falls into one of three physical or protocol-layer failures.

1. Baud Rate Mismatch (Garbage Characters)

Symptom: You send 'Hello' from your phone, and the Arduino Serial Monitor prints 'ÿÿÿ'.
Cause: The physical UART baud rate (Serial.begin(115200)) does not match your PC's serial monitor setting, or the Bluetooth terminal app on your phone is hardcoded to 9600 baud.
Fix: Verify the baud rate dropdown in the Arduino IDE Serial Monitor matches 115200. In your Android Bluetooth Terminal app, go to Connection Settings and explicitly set the baud rate to 115200 (SPP ignores this over the air, but the app's local UART buffer expects it).

2. Missing Pull-Up Resistors (Bus Lockup)

Symptom: The ESP32 boots, connects to Bluetooth, but freezes after 30 seconds when trying to read an I2C OLED display.
Cause: Missing 4.7kΩ pull-ups on the I2C SDA/SCL lines. The bus floats, causing the ESP32's I2C state machine to hang indefinitely, triggering a watchdog reset.
Fix: Measure the SDA and SCL lines with a multimeter. They should read a steady 3.3V when idle. If they read 0V or float, install 4.7kΩ resistors to the 3.3V pin.

3. Address Clash and BLE MAC Randomization

Symptom: Two sensors on the I2C bus fail to initialize, or a BLE device drops pairing every time it reconnects.
Cause: For I2C, an address clash occurs when two sensors (e.g., BME280 and BMP280) share the same default I2C address (0x76). For BLE, modern smartphones use MAC address randomization for privacy, causing the ESP32's bonded whitelist to reject the 'new' phone.
Fix: For I2C, run an I2C Scanner sketch to detect clashes and change the address via hardware jumper pads. For BLE pairing drops, clear the ESP32's NVS (Non-Volatile Storage) partition using the Arduino IDE 'Erase All Flash Before Sketch Upload' tool.

How to Sniff and Debug the Bus

When logic fails, you must capture the raw packets.

  • Physical UART Sniffing: Clip a Saleae Logic Analyzer or a $10 USB logic analyzer (running Sigrok/PulseView) to GPIO 1 (TX) and GND. Set the sample rate to 4 MS/s and decode using the UART protocol decoder. This proves whether the ESP32 is actually transmitting bytes.
  • RF Layer Sniffing (Android): Enable 'Bluetooth HCI snoop log' in your Android Developer Options. Reproduce the connection failure, disable the log, and pull the btsnoop_hci.log file. Open it in Wireshark and filter with btatt or btspp to see if the ESP32 is rejecting the RFCOMM channel request.

Protocol Decision Tree: Which Wireless Bus Fits?

Do not default to Bluetooth for every project. Use this decision matrix to select the correct wireless protocol for your specific constraints. Follow the path down to your concrete pick.

Constraint / RequirementIf YES...If NO...
Target device is an Apple iOS product? Go to BLE row. Continue to next row.
Need to use a generic 'Serial Terminal' app? Pick: Bluetooth Classic SPP (BluetoothSerial.h) Continue to next row.
Range > 10 meters or battery operated (coin cell)? Pick: BLE 5.0 (NimBLE-Arduino library) Continue to next row.
Need high throughput (>1 Mbps) or IP networking? Pick: Wi-Fi (TCP/UDP) (WiFi.h) Re-evaluate SPP.
The Final Verdict: For 90% of hobbyist debugging scenarios where you just want to send text commands from an Android phone to an ESP32 without writing a custom mobile app, Bluetooth Classic SPP is the definitive choice. Use the BluetoothSerial.h library, stick to 115200 baud on your physical UART, and ensure your 3.3V logic levels are respected.