Nei precedenti tutorial avete imparato come utilizzare le funzionalità wifi del chip esp32. A partire da questo tutorial vi illustrerò invece la seconda tecnologia di comunicazione wireless che il chip supporta: il bluetooth.
In particolare tratteremo lo standard Bluetooth Low Energy (BLE), chiamato anche Bluetooth 4.0 o Bluetooth Smart:
Bluetooth Low Energy
BLE è una tecnologia per la realizzazione di reti personali wireless (WPAN); ovvero consente di mettere in comunicazione diversi dispositivi (computer, smartphones, smartwatches…) “vicini” tra loro (distanza massima teorica 100m). Come indica anche il nome, la versione 4.0 dello standard Bluetooth è stata disegnata per ridurre il consumo di energia dei dispositivi collegati.
I dispositivi sono suddivisi in due tipologie:
[checklist]
- central
- peripheral
[/checklist]
I primi (central) sono dispositivi quali PC, tablet o smartphones con elevata quantità di memoria ed elevata capacità di calcolo. I secondi (peripheral) sono invece sensori, tag… con ridotte risorse hardware e bassi consumi. Un central device può essere connesso contemporaneamente a più peripheral devices, mentre non è vero il contrario:
I dispositivi BLE segnalano periodicamente la loro presenza trasmettendo pacchetti di advertising. Il pacchetto di advertising può contenere fino a 31 bytes di dati e la frequenza di trasmissione può essere scelta dal singolo dispositivo: diminuendo tale frequenza è infatti possibile ridurre il consumo energetico.
Se un dispositivo BLE, alla ricezione di un pacchetto di avertising, vuole ottenere maggiori informazioni dal dispositivo che lo ha trasmesso, può richiedere a questo un secondo pacchetto di informazioni (sempre per un massimo di 31 bytes), il pacchetto di scan response. La trasmissione di questo secondo pacchetto di dati è facoltativo:
Un dispositivo BLE può sfruttare i pacchetti di advertising per inviare dati in modalità broadcast. In questo caso tale dispositivo viene chiamato broadcaster, mentre i dispositivi che ricevono i dati sono chiamati observers.
Quanto spiegato sopra è definito all’interno di una specifica BLE chiamata Generic Access Profile (GAP).
esp32
In questo primo tutorial dedicato a BLE vediamo come sviluppare un programma che effettua periodicamente lo scan alla ricerca di periferiche BLE, ovvero riceve i pacchetti di advertising e visualizza in console i dati ricevuti.
Prima di poter eseguire un programma che utilizza il controller Bluetooth del chip, verifichiamo sempre (tramite menuconfig) che tale controller sia abilitato (Component config -> Bluetooth):
Iniziamo il nostro programma includendo gli headers necessari:
#include "esp_bt.h" #include "esp_bt_main.h" #include "esp_gap_ble_api.h" |
Per poter utilizzare il controller Bluetooth, abbiamo bisogno della partizione NVS:
ESP_ERROR_CHECK(nvs_flash_init()); |
Il controller Bluetooth del chip esp32 supporta sia la modalità classic che low energy. Se non è necessaria una delle due modalità, è possibile liberare la memoria normalmente allocata dal framework per gestirla con il comando esp_bt_controller_mem_release(). Nel nostro caso non utilizzeremo la modalità classic, quindi:
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); |
Possiamo ora configurare il controller (utilizzeremo la configurazione di default) e abilitarlo in modalità BLE:
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); |
Il framework esp-idf utilizza lo stack Bluetooth Bluedroid. Questa libreria è stata sviluppata da Broadcom e utilizzata da Android dalla versione 4.2. Bluedroid viene inizializzato e abilitato con i seguenti comandi:
esp_bluedroid_init(); esp_bluedroid_enable(); |
Siamo ora pronti ad eseguire lo scan…
GAP, eventi
In maniera simile a quanto visto con il driver wifi, anche il driver bluetooth viene eseguito in un thread separato rispetto al nostro programma e comunica con esso tramite eventi. Per poter ricevere tali eventi, dobbiamo implementare una funzione di callback. Ogni volta che il driver bluetooth dovrà notificare un evento, chiamerà tale funzione.
Il prototipo della funzione di callback è:
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); |
Si indica al driver quale funzione di callback utilizzare con il metodo esp_ble_gap_register_callback():
ESP_ERROR_CHECK(esp_ble_gap_register_callback(esp_gap_cb)); |
Gli eventi possibili sono tantissimi, vediamo quelli relativi al processo di scan:
Prima di poter eseguire il processo di scan, è necessario configurare i relativi parametri. La configurazione avviene tramite la struct esp_ble_scan_params_t. Importante è che la variable che contiene i parametri sia disponibile durante tutto lo scan; conviene quindi definirla globale:
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 }; |
Con il metodo esp_ble_gap_set_scan_params() si configura il processo di scan passando al driver la struct sopra definita:
esp_ble_gap_set_scan_params(&ble_scan_params); |
Quando il driver ha terminato la configurazione, chiama la funzione di callback con l’evento ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT. In base all’evento sollevato, la funzione di callback riceve anche dei parametri. La Programming Guide del framework riporta – per ogni evento – i relativi parametri. Per questo evento è disponibile la variabile scan_param_cmpl che contiene solo il parametro status.
Nella funzione di callback utilizziamo un comando switch per identificare il singolo evento:
switch (event) { case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: [...] break; |
e verifichiamo se la configurazione ha avuto esito positivo con:
if(param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS) |
In caso affermativo possiamo lanciare il processo di scan con:
esp_ble_gap_start_scanning(10); |
Il parametro indica la durata (in secondi) della scansione.
Una volta avviato il processo di scan, il driver solleva l’evento ESP_GAP_BLE_SCAN_START_COMPLETE_EVT. Anche qui è possibile verificare la corretta esecuzione del processo leggendo il parametro status (attenzione, cambia il nome della variabile che lo contiene!):
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, processo di scan
Quando il processo di scan è in esecuzione, per ogni pacchetto di advertising ricevuto viene sollevato l’evento ESP_GAP_BLE_SCAN_RESULT_EVT.
Questo evento contiene a sua volta dei sottoeventi. E’ possibile identificare di quale sottoevento si tratta leggendo il parametro scan_rst.search_evt. Ci interessano due sottoeventi in particolare:
il primo indica che un nuovo dispositivo è stato rilevato, mentre il secondo che il processo di scan è terminato.
Per ogni dispositivo rilevato sono disponibili diverse informazioni, per ora stampiamo in console il suo indirizzo:
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(":"); } |
L’indirizzo è rappresentato da un array di uint8_t. La dimensione dell’array è definita dalla costante ESP_BD_ADDR_LEN. Normalmente l’indirizzo viene visualizzato in forma esadecimale, con i bytes divisi da :
Gestione elenco devices
Come detto, l’evento ESP_GAP_BLE_SCAN_RESULT_EVT viene sollevato ogni volta che un dispositivo invia un pacchetto di advertising. Questo significa che un singolo dispositivo sarà rilevato più volte durante il processo di scansione.
E’ quindi necessario mantenere un elenco dei dispositivi già noti. Nel mio repository Github trovate un programma di esempio che effettua uno scan e stampa tutti i dispositivi rilevati.
E’ possibile verificare il corretto funzionamento confrontando quanto rilevato dal chip esp32 con quanto rilevato da uno smartphone… per Android ad esempio si può utilizzare l’ottima applicazione nRF Connect di Nordic.
Ecco quanto rilevato dal mio programma:
ed ecco lo screenshot di nRF Connect:
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