Bluetooth Low Energy (BLE) is everywhere these days. If you fire up a scanner on your phone and walk around the neighborhood, you�ll undoubtedly find dozens, if not hundreds, of BLE devices.
If you�re curious and want to start working with BLE, this tutorial is an excellent starting point. It will give you a quick overview of BLE (specifically how data is organized in BLE, how two BLE devices communicate with each other), and how to use BLE on the ESP32.
Basics of Bluetooth Low Energy
Bluetooth Low Energy (BLE), sometimes referred to as �Bluetooth Smart,� is a light-weight subset of classic Bluetooth introduced as part of the Bluetooth 4.0 core specification.
BLE is designed for devices that send small amounts of data infrequently and operate on small batteries. It uses the same frequency band as classic Bluetooth�the 2.4GHz ISM (industrial, scientific, and medical) band, which does not require a license to use. The ISM band is divided into 40 channels, and BLE devices hop between them to avoid interference.
To save power, BLE has lower transmission power (0.01 to 10 mW) compared to classic Bluetooth (up to 100 mW for class 1 and 1 mW for class 3 devices). Data is sent in the same way (using Gaussian Frequency Shift Keying), but the speed of data transmission is lower�a maximum of 1 Mb/s, compared to the 24 Mb/s maximum of classic Bluetooth. BLE devices can also switch between standby and active modes much faster than classic Bluetooth devices, saving even more power.
In a nutshell, BLE is designed to offer many of the same features as Bluetooth Classic, but with a focus on low power. As a result, it has become the standard technology for a wide range of applications, including smart lighting, smart homes, beacons, fitness trackers, insulin pumps, hearing aids, and other energy-sensitive applications.
Bluetooth Profiles
Bluetooth profiles are additional protocols that build upon the basic Bluetooth standard. They help specify the type of data a Bluetooth module transmits. While Bluetooth specifications define how the technology works, profiles define how it is used.
The profiles a Bluetooth device supports determine the applications it is designed for. For instance, a hands-free Bluetooth headset uses the headset profile (HSP), whereas a wireless keyboard uses the human interface device (HID) profile. These profiles are developed by either the Bluetooth SIG (Bluetooth Special Interest Group) or the peripheral designers. The complete list of officially adopted profiles can be seen here.
The profiles that you should be familiar with are GAP and GATT, as all standard BLE profiles are based on them. So let�s get to know them better.
GAP (Generic Access Profile)
GAP controls connections and advertising in Bluetooth. GAP is what makes your device visible to the outside world and determines how two devices discover and establish a connection with each other.
GAP defines various roles for devices, but the two key concepts to keep in mind are Central devices and Peripheral devices.
Peripheral devices are usually small, low-power, resource-constrained devices, such as heart rate monitors or proximity tags, that can connect to a much more powerful central device.
Central devices are usually devices with far more processing power and memory, such as mobile phones or tablets, to which you connect.
A peripheral device advertises by sending out advertising packets at set intervals to inform nearby central devices of its presence. Once a connection is established between a peripheral and a central device, the advertising process stops and GATT comes into play, allowing communication to occur in both directions.
GATT (Generic ATTribute Profile)
GATT specifies how data should be organized and how two BLE devices should share data. Unlike GAP, which defines the low-level interactions with devices, GATT deals only with actual data transfer procedures and formats.
The data is organized hierarchically into sections called services, which group conceptually related pieces of user data called characteristics, as shown in the illustration below:
Services
A GATT service is a collection of conceptually related data called characteristics. Each service can have one or more characteristics and has its own unique numeric identifier, or UUID, which is either 16 bits (for officially adopted BLE Services) or 128 bits (for custom services). More on that later.
For instance, let�s consider the Heart Rate Service. This officially adopted service has a 16-bit UUID of 0x180D, and contains up to 3 characteristics: Heart Rate Measurement, Body Sensor Location and Heart Rate Control Point. You can find more Bluetooth SIG-defined services here.
Characteristics
A GATT characteristic is a group of information called Attributes. Attributes are the information actually transferred between BLE devices. A typical characteristic is composed of the following attributes:
- Value: is the actual data that is stored in the characteristic. The value can be any type of data, such as a number, a string, or an array of bytes.
- Descriptor: provides additional information or configuration options for the characteristic.
In addition to the value, the following properties are associated with each characteristic:
- Handle: a 16-bit number used to access the characteristic on the server device.
- UUID: a 128-bit universally unique identifier that indicates what the characteristic represents.
- Permissions: defines which operations on the characteristic are permitted, such as read, write, or notify.
UUID (Universally Unique Identifier)
UUIDs ensure that services and characteristics can be uniquely identified. They play an essential role in defining and accessing the data on a BLE device. There are two types of UUIDs in BLE:
- 16-bit UUID: is used for official BLE profiles, services, and characteristics. These are standardized by the Bluetooth-SIG. For example, the �Heart Rate Service� has a standardized 16-bit UUID of 0x180D, while the �Heart Rate Measurement� characteristic within the Heart Rate Service uses a UUID of 0x2A37.
- 128-bit UUID: is used for custom (vendor-specific) services and characteristics. If a company develops its own service not covered by the official BLE services, they would use a unique 128-bit UUID. A 128-bit UUID looks like this: 4fafc201-1fb5-459e-8fcc-c5c9c331914b.
GATT Server and GATT Client
From a GATT perspective, when two devices are connected, they are each in one of two roles.
- The GATT server is the device that contains the characteristic database.
- The GATT client is the device that reads from or writes data to the GATT server.
The following figure illustrates this relationship in a sample BLE connection, where the peripheral device (an ESP32) is the GATT server, while the central device (a smartphone) is the GATT client.
It�s important to note that the GATT roles of client and server are independent from the GAP roles of peripheral and central. A peripheral can be either a GATT client or a GATT server or both, and similarly, a central can be either a GATT client or a GATT server.
Using BLE on the ESP32
Now that you have learned about the Bluetooth Low Energy (BLE) wireless communication protocol, including its features, profiles, and how it communicates with devices, it�s time to give it a try.
There are several example sketches available for the ESP32 in the ESP32 BLE library. This library comes installed when you install the ESP32 core on the Arduino IDE.
To access the example sketches, navigate to File > Examples > ESP32 BLE Arduino.
You will see a selection of example sketches. You can choose any of them to load the sketch into your IDE. Let�s get started with the BLE_server example.
This example configures the ESP32 as a BLE server with a specific service and characteristic. It then advertises this service, making it discoverable and accessible by BLE clients like your smartphone.
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
void setup() {
Serial.begin(115200);
Serial.println("Starting BLE work!");
BLEDevice::init("MyESP32"); // set the device name
BLEServer *pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setValue("Hello World!");
pService->start();
// BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
void loop() {
// put your main code here, to run repeatedly:
delay(2000);
}
Code Explanation:
Let�s go over the BLE_server code in detail. It starts by including the necessary libraries for BLE operations on the ESP32.
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
Next, UUIDs for the Service and the Characteristic are defined. UUIDs (Universally Unique Identifiers) uniquely identify services and characteristics in BLE.
You can use the default UUIDs or go to uuidgenerator.net to generate random UUIDs.
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
In the setup, the serial communication is initialized at a baud rate of 115200.
Serial.begin(115200);
Then, a BLE device called MyESP32
is created. You are free to change the name to whatever you want.
// Create the BLE Device
BLEDevice::init("MyESP32");
After that, our BLE device is configured as a server.
BLEServer *pServer = BLEDevice::createServer();
Next, a new service for our server is created with the previously defined UUID.
BLEService *pService = pServer->createService(SERVICE_UUID);
Then, a new characteristic for the service is created. As you can see, the properties of the characteristic, in this case READ
and WRITE
, are passed as arguments along with the CHARACTERISTIC_UUID
.
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
After creating the characteristic, use the setValue()
method to set its value. In this case, the value is set to the text �Hello World!�. You can change this text to whatever you want; it could be a sensor reading, the state of a button, or anything else.
pCharacteristic->setValue("Hello World!");
Next, the service is started so that it can respond to incoming requests.
pService->start();
Finally, advertising parameters are configured, and the service is then advertised. Advertising makes the ESP32 discoverable by other BLE devices.
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
This is a simple example of how to set up a BLE server, so nothing is executed within the loop()
. Depending on the application, however, you can include actions for when a new client connects (refer to the BLE_notify example for guidance).
void loop() {
delay(2000);
}
Using the nRF Connect to Test
To test the BLE connection, you�ll have to pair the ESP32 with your smartphone. You�ll also need a Bluetooth debugging application installed on it. There are several options available; one of our favorites is Nordic�s nRF Connect, which is available for both iOS and Android devices. It�s a powerful tool that allows you to scan and explore your BLE devices and communicate with them.
1. Go to the Google Play Store or the Apple App Store and search for �nRF Connect for Mobile�. Install the app and open it.
2. Ensure that your phone�s Bluetooth is turned on.
3. In the app, tap on the �SCAN� button. The app will start scanning for nearby BLE devices.
4. A list of available devices with their respective signal strengths and other details will appear. Look for �MyESP32�, and click the �Connect� button next to that.
5. You�ll be sent over to the �Services� view. There, you�ll see a list of available services. Click �Unknown Service��the UUID string should match SERVICE_UUID
in your example code.
6. Tap on a service to see its associated characteristics. Next to each characteristic, there will be two icons. The down arrow allows you to read a characteristic, whereas the up arrow allows you to write to it.
7. Tap on the down arrow to read the characteristic. You�ll see the characteristic�s UUID, READ and WRITE properties, and the value �Hello World!�, exactly as specified in our code.