Bluetooth BLE on ESP32

Bluetooth BLE on ESP32

Advertise a BLE service, send notifications, and connect from a phone.

Bluetooth on the ESP32

The ESP32 supports both Classic Bluetooth (for audio and serial SPP profiles) and Bluetooth Low Energy (BLE). BLE is more commonly used in modern IoT projects because it is energy-efficient and compatible with phones.

BLE vs Classic Bluetooth

Feature Classic Bluetooth BLE
Data rate Up to 3 Mbps Up to 2 Mbps
Power use Higher Very low
Phone support Older apps / SPP All modern phones
Use cases Audio, file transfer Sensors, beacons, IoT

BLE Terminology

  • GATT ߀” Generic Attribute Profile: how data is organised in BLE
  • Service ߀” a collection of related data (e.g., "Heart Rate Service")
  • Characteristic ߀” a single data value within a service (e.g., "Heart Rate Measurement")
  • UUID ߀” a unique identifier for services and characteristics
  • Notify ߀” the server pushes updates to a connected client automatically
  • Read/Write ߀” the client reads or writes a value on demand

BLE Server (Arduino Library)

The ESP32 Arduino core includes BLEDevice.h for easy BLE setup:

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

BLECharacteristic* pCharacteristic;
bool deviceConnected = false;

class ServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer)    { deviceConnected = true; }
  void onDisconnect(BLEServer* pServer) { deviceConnected = false; }
};

void setup() {
  Serial.begin(115200);
  BLEDevice::init("ESP32-BLE-Sensor");

  BLEServer* pServer = BLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  BLEService* pService = pServer->createService(SERVICE_UUID);

  pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID,
    BLECharacteristic::PROPERTY_READ |
    BLECharacteristic::PROPERTY_NOTIFY
  );
  pCharacteristic->addDescriptor(new BLE2902());

  pService->start();
  BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();
  Serial.println("BLE advertising started");
}

void loop() {
  if (deviceConnected) {
    int value = analogRead(34);
    String msg = String(value);
    pCharacteristic->setValue(msg.c_str());
    pCharacteristic->notify();
    delay(1000);
  }
}

Scanning for BLE Devices

#include <BLEDevice.h>
#include <BLEScan.h>

BLEScan* pBLEScan;

void setup() {
  Serial.begin(115200);
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setActiveScan(true);
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);
}

void loop() {
  BLEScanResults results = pBLEScan->start(5, false);
  Serial.print("Devices found: ");
  Serial.println(results.getCount());
  for (int i = 0; i < results.getCount(); i++) {
    BLEAdvertisedDevice d = results.getDevice(i);
    Serial.println(d.toString().c_str());
  }
  pBLEScan->clearResults();
  delay(2000);
}

Classic Bluetooth Serial (SPP)

For simple serial communication over Bluetooth Classic (works with Bluetooth serial apps on Android):

#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

void setup() {
  Serial.begin(115200);
  SerialBT.begin("ESP32-BT-Device");  // Name visible when pairing
  Serial.println("Bluetooth device ready to pair.");
}

void loop() {
  if (SerialBT.available()) {
    char c = SerialBT.read();
    Serial.write(c);           // Echo to USB serial
  }
  if (Serial.available()) {
    SerialBT.write(Serial.read());  // Forward USB serial to BT
  }
}

BLE vs WiFi Coexistence

The ESP32 can run WiFi and BLE simultaneously, but there are limitations:

  • They share the same 2.4 GHz antenna via time-division multiplexing
  • Running both reduces throughput on each
  • ADC2 cannot be used when WiFi or BT radio is active
  • Use esp_coex_preference_set() to prioritise one radio if needed