BLE Basics and the ArduinoBLE library
In this document you will find details on what BLE is and how it can be used in the context of the Arduino Robotics Board using the Arduino Nano 33 BLE and the ArduinoBLE
library, although this information applies to any project using the Arduino Nano 33 BLE.
What is BLE?
BLE stands for Bluetooth Low Energy and was introduced with Bluetooth 4.0. Although it is part of the Bluetooth standards, it is a separate standard to Bluetooth Classic and they are not compatible with each other, although they can co-exist on the same device. Generally most full Bluetooth devices, 4.0 and upwards, will implement both Classic and BLE, but there are many BLE only devices, such as the Arduino Nano 33 BLE, which only has BLE functionality.
BLE was introduced as a need for a widely supported, low power, short range communication protocol was identified, as IoT (Internet of Things) devices, such as smart home devices or personal health sensors, started to come onto the market. In contrast to Bluetooth Classic, which essentially operates as a wireless serial channel, BLE works by transferring small packets of data at a time, allowing it to consume much less energy than keeping a serial connection constantly open.
BLE operates on a client-server model, where your “peripheral” device operates as the server and your receiving device, known as the “central”, is the client. For example, in the scenario of a BLE smart watch, the smart watch would be the peripheral device and your phone would be the central.
BLE devices work by offering “services”, with each service providing one or more “characteristics”. Services are the categories of information that your peripheral provides. Under each service you will have one or more characteristics. Each characteristic represents a single value, up to 512 bytes each. For example you might have a sensor service, with the value from each sensor on the peripheral put into its own characteristic. These characteristics can be read from and written to by a central device. A central device can also subscribe to a characteristic. This tells the peripheral that the central wants to receive the new value of a characteristic every time it is updated without needing to request it each time, it’s sent automatically by the peripheral.
Each service or characteristic is identified by a UUID. Bluetooth SIG, which sets the Bluetooth standards, sets out several 16-bit UUIDs that are reserved, either for specific functions like battery indication, heart rate monitoring, volume control etc, or are reserved by companies for their proprietary use. For example the UUID 0x180F
would be the “Battery Level” service, whereas 0xFCF6
belongs to The Linux Foundation. Using the predefined services where possible allows devices to be intercompatible, so using the “Battery Level” service will allow that service to be identified as “Battery Level” with any device that recognises the standard UUIDs.
For custom services or characteristics that do not fit into any of the predefined UUIDs a 128-bit UUID is used instead. These are generated at random to avoid two developers using the same UUID, but it’s common practice to use a base UUID for your service, then change the 3rd and 4th bytes for each characteristic of that service. Eg:
3698-0000-82c9-4adb-90cd-792b53207775
might be my service and
3698-0001-82c9-4adb-90cd-792b53207775
3698-0002-82c9-4adb-90cd-792b53207775
3698-0003-82c9-4adb-90cd-792b53207775
3698-XXXX-82c9-4adb-90cd-792b53207775
might be my characteristics, where XXXX
is the number of the characteristic.
When generating 128-bit UUIDs its important to know that the 16-but UUIDs are only shorthand for 128-bit ones, relating to a base UUID:
0000XXXX-0000-1000-8000-00805F9B34FB
where XXXX
is the 16-bit UUID.
You should therefore make sure that your 128-bit random UUIDs don't
collide with the pre-defined ones so avoid any UUIDs like:
XXXXXXXX-0000-1000-8000-00805F9B34FB
where XXXXXXXX
is any number.
ArduinoBLE library
The ArduinoBLE
library is a cross-device library compatible with any Arduino device that has BLE functionality, including the Arduino Nano 33 BLE. It contains everything you need to setup and use BLE from your Arduino Sketch. The ArduinoBLE
library can be installed through the Arduino library manager under “Sketch->Include Library->Manage Libraries…”. There are examples provided with the library, but I will go over some of the important functions here.
Services
Services are defined as objects of the type BLEService
, generally globally:
BLEService ServiceName(ServiceUUID);
Characteristics
Characteristics are defined as objects, also generally globally. There are multiple characteristics you can use. BLECharacteristic
allows you to define a characteristic of any size, up to the limit of 512 bytes:
BLECharacteristic(uuid, properties, value, valueSize); BLECharacteristic(uuid, properties, stringValue);
But there are other characteristic types that are predefined for different C data types:
BLEIntCharacteristic(uuid, properties) BLEBoolCharacteristic(uuid, properties) BLECharCharacteristic(uuid, properties)
etc.
These types are all defined with a UUID and a properties parameter. The properties parameter defines how the central is allowed to interact with this characteristic. There are 4 main properties you will use with your characteristics:
BLERead
allows the central device to request the value of this characteristic. This is used for rarely changing data, such as a device ID or manufacturer name.
BLEWrite
allows the central device to write to this characteristic. This is used for sending commands to the peripheral or updating configurations etc
BLEIndicate
and BLENotify
are both used for regularly changing data. They allow the central device to “subscribe” to the characteristic. This asks the peripheral to automatically send the updated data to the central whenever it changes, without the central having to ask for it each time. The difference between Indicate and Notify is that a Notification is unacknowledged whereas an Indication requires an acknowledgement back from the receiving device. This makes Indicate slower but more reliable.
Generally most characteristics will always have BLERead
enabled because if you are writing to a characteristic you probably want to be able to read it back, and even if you are relying on BLENotify
or BLEIndicate
for automatically sending new values, you might still want to read it manually.
Multiple properties are added to the same characteristic by ORing them together:
BLEIntCharacteristic CharacteristicName(UUID, BLERead | BLENotify);
Values are written to a characteristic with the method writeValue()
. Eg:
CharacteristicName.writeValue(0);
Values are read from a characteristic with the method value()
. Eg:
int myValue = CharacteristicName.value();
Setup and Advertisement
The BLE device needs to be set up before it can be used, and needs to advertise itself to be seen by other devices. There are several methods for this:
BLE.begin()
starts the BLE device. It should be the first thing called before the other BLE setup methods. It returns a status value, 1 if successful, 0 for failure, so you can tell the user if the initialisation fails:
if(!BLE.begin()){ Serial.println("BLE init failed"); }
Once started the address of the BLE device can be found by calling BLE.address()
:
Serial.println(BLE.address());
This is like the MAC address of the Bluetooth device and should be persistent and can be used to identify the correct device on the central side.
The name of the peripheral can be set using the BLE.setDeviceName()
method:
BLE.setDeviceName("myName");
This will show up when searching for the device, but may be truncated to fit within the advertisement packet. You should try to make this unique to help in identifying your device.
The advertisement packet has room for a single service UUID. This will be advertised as your devices main functionality, although the central devices will discover all your devices services and characteristics during the handshake process. Your advertised service is set using:
BLE.setAdvertisedService(MyService);
Characteristics need to be added to a service to be used:
MyService.addCharacteristic(MyCharacteristic);
Services also need to be registered with the main BLE object to be useable:
BLE.addService(MyService);
Finally once everything is set up, you can begin advertisement:
BLE.advertise();
You must also call the BLE.poll()
method at least once per loop()
, so that BLE requests can be serviced.
Other Information
During testing on the Arduino Nano 33 BLE I discovered that the ArduinoBLE
library is incompatible with the pulseIn()
function used to read pulses, such as with ultrasonic sensors, on the 33 BLE as they both use the same hardware timers. If this is required by your project then you will need to implement your own pulse reading function.
Central
For your BLE code to be useful, you will be using it with a central device. For testing purposes, you can download a phone app that is able to scan for BLE devices and read their characteristics. For this I would recommend Bluefruit Connect by Adafruit, available on both iOS and Android.
For more serious usage, you will want to write your own software that can act as the BLE central device on a PC. One of the main obstacles for this is that each OS implements Bluetooth access differently, and often in over-complicated ways. As a solution to this I would recommend a library called SimpleBLE.
SimpleBLE
The SimpleBLE library is a cross-platform library which wraps each OS’s Bluetooth API’s and provides simple functions in either C or C++. This allows the same code to be compiled on Linux, Windows or macOS where needed, and the library will use the correct set of system calls at compile time. A companion project to SimpleBLE is simplepyble which provides Python binding to the C/C++ SimpleBLE library. While these projects are both in early development, through testing it seems to be reasonably reliable. As an alternative to SimpleBLE, you may wish to investigate gattlib, available only on Linux, but here we will be focusing on SimpleBLE, specifically the C version of the library. An example program will be provided which shows reading, writing and notification.
Compiling the SimpleBLE Library
We will provide precompiled versions of SimpleBLE library, you may need to compile it yourself.
Linux
SimpleBLE uses the cmake build system to build its library. To build the library on Linux, using cmake, from the root of the SimpleBLE project:
mkdir build cd build cmake ..
These commands create a new directory for the build files, changes to it, then compiles the whole project using cmake.
Windows
Compiling SimpleBLE on Windows requires Visual Studio, with the C++, UWP, .NET and Git development components installed. To get a command prompt capable of using all the installed tools, launch the “x64 Native Tools Command Prompt for VS”. From this command prompt, launch the “build_examples.bat
” script from the scripts/windows directory in the SimpleBLE project.
Compiling your program with SimpleBLE
To compile a program that uses the SimpleBLE library we need to link in the libraries. We will link them statically so that the executable is self-contained, and doesn’t need to be kept with the library files.
Linux
The library files required for Linux are libsimpleble-static.a
, libsimpleble-c-static.a
, libsimplebluez-static.a
, and libsimpledbus-static.a
. You will also need the header files in the ./include
directory.
To compile a C program using SimpleBLE I used the command:
g++ ./BLECentral.c -o centralTest -I ./include ./libsimpleble-c-static.a ./libsimpleble-static.a ./libsimplebluez-static.a ./libsimpledbus-static.a -l dbus-1 -lpthread
where g++
is the compiler, ./BLECentral.c
is your code, -I ./include
tells it where to find the headers, ./*.a
are the static libraries generated by compiling SimpleBLE, and -ldbus-1
and -lpthread
tells it to link in the required system libraries.
Windows
Compiling with SimpleBLE on Windows requires Visual Studio, with the C++, UWP, .NET and Git development components installed. To get a command prompt capable of using all the installed tools, launch the “x64 Native Tools Command Prompt for VS".
The library files required for Windows are simpleble-static.lib
and simpleble-c-static.lib
. You will also need the header files in the ./include
directory.
To compile a C program using SimpleBLE I used the command:
cl /I include BLECentral.c simpleble-static.lib simpleble-c-static.lib /NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB: msvcrtd.lib
where cl is the compiler, /I include
tells it where to find the header files, BLECentral.c
is your code, simpleble-static.lib
and simpleble-c-static.lib
are the static libraries generated by compiling SimpleBLE, and the /NODEFAULTLIB
switches exclude certain default Windows libraries that conflict and are not needed.