Arduino BLE Library, Pointers, and Bitwise Operators – An unlikely combination!

  1. Example sketch for BLE library functions
  2. Arduino BLE Library Deeper Dive
  3. Create a BLE Server
  4. Creating a BLE Service and Creating a BLE Characteristic
  5. BLE and Pointers
  6. Arduino BLE Library and Bitwise Operators
  7. Advertising a BLE Service
  8. Running The Arduino BLE Library Example Sketch
  9. Where To Go From Here

If you have been working through any of the ESP32 BLE library articles here on Programming Electronics Academy, then you may have been wondering about some of the details behind the library function calls used in the example sketches.

In this article, we will be taking a deeper dive into BLE and more specifically some of those BLE Library function calls.

As a review, BLE stands for Bluetooth Low Energy.  And as the name implies, BLE is simply a power conserving version of the original Bluetooth technology.  Bluetooth technology is a wireless communications technology used over short distances for personal area networks.  

And also as a review, you might remember that you are probably most familiar with BLE technology when you connect your smartwatch to your smartphone.  Or, when you use a digital key to access your hotel room with your smartphone.  

The BLE technology was designed and marketed by the Bluetooth Special Interest Group (Bluetooth SIG).

BLE technology uses the Generic Attribute Profile (GATT) and BLE technology is founded on the following software model and terminology:

BLE Client

The BLE Client is a device that makes GATT requests to a BLE Server. If you had a smartphone that connected to BLE headphones, the smartphone would be the BLE client of the Headphones.  An ESP32 can be a client.     

BLE Server

The BLE Server is a device that receives GATT requests. BLE headphones are a common example, but the possibilities are endless.  Any device that is “serving up” information, is the server.  An ESP32 can act as a server too!

BLE Service

A BLE Service is a collection of characteristics for a server.  These characteristics might include temperature and humidity from a sensor.

A device profile is the top level of the BLE architecture.  A BLE device profile may have one, or many, BLE Services.

The BLE architecture is defined in a hierarchical structure.  So, BLE devices have services and those services have characteristics and those characteristics have descriptors.  

BLE Characteristic

A BLE Characteristic is the actual value transferred between the BLE client and the BLE server.  

And importantly every BLE Service can have one, or many, BLE Characteristics.  A temperature reading is an example of a BLE Characteristic.

BLE Descriptor

BLE Descriptors contain additional information about a characteristic.  For example, it may indicate if a temperature value is in Celsius or Fahrenheit.

UUID

A UUID is a Universally Unique Identifier.  There are UUIDs for BLE services, for BLE Characteristics, and for BLE Descriptors.

Unique UUIDs can be created for free using the following free UUID generator:  UUID Generator

Example sketch for BLE library functions

The following is the entire example sketch of how to create and use a BLE Server with your ESP32.  In the sections below, we will take a deeper look at some of these BLE functions.  

//  BLE Server Example Sketch
//
//  Programming Electronics Academy
//

#include <BLEDevice.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 server setup!");
  BLEDevice::init("PEA - BLE Server Test");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(

                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setValue("We love Programming Electronics Academy");
  pService->start();
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();

  Serial.println("Characteristic defined!");

}

void loop() {

  // put your main code here, to run repeatedly:
  delay(2000);

}

Arduino BLE Library Deeper Dive

One of the great things about using the Arduino development environment is that there are so many awesome open source libraries available to use!  

At the top of the program, we’ll take advantage of some of these libraries by including the BLE device library and BLE server library.

These types of libraries are part of what makes the Arduino platform so great….that we can take advantage of these BLE libraries that have been shared by other developers!

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

Now, we need to define some unique identifiers for the BLE server that we will be creating.  BLE devices broadcast a unique identifier (a UUID) for the device often along with other identifying characteristics.  

Below we have defined the BLE service UUID and BLE characteristic UUID of the BLE Server that a BLE Client might be searching for.  If you have a sensor from a manufacturer, they will provide you with the UUIDs for their device.  In our example, we needed to create our own UUIDs.  Which we have done using the free UUID generator:  UUID Generator.

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

Create a BLE Server

To create a new BLE Server, first the BLE device must be initialized using the BLEDevice::init() function.  When the init function is called, a name for the BLE server can be passed in as a parameter.  In the example below, the name of “PEA – BLE Server Test” is passed in as a parameter for naming the BLE Server.    

After the BLE device is initialized using the init() function, the device can then be created using the BLEDevice::createServer() function.

BLEDevice::init("PEA - BLE Server Test");
BLEServer *pServer = BLEDevice::createServer();

Creating a BLE Service and Creating a BLE Characteristic

To create a BLE Service for a device, use the function createService().  

To create a BLE Characteristic for a BLE Service, use the function createCharacteristic().  

When creating the BLE Service and BLE Characteristic for this BLE Server, we use the BLE Service UUID (SERVICE_UUID) and the BLE Characteristic UUID (CHARACTERISTIC_UUID) that we had previously declared at the top of our program.  

Make sure the characteristic has both read (BLECharacteristic::PROPERTY_READ) and write (BLECharacteristic::PROPERTY_WRITE) properties set.  So other BLE Clients can access the BLE Server’s characteristics value.  

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

                                         CHARACTERISTIC_UUID,

                                         BLECharacteristic::PROPERTY_READ |

                                         BLECharacteristic::PROPERTY_WRITE

                                       );

BLE and Pointers

Now, in the code above you might be wondering what all of these -> (arrows) are doing?  Is this some kind of an archery match?  And why are so many of the variable names starting with “*p”?  

Well, no, this is not an archery match!  The arrow operator, ->, is used to access variables through a pointer in C++.  

A pointer is a variable that stores the memory address of another variable. Pointers are used extensively in C++.  Pointers point to the variable whose address they store.

And the dereference operator, *, character in C++ is used to represent a pointer.  

So, BLEService *pService declares a pointer variable named pService of type BLEService.  And BLECharacteristic *pCharacteristic declares a pointer variable named pCharacteristic of type BLECharacteristic.

And, previously we defined BLEServer *pServer = BLEDevice::createServer(); which declared pServer as a pointer variable of type BLEServer,  

So, we can access functions in the BLEServer object by using the arrow operator like this: (pServer->createService(SERVICE_UUID)).

If your head is swimming, don’t sweat it!  Pointers can take some time to sink in, but once they do you realize how useful they are.

Programming Electronics Academy members, learn how data is stored in memory in the  Using Pointers course.

Not a member yet?  Sign up here.

Arduino BLE Library and Bitwise Operators

In an attempt to make their software more readable and maintainable, software engineers frequently define variables that have meaning and are easy to read by others, instead of just hard coding numbers.

BLECharacteritsic::PROPERTY_READ and BLECharacteristic::PROPERTY_WRITE are two of those types of variables.  PROPERTY_READ has a defined value of 1.  And PROPERTY_WRITE has a defined value of 2.

In BLEServer.h, you will find those variables defined as follows:

static const uint32_t PROPERTY_READ      = 1;
static const uint32_t PROPERTY_WRITE     = 2;

The Bitwise operator OR, |, allows us to take two numbers and OR them together using binary math.

So, in the line of code above where we create the BLE Characteristic,

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(

                                         CHARACTERISTIC_UUID,

                                         BLECharacteristic::PROPERTY_READ |

                                         BLECharacteristic::PROPERTY_WRITE

                                       );

the second parameter passed to the createCharacteristic function is 3.

This value of 3 that is passed to the createCharacteristic function is the result of 1, PROPERTY_READ, and 2, PROPERTY_WRITE, being OR’d together in binary math.

BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE

1 | 2 = 3

The resulting value of the bitwise OR’ing together of the two values (PROPERTY_READ and PROPERTY_WRITE) is the value passed to the createCharacteristic function.  This method of combining multiple variables together is referred to as bit masking.  

Bit masking is an efficient way for dealing with binary data.  It is often used as a method for determining if a bit of data is set or not.  

In the code above, the createCharacteristic function uses a bit mask to determine which properties (PROPERTY_READ and PROPERTY_WRITE) are set for a specific characteristic.

You can also see below that if this code was written without using these predefined values for PROPERTY_READ and PROPERTY_WRITE, the function above might look something like this:

BLECharacteristic *pCharacteristic = pService->createCharacteristic(“beb5483e-36e1-4688-b7f5-ea07361b26a8”, 3);

This style of writing software would make it very difficult to read and understand what is happening in this line of code!  Fortunately for all of us, software engineers generally write code in a readable format!

Advertising a BLE Service

We need to start advertising the service that was created so that other devices can see and interact with the BLE Server.

To do that, we’ll initialize the BLE Characteristic value to “We love Programming Electronics Academy” and start the service.  This is the value the BLE Client will see when it connects to our BLE Server.

pCharacteristic->setValue("We love Programming Electronics Academy");

  pService->start();

And, finally, start Advertising the BLE Service, so the BLE Client can find, connect, and communicate with our BLE Server.

BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  
  pAdvertising->setMinPreferred(0x12);

  BLEDevice::startAdvertising();

  Serial.println("Characteristic defined!");

Running The Arduino BLE Library Example Sketch

After you have uploaded the example sketch to an ESP32, press the reset (RST) button on your ESP32 device and you should see the following displayed on the serial monitor.

Arduino BLE Library - Serial Monitor

Now, you can search for this device on your smartphone!  When you open your Bluetooth settings on your phone, you should be able to see the BLE Server that was created above like in the screenshot below!

Arduino BLE Library - Smartphone Settings

Where To Go From Here

Working with BLE technology and your ESP32 device can create a whole new realm of possibilities for your ESP32 projects.  

For practice, try changing your code to use your own generated UUIDs for the BLE Service and BLE Characteristic and then modify the name of your BLE Server.  You can verify that the name change worked by searching for your ESP32 BLE Server on your smartphone!

BLE Devil - thumbnail

Arduino BLE Library, Pointers, and Bitwise Operators – An unlikely combination!

installing Arduino libraries

Installing Arduino Libraries | Beginners Guide

IoT sewage project

Pumping poo! An IoT sewage project

ESP32 TV Remote Control thumbnail

Control your ESP32 with an Infrared TV Remote Control

ESP32 webOTA updates

How to update ESP32 firmware using web OTA [Guide + Code]

There's a better way to select your ESP32 pins

Leave a Comment