In my previous tutorials you learned how to use the wifi interface of the esp32 chip. Starting from this post, I’m going to explain you the second wireless technology the esp32 chip supports: bluetooth.
In particular, my tutorial will be about Bluetooth Low Energy (BLE), sometimes called also Bluetooth 4.0 or Bluetooth Smart:
Bluetooth Low Energy
BLE is a technology to build personal wireless area networks (WPAN); that is it allows to put in communication different devices (computers, smartphones, smartwatches…) “close” to each other (a theoretical maximum distance of 100m). As the name suggests, version 4.0 of the Bluetooth standard was designed to reduce the power consumption of the devices connected to the network.
Devices are divided into two families:
[checklist]
- central
- peripheral
[/checklist]
the first ones (central) are devices like PCs, tablets or smartphones with good processing power and memory. The second ones (peripheral) are instead sensors, tags… with less hardware resources and power. A central device can be connected to more peripheral devices at the same time, while it’s not true the opposite:
BLE devices periodically report their presence by transmitting advertising packets. The advertising packet can contain up to 31 bytes of data and the transmission frequency can be chosen by the single device: reducing this frequency can indeed reduce energy consumption.
If a BLE device, after having received an avertising package, wants to obtain more information from the device that transmitted it, it can request a second packet of information (always for a maximum of 31 bytes), the scan response packet. The transmission of this second data package is optional:
A BLE device can take advantage of advertising packages to send data in broadcast mode. In this case, this device is called a broadcaster, while the devices that receive the data are called observers.
What explained above is defined within a BLE specification called Generic Access Profile (GAP).
esp32
In this first tutorial you’ll learn how to develop a program that will periodically scan the air looking for BLE devices, that is a program which receives advertising packets and displays the data received in the serial console.
Before compiling a program which uses the Bluetooth controller, make sure (using menuconfig) that the controller is enabled (Component config -> Bluetooth):
Start your program with the required header files:
#include "esp_bt.h" #include "esp_bt_main.h" #include "esp_gap_ble_api.h" |
You also need to initialize the NVS partition, used by the Bluetooth driver:
ESP_ERROR_CHECK(nvs_flash_init()); |
the Bluetooth controller of the esp32 chip supports both the classic and the low energy mode. If one of this two modes is not required in your program, you can release the memory the framework normally allocates to manage it using the esp_bt_controller_mem_release() command. In this example you’re not going to use the classic mode, so:
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); |
Now you can configure (using the default settings) the controller in BLE mode:
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); esp_bt_controller_init(&bt_cfg); esp_bt_controller_enable(ESP_BT_MODE_BLE); |
The esp-idf framework esp-idf includes the Bluedroid bluetooth stack. This library was developed by Broadcom and used by Android since version 4.2 Bluedroid is initialized and enabled with the following commands:
esp_bluedroid_init(); esp_bluedroid_enable(); |
Now you’re ready to start scanning…
GAP, events
In a similar way to what you learned about the wifi driver, the bluetooth driver also runs in a thread separate from our program and communicates with it via events. In order to receive such events, you have to implement a callback function. Whenever the bluetooth driver has to notify an event, it will call that function.
The prototype of the callback function is:
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |
You tell the driver which callback function has to use with the esp_ble_gap_register_callback() method:
ESP_ERROR_CHECK(esp_ble_gap_register_callback(esp_gap_cb)); |
The Bluetooth driver handles several events, there are the ones related to the scan process:
Before being able to start the scan process, you have to configure the scan parameters. The configuration is performed using the esp_ble_scan_params_t struct. It’s very important that the variable with the scan parameters is available during all the scan process; it’s therefore necessary to define it globally:
static esp_ble_scan_params_t ble_scan_params = { .scan_type = BLE_SCAN_TYPE_ACTIVE, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, .scan_interval = 0x50, .scan_window = 0x30 }; |
With the esp_ble_gap_set_scan_params() method you configure the scan process passing the struct defined above to the driver:
esp_ble_gap_set_scan_params(&ble_scan_params); |
When the driver has finished the configuration, it calls the callback function with the event ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT. Depending on the event raised, the callback function also receives some parameters. The framework’s Programming Guide explains – for each event – the related parameters. For this event, it’s available the variable scan_param_cmpl that contains only the status parameter.
In the callback function you can use the switch statement to identify each event:
switch (event) { case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: [...] break; |
and check if the configuration was successful with:
if(param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS) |
If so, you can start the scan process:
esp_ble_gap_start_scanning(10); |
The parameter is the scan duration (in seconds).
Once the scan process has started, the driver raises the ESP_GAP_BLE_SCAN_START_COMPLETE_EVT event. For this event too it’s possible to verify the correct execution by reading the status parameter (pay attention: the name of the variable which contains the parameter changes!):
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: if(param->scan_start_cmpl.status == ESP_BT_STATUS_SUCCESS) printf("Scan started\n\n"); else printf("Unable to start scan process"); break; |
GAP, scan process
During the scan process, for each advertising packet the chip receives the event ESP_GAP_BLE_SCAN_RESULT_EVT is raised.
This event contains some subevents. You can identify which subevent was raised reading the scan_rst.search_evt parameters. Two subevents are in particular interesting:
the first tells you that a device was detected, while the second one that the scan process completed.
For each detected device, various information is available. For now let’s print its address in the console:
case ESP_GAP_BLE_SCAN_RESULT_EVT: if(param->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) { printf("Device found: ADDR="); for(int i = 0; i < ESP_BD_ADDR_LEN; i++) { printf("%02X", param->scan_rst.bda[i]); if(i != ESP_BD_ADDR_LEN -1) printf(":"); } |
The address is an uint8_t array, whose size is defined by the ESP_BD_ADDR_LEN constant. The address is normally displayed in hex form, with the bytes separated by :
Device list management
As explained above, the ESP_GAP_BLE_SCAN_RESULT_EVT event is raised everytime a device sends an advertising packet. This means that a single device will be detected multiple times during the scan process.
It’s therefore necessary to maintain a list of the known devices. In my Github repository you can find a test program that scans the network and prints all the detected devices.
You can verify if it works correctly comparing what the program detects with the BLE devices listed by a smartphone… for Android for example you can use the very good nRF Connect application by Nordic.
Here’s what my program detected:
and here’s the nRF Connect’s screenshot:
good short article, enjoy them,
Could you please address:
a. BLE in peripheral mode.
b. BLE and wiFi in a single program.
when will you publish your next application.
Thx – please keep up the good work
Hi,
how can i measure the latency and data rate of BLE ?
you can’t do it using only the framework, you probably have to implement some functions in your code (adding a timestamp to each packet can help in measuring the latency but tx and rx must be well syncronized…)
Ciao! e complimenti per i tuoi aritcoli! volevo chiederti quanto consuma all’incirca il modulo in trasmissione di pacchetti advertising?
Ed in questo pacchetto se ho capito bene possiamo anche fornire informazioni circa lo stato di qualche i/o? ad esempio lo stato di una serie di led o pulsanti?
Grazie!
Purtroppo il consumo del modulo esp32 è molto dibattuto… ancora oggi è molto lontano da quello di altri chip (20mA vs 1.5mA) quindi per applicazioni che devono consumare molto poco non è ancora il chip ideale. Passando all’altra domanda: si è possibile inserire quello che vuoi nel payload del pacchetto, ovviamente l’applicazione che lo riceve deve essere in grado di interpretarlo e non avrai “confidenzialità” perché chiunque ascolta i pacchetti di AVD può leggere i dati che invii.
Ciao Luca,
non mi è chiaro se e, eventualmente, come un modulo ESP32 può funzionare come CENTRAL e quindi scambiarsi i dati con un altro ESP32.
Grazie
ciao Leo, sì è possibile… insieme al framework vi sono alcuni esempi in tal senso
Ciao Luca,
complimenti per i tuoi tutorial.
Volevo chiederti pe è possibile usare il modulo ESP32 come un normale modulo bluetooth tipo il HC-05. Ho installato sull’ESP32 il software per gestirlo con i comandi AT, riesco ad attivare il bluetooth seguendo un esempio ma non riesco a connettermi, ad esempio, con “Serial Bluetooth Terminal”.
Mi puoi aiutare?
Grazie