Arduino Nano 33 IOT – WiFi, BLE e IMU

In questo articolo vi parlo dell’Arduino Nano 33 IOT: quali sono le differenze con gli altri prodotti della famiglia Nano e come si confronta con prodotti commerciali simili. Vi spiegherò come preparare l’ambiente di sviluppo per poter utilizzare al meglio questa board e aggiornarne anche il firmware. Darò dei consigli rapidi sull’utilizzo del Wi-Fi dopodichè ci tufferemo in una parte più corposa in cui vi spiego come funziona il Bluetooth Low Energy, quali sono le differenze col Bluetooth classico, e passeremo alla realizzazione di uno sketch che legge la tensione di batteria, accelerometro e giroscopio e ritrasmette questi dati verso un’applicazione Android che andremo a realizzare con un tool gratuito e facile da utilizzare.

Sponsor

L’Arduino Nano 33 IOT, oggetto dell’articolo, è stato gentilmente offerto da Arduino insieme ad un codice sconto in esclusiva per i fan di Settorezero.com! Per acquistare prodotti Arduino sullo store ufficiale con uno sconto del 10% registratevi su store.arduino.cc, sfogliate il catalogo, aggiungete i prodotti nel carrello e alla fine dello shopping cliccate sull’icona del carrello in alto a destra, selezionate Go To Cart e nel campo Discount Code scrivete SETTOREZEROCOM (senza punto!) e premete Apply. I costi di spedizione partono da meno di €4 con corriere espresso. Codice Sconto valido dall’1 al 30 Aprile 2021. Le opinioni qui riportate sono del tutto personali e non influenzate in alcun modo dal fornitore del prodotto.

La famiglia Nano

L’Arduino Nano 33 IOT appartiene alla famiglia Nano delle schede di sviluppo Arduino, che meccanicamente sono caratterizzate da un form-factor ridotto al minimo (45x18mm), due file da 15pin a passo 2.54mm e spaziate tra loro di 600mil (come sugli IC “larghi”).

Le file di pin oltre a presentare la possibilità di avere montati degli headers (sullo shop di Arduino per molte boards vengono vendute le versioni con e senza headers da saldare), presentano anche la castellatura che permette a queste schede di essere direttamente saldate su board più grandi predisposte. Attualmente della famiglia Nano fanno parte 5 schede:

A breve si aggiungerà alla famiglia anche la Arduino Nano RP2040 Connect

Una caratteristica per me molto interessante di tutte queste schede (presente su tutte tranne che sulla Nano) è che possono essere alimentate fino a 21V avendo a bordo un regolatore switching MPM3610 molto efficiente: un componente allo stato dell’arte che non necessita nemmeno di induttanze esterne dal momento che ha buona parte della circuiteria necessaria al funzionamento già inglobata all’interno.

Le prime due board della lista di cui sopra hanno gli I/O a 5V, a differenza di tutte le altre che funzionano invece a 3.3V…

Ecco perchè si chiamano Nano 33 ma in realtà mi aspettavo che anche la futura RP2040 possedesse tale dicitura, ma in realtà il nome poi sarebbe diventato troppo lungo.

…e utilizzano microcontrollori ad 8 bit: il classico ATMega 328 (quello di Arduino UNO) la prima, un più performante ATMega 4809 la seconda.

In particolare la Arduino Nano Every è quella più economica di tutte, €9, e viene venduta anche in bundle da 3 pezzi che fanno risparmiare ancora di più.

Le due successive Nano 33 BLE e BLE Sense usano invece un microcontrollore della Nordic Semiconductor: nRF52840 (ARM Cortex-M4 64MHz) che ha anche funzionalità Bluetooth LE e pairing tramite NFC. Il microcontrollore su queste due schede si trova inglobato all’interno di un modulo schermato NINA-B3 della U-Blox. Entrambe queste due schede hanno a bordo un’IMU a 9 assi LSM9DS1 (Accelerometro, Giroscopio, Magnetometro). La Sense in più ha anche una carovana di altri sensori: pressione, temperatura, umidità, microfono, gesture, luce, colore, prossimità, ed è la scheda più costosa di questa famiglia (€27 senza headers, iva esclusa – Questa viene anche venduta insieme ad un interessante kit educativo per il Machine Learning). La 33 IOT, oggetto di questo articolo, la vediamo adesso più in dettaglio.

Giusto per completezza di informazioni: con un form-factor più o meno simile a queste c’è l’Arduino Micro, che è però un po’ più lungo (48mm) in quanto ha un totale di 34pin. Si tratta in pratica di una versione miniatura del Leonardo in quanto monta l’ATMega 32U4 e accetta come tensione di alimentazione max 9V.

Arduino Nano 33 IOT

La Nano 33 IOT utilizza un microcontrollore SAM D21 a basso consumo (ARM Cortex-M0+ 48MHz – per cui ha una potenza di elaborazione inferiore alle due 33 BLE, ma superiore alle Nano ed Every) e possiede un modulo NINA-W102 della U-Blox basato su un ESP32 (più precisamente un ESP32-D0WDQ6 con una memoria flash da 16Mbit, cioè 2MB) che, in questo caso specifico, non viene utilizzato come microcontrollore principale ma unicamente per fornire la connettività WiFi e Bluetooth LE e comunica con l’MCU attraverso un bus SPI.

Per chi è abituato ad utilizzare i vari modelli di NodeMCU può sembrare strano il fatto di non utilizzare ESP32 come unità principale ma piuttosto come “accessorio”. In realtà i moduli della U-Blox sono altamente ingegnerizzati: se fate il confronto con i NodeMCU classici notate che il modulino schermato degli U-Blox è molto più piccolo, questo perchè non c’è la necessità di riportare all’esterno tutti i pin dell’ESP32, viene utilizzato a bordo il quantitativo di memoria flash strettamente necessario ad implementare il firmware per la connettività e l’antenna utilizzata non è a PCB ma è 3D.

NodeMCU ESP32 vs Arduino Nano 33 IOT
NodeMCU ESP32 (sinistra) vs Arduino Nano 33 IOT (destra). Hanno lo stesso numero di pin.

Osservando la foto sopra, l’Arduino Nano 33 IOT ha dimensioni quasi pari alla metà dei moduli “classici” basati su ESP32 pur avendo lo stesso numero di pin. La miniaturizzazione viene fuori da tanti accorgimenti: ad esempio avendo SAM D21 una periferica USB, la Nano non ha bisogno di un bridge USB/UART aggiuntivo come montato sui NodeMCU (il CP2102 o il CH340G per fare due esempi). Nonostante la Nano 33 IOT sia più piccola di un NodeMCU, ci sono addirittura molte cose in più che vedremo tra poco. Rimanendo per ora sul discorso WiFi sottolineo anche un altro fattore importante: questi moduli possiedono tutta una serie di certificazioni in vari paesi del mondo come è possibile leggere dal datasheet del modulo stesso (vedi capitolo 6).

Le certificazioni chiaramente sono valide fintantoché non viene cambiato il firmware precaricato a bordo del modulo.

La presenza di IOT (Internet Of Things) nel nome denota quindi il fatto che, a differenza delle altre della serie (fin’ora, perchè attendiamo la nano con l’RP2040), possiede una connettività WiFi che viene ulteriormente arricchita di features rispetto ad altre board con cui viene naturale fare il confronto dal momento che a bordo è presente un Elemento di Sicurezza: un chip ATECC608A della Microchip che si occupa di tutta la parte di CrittografiaAutenticazione e Gestione dei certificati SSL necessaria per servizi di rete sicuri come il collegamento ad Amazon Web Services, Arduino IOT Cloud e qualsiasi altro servizio richieda tutte queste caratteristiche.

La Nano 33 IOT possiede anche una IMU, ma a differenza delle due Nano BLE, questa manca del magnetometro per cui è una IMU a 6 assi (accelerometro e giroscopio), modello LSM6DS3. Avendo il SAM D21 anche una periferica I2S, è possibile utilizzare esempi che sfruttano le capacità audio digitali.

Tra le schede della serie Nano (nella cui comparativa, ricordo, non rientra ancora la RP2040 di cui si hanno ancora poche notizie), la Nano 33 IOT è quella che si piazza sicuramente a metà strada come rapporto prezzo/prestazioni (€16 senza headers, iva esclusa) e per ora è l’unica ad avere il WiFi, mentre se necessitiamo di una scheda di sviluppo piccola e dotata solo di Bluetooth LE c’è sicuramente più scelta (BLE e BLE Sense).

Utilizzo in Arduino IDE

Per l’esempio, nelle immagini terrò conto del nuovo Arduino IDE 2 (qui la pagina Github dell’ultima release sempre aggiornata) anche se in versione Beta, dal momento che io sto utilizzando questo, che trovo ad un livello superiore rispetto al vecchio IDE

Sono contrario a quelli che dicono cose del tipo «eh! ma è Beta! Aspettiamo che esce la release definitiva» perchè non è un discorso da vero sviluppatore nè tantomeno collaborativo. Bisogna utilizzare le versioni Beta per segnalare eventuali bug o miglioramenti da apportare utilizzando gli strumenti messi a disposizione. Ad ogni modo per l’installazione di board/librerie aggiuntive e tutto il resto è perfettamente uguale al vecchio e con le board Arduino e, per quello che mi riguarda, funziona già perfettamente.

Per utilizzare la Nano 33 IOT è necessario installare il gruppo di boards ufficiali Arduino basate sul SAMD21 per cui da Tools > Board: “xxx” > Board Manager (oppure sul nuovo IDE cliccando il tasto a forma di circuito integrato) cercate Arduino SAMD Boards:

La selezione giusta è Arduino SAMD Boards (32-bits Cortex-M0+) by Arduino. Chiaramente di questa installazione non entrano a far parte gli accessori presenti sulle schede, per cui bisogna installare le librerie aggiuntive per l’IMU e per il modulo U-Blox. Dal Menù Tools > Library Manager (oppure cliccando sul tasto a forma di cartella sul nuovo IDE2) cerchiamo la libreria per l’IMU digitando LSM6DS3:

La selezione corretta è la Arduino_LSM6DS3 by Arduino (vediamo che nella descrizione è presente Arduino Nano 33 IoT).

Anche se a bordo della nostra board il modulo U-Blox implementa sia WiFi che Bluetooth, è necessario installare due librerie separate. Installiamo la libreria per utilizzare il modulo Bluetooth a bordo della Nano 33 IOT cercando ArduinoBLE (tutto attaccato):

La selezione corretta è ArduinoBLE by Arduino. Questa libreria è buona per tutti i moduli BLE presenti sui prodotti ufficiali Arduino, anche quelli implementati col chip della Nordic piuttosto che con l’ESP32. Installiamo infine la libreria per utilizzare il WiFi con i moduli U-Blox digitando WiFiNINA. Qui l’elenco di librerie restituite è molto lungo e bisognerà scorrere in basso parecchio prima di trovare quella giusta:

Anche qui la selezione corretta è quella che riporta scritto alla fine by Arduino. In questo caso, la libreria WiFiNINA è chiaramente valida solo per i prodotti che implementano il WiFi con i moduli NINA della U-Blox. Alla fine delle installazioni nel menù File > Examples possiamo trovare tutti gli esempi relativi ai componenti che abbiamo appena installato e cominciare a sperimentare:

Esempi WiFi

Non mi dilungherò tanto sulla parte di esempi relativi al WiFi. Se siamo abituati con i vari modelli di development boards basate su ESP8266 e ESP32 noteremo che praticamente i nomi delle funzioni e tutto il resto è quasi uguale con la differenza che ora, per utilizzare il WiFi, includeremo sempre e solo <WiFiNINA.h>, mentre il nome dell’oggetto per utilizzare la connessione sicura HTTPS (anzichè http) è WiFiSSLClient anzichè WiFiClientSecure.

Uno dei primi esempi che sicuramente andremo a provare è WiFiWebClient.ino che si collega ad una pagina web (www.google.com) e ne scarica il contenuto visualizzandolo sul terminale seriale. Approfitto di questo esempio per dire due cose:

Aggiornamento Firmware WiFi NINA

Eseguendo lo sketch noterete che ad un certo punto sul terminale seriale compare la frase Please upgrade the firmware. Questo perchè a riga 57 dello sketch c’è un’istruzione che confronta la release del firmware a bordo del modulo WiFi NINA con una costante presente nella libreria e che riporta un numero di versione più aggiornato. Come dicevo sopra, l’aggiornamento del firmware del modulo porta alla perdita delle certificazioni ma chiaramente a noi, che ne facciamo un uso hobbystico, non importa più di tanto anche perchè siamo ben consapevoli che installando il firmware ufficiale rilasciato da Arduino con l’updater non può accadere nulla di male.

La versione beta attuale dell’IDE2 non supporta ancora l’aggiornamento del Firmware del modulo WiFi, che va fatto pertanto con il vecchio IDE 1.8.13, la procedura è molto semplice e sul sito Arduino è illustrata passo passo: per cui non ve la illustro qui, insomma si tratta semplicemente di selezionare una funzione presente nel menù del vecchio IDE (Tools > WiFi101/WiFiNINA Firmware updater), caricare uno sketch sulla board e premere un pulsante a video.

Se come me state utilizzando il nuovo IDE, per aggiornare il Firmware dovrete installare board e libreria per il modulo NINA anche sul vecchio 1.8.13. Anche se sul nuovo IDE compare lo sketch Firmware Updater ricordo che l’aggiornamento del firmware è un tool in due parti: la parte che gira sulla scheda, costituita dallo sketch e che potete caricare anche dal nuovo IDE, e la parte che gira sull’IDE stesso, che nel 2 beta non è ancora presente.

Durante l’aggiornamento vi renderete conto che i numeri di versione riportati non combaciano, e questo per un motivo preciso. Ad esempio lo sketch che informa del numero di versione mi dice che sul modulo c’è la versione 1.2.3 e che è disponibile la versione 1.4.3 (questo valore, 1.4.3, è riportato nel file di libreria \WiFiNINA\src\WiFi.h con la costante #define WIFI_FIRMWARE_LATEST_VERSION “1.4.3” e attualmente combacia con il valore presente sulla pagina della release di Github):

Dopo l’aggiornamento mi viene detto che ora la versione installata è la 1.3.0 e che comunque c’è ancora una 1.4.3:

Questo perchè la 1.3.0 è l’ultima versione disponibile proprio nella cartella di Arduino IDE 1.8.13 :

Purtroppo non basta scaricare l’ultima release del firmware e inserirla a mano in questa cartella rispettando la nomenclatura: il tool di updater installerà sempre come ultima versione disponibile la 1.3.0 dato che questo valore è hardwired nel file eseguibile JAR dell’updater.

Se come me volete a tutti i costi fare questa operazione senza aspettare che venga rilasciato il tool apposito nel nuovo IDE2, potete scaricare l’ultima release del plugin WiFi Firmware updater: scompattate l’archivio, si ottiene una cartella chiamata WiFi101, incollate tale cartella in C:\Program Files (x86)\Arduino\tools sovrascrivendo quella già presente; ora dal vecchio IDE 1.8.13 vedete che è possibile installare la release 1.4.3 (ricordate di caricare sempre prima l’updater sketch sulla board):

Mancato collegamento ad Internet

Sulla mia rete di casa, quando mi collego ad uno Switch+AP di rete piuttosto che al router principale, dagli sketch non riesco a collegarmi ad internet, ed è una cosa che mi succede anche con i prodotti Espressif.

Il sospetto va a qualche configurazione strana che ho negli switch dato che è un difetto che ho solo con essi e non quando mi collego direttamente ad un router, compreso anche l’AP generato dal cellulare.

Risolvo in genere questo problema configurando a mano il WiFi mediante la funzione WiFi.config(). Nella parte alta dello sketch WiFiWebClient, diciamo a riga 42, definisco degli indirizzi IP statici:

IPAddress myip(192,168,1,93);
IPAddress dns(8,8,8,8);
IPAddress gtw(192,168,1,1);
IPAddress snet(255,255,255,0);

che sono, rispettivamente, l’indirizzo IP statico che voglio assegnare al mio dispositivo, l’indirizzo del DNS (8-8-8-8 è l’indirizzo del server DNS primario di Google, potete utilizzare anche 1-1-1-1 che è quello di CloudFlare), l’indirizzo del Gateway (il router nel nostro caso) e la maschera di sottorete. Dopodichè subito dopo l’istruzione WiFi.begin(ssid,pass) configuro la rete manualmente:

WiFi.config(myip, dns, gtw, snet);

In questo modo il collegamento internet mi va sempre a buon fine senza problemi. Maggiori informazioni su WiFiConfig della libreria di Arduino le trovate qui.

Attenzione quando eventualmente convertite gli sketch che già usate per ESP32: la funzione WiFi.config() di Arduino è diversa da quella che avete utilizzato fin’ora con gli ESP32: accetta i parametri disposti in maniera diversa e in più quella di ESP32 for Arduino presenta anche un parametro in più per il dns secondario.

Il Bluetooth Low Energy (BLE)

Prima di continuare, dato che non è mia abitudine fornire del codice senza dare spiegazioni, devo fare alcune premesse sia per chi è alle prime armi, sia agli esperti che possono leggere per eventualmente correggermi. Chi è abituato ad utilizzare i moduli HC-05 / HC-06 per il collegamento Bluetooth, si trova un po’ spaesato perchè qui le cose si fanno più articolate. Quei modulini Bluetooth (e altri) sono moduli Bluetooth classici  nel senso che implementano le funzionalità di uno standard che è chiamato, appunto, Bluetooth Classic: lo standard sviluppato fino al 2009 (versioni 1.0, 1.11.22.0, 2.1 e 3.0).

In particolare i moduli HC-05 e HC-06 utilizzano lo standard 2.0 definito nel 2004

Una delle modalità di comunicazione più sfruttate del Bluetooth Classic (che è quella per cui in realtà il Bluetooth venne inizialmente concepito) è la SPP (Serial Port Profile) ovvero sostituire i cavi RS232 con una comunicazione wireless. Questa modalità di comunicazione è punto-punto e prevede la famosa procedura di accoppiamento che tutti abbiamo imparato a mettere in pratica. Un modulo Bluetooth classico esterno è molto facile da utilizzare dato che ci si interfaccia unicamente con una semplice periferica UART sulla quale possiamo fare ciò che vogliamo (in particolare scegliere come comunicare i dati).

A partire dal 2010 (anno in cui è uscita la versione Bluetooth 4.0)  è stato definito un nuovo standard: il Bluetooth Low Energy (BLE) che si prefigge di rimediare ad uno dei più grandi problemi del Bluetooth Classic: il consumo esagerato di energia (dovuto soprattutto al fatto che i due device sono in comunicazione costante).

I dispositivi BLE sono comunque retrocompatibili col Bluetooth Classic.

La versione BLE più recente ad oggi è la 5.2. Per quello che interessa a noi attualmente, con il BLE non c’è più il classico profilo SPP (RX/TX)

Questo non è completamente vero perchè è possibile emulare il servizio UART al di sopra del BLE, questo esperimento l’ho già fatto anni fa utilizzando l’ESP32.

Ma si comunica in maniera più articolata definendo tutta una serie di oggetti e, in aggiunta, non viene più eseguita quella fastidiosa procedura di accoppiamento tra i dispositivi. In pratica per le comunicazioni a basso datarate (come appunto una comunicazione seriale) il BLE implementa una specie di Bullettin Board ovvero la struttura della comunicazione è del tipo Publish – Subscribe : c’è un dispositivo chiamato Peripheral che pubblica un messaggio in una sorta di “canale” (anche se è sbagliato chiamarlo così), eventualmente inviando una notifica, e i dispositivi che si sono “iscritti al canale” possono leggerlo: questi dispositivi sono chiamati Central.

Potrebbe venire spontaneo pensare al contrario: cioè il central è quello che trasmette i messaggi. NO. Il Peripheral è quello che implementa il Bullettin Board e va considerato come un Server mentre i dispositivi Central (client) sono quelli che si collegano ad esso per leggere, ma anche scrivere, messaggi/variabili.

Il dispositivo Peripheral può erogare uno o più Servizi e ogni servizio può possedere una o più Caratteristiche. Il servizio può essere immaginato come una sorta di Contenitore per le varie informazioni costituite dalle caratteristiche. Ad esempio una stazione meteo potrebbe avere il servizio Meteo costituito dalle caratteristiche Temperatura, Pressione, Umidità e magari anche il servizio Ora Precisa se magari possiede un GPS con il quale potrebbe fornire le caratteristiche Ora, Data : questi sono solo esempi e siamo noi a decidere come strutturare il dispositivo periferico.

A loro volta i dispositivi centrali, sempre tenendo presente l’esempio della stazione meteo, possono iscriversi per ricevere le notifiche, quando, ad esempio, cambia il valore di temperatura, umidità ecc. Capite che la struttura pensata in questo modo a parte essere più organizzata, non richiede il pairing dei devices in quanto ogni servizio e caratteristica ha un proprio codice identificativo che è univoco e quindi basta quello, non richiede la presenza costante del collegamento tra i due dispositivi e in più permette al dispositivo centrale di iscriversi solo a ciò che interessa, ricevendo una notifica quando opportuno, e al dispositivo periferico di erogare i suoi servizi a più dispositivi in ascolto non essendo vincolato al pairing.

Modello Bullettin-Board implementato dal BLE

I nostri valori da trasmettere e ricevere, quindi, saranno contenuti nelle caratteristiche, per cui le caratteristiche sono  definite come variabili da leggere o da scrivere. Tutta questa roba di cui ho appena parlato è definita da un documento che si chiama Generic ATTribute profile (GATT).

Utilizzare il BLE su Arduino

Come dicevo sopra, è chiaro che quello che sto per dire va bene per tutti i moduli BLE presenti sui prodotti ufficiali Arduino anche se implementati con chip diversi dato che la libreria è unica.

E’ necessario innanzitutto includere la libreria BLE:

#include <ArduinoBLE.h>
Bisogna poi definire i codici identificativi sia per i servizi che per le caratteristiche. Questi codici prendono il nome di GUUID (Globally Universally Unique IDentifier) o più semplicemente UUID (troverete spesso alternati questi due termini, ma indicano la stessa cosa): si tratta di semplici stringhe esadecimali. C’è da sottolineare che alcuni gruppi di GUUID sono già assegnati e servono per definire dei servizi standard ricercabili tramite il Service Discovery Protocol (SDP) che è una funzionalità dei dispositivi Bluetooth che permette, ad esempio, di fare cose del tipo «cercami in giro i dispositivi che possono ricevere l’audio via Bluetooth».
 
E’ possibile utilizzare dei servizi online (questo o questo) che generano dei GUUID random a 128bit (nel formato richiesto, ovvero 5 gruppi, separati da trattino, di 4-2-2-2-6 bytes) che dovrebbero essere sicuri da utilizzare in quanto le combinazioni possibili su un numero a 128 bit sono davvero tante. Il GUUID viene impostato solo per il/i servizi, dopodichè per le caratteristiche facenti parte di quel servizio si aumenta di 1 il primo gruppo (quello più a sinistra) di 4 bytes.
 
Per esempio nel codice andremo a scrivere cose simili:
const char* UUID_myservice         = "84582cd0-3df0-4e73-9496-29010d7445dd";
const char* UUID_mycharacteristic1 = "84582cd1-3df0-4e73-9496-29010d7445dd";
const char* UUID_mycharacteristic2 = "84582cd2-3df0-4e73-9496-29010d7445dd";

In questo esempio sopra ho definito il GUUID di un solo servizio e i GUUID di due caratteristiche che poi definirò come appartenenti al servizio: vedete che il GUUID è lo stesso del servizio, ma aumentato di 1 unità sul primo gruppo. Istanziamo quindi tutti i servizi che ci interessano mediante l’oggetto BLEService:

BLEService myService(UUID_myservice);

Dobbiamo quindi istanziare le caratteristiche che vorremo definire, ovvero i valori che vogliamo trasmettere o ricevere. Essendo le caratteristiche valori variabili, vanno istanziate in base al tipo di variabile che devono trasportare: un numero intero, un float, una variabile booleana, un byte ecc ecc. L’elenco completo dei tipi di caratteristica lo trovate come sempre sulla documentazione ufficiale.

Nel mio esempio mi definisco due caratteristiche di tipo float:

BLEFloatCharacteristic myCharacteristic1(UUID_mycharacteristic1, BLERead|BLENotify);
BLEFloatCharacteristic myCharacteristic2(UUID_mycharacteristic2, BLERead|BLENotify);

Vedete che il primo parametro è la stringa che contiene il GUUID della caratteristica e il secondo è un parametro (una maschera) che imposta la caratteristica in lettura (BLERead, ovvero il dispositivo peripheral/server la trasmette e i dispositivi central/client la leggono), in scrittura (BLEWrite) o altro (vedi documentazione), in particolare vedete che ho fatto l’OR (|) con il parametro BLENotify che serve ad inviare al client una notifica quando il valore della caratteristica cambia. Nel caso in cui la caratteristica sia una stringa (un array di bytes) si aggiunge un terzo parametro che è il valore di default della stringa, ed eventualmente un quarto che ne indica la dimensione. Nella funzione di setup si avvia quindi il modulo BLE:

BLE.begin()

Questa funzione restituisce true se il modulo viene inizializzato correttamente, dopodichè è possibile impostare un nome con il quale il device verrà identificato sui dispositivi client:

BLE.setLocalName("Settorezero");

Si impostano quindi i servizi che devono essere pubblicizzati mediante il metodo setAdvertisedServicedell’oggetto BLE:

BLE.setAdvertisedService(myService);

E ai singoli servizi vengono aggiunte le loro caratteristiche mediante il metodo addCharacteristic:

myService.addCharacteristic(myCharacteristic1);
myService.addCharacteristic(myCharacteristic2);

Il servizio viene quindi aggiunto al modulo BLE:

BLE.addService(myService);

E’ possibile quindi, in maniera facoltativa, impostare dei valori iniziali alle caratteristiche, utilizzando la stessa funzione che useremo dopo per trasmettere i valori, in modo che non trasmettano valori a caso:

myCharacteristic1.writeValue(0);
myCharacteristic2.writeValue(0);

Infine si avvia la pubblicazione (o forse meglio: pubblicizzazione) del servizio BLE:

BLE.advertise();

Tutti questi passaggi vanno eseguiti in sequenza precisa, altrimenti il modulo BLE non funziona correttamente.  Dopo fatto questo, il device client/central può cercare il dispositivo attivando il Bluetooth e comparirà nell’elenco il nome che abbiamo impostato col metodo setLocalName: come ho ripetuto più volte, non va eseguito l’accoppiamento.

Le app proprietarie di dispositivi Bluetooth, come ad esempio gli  smartwatch, in realtà cercano, trovano il dispositivo che trasmette il GUUID proprietario e si collegano direttamente, senza presentare una lista di device trovati.

Utilizzando un’app gratuita per Android, come LightBlue® o nFR Connect è possibile tirare fuori tutti questi valori che abbiamo impostato e quindi fare anche debug nel caso qualcosa non funzionasse, e vi dico che queste app mi sono tornate molto utili durante il debug per capire perfettamente il funzionamento dei protocolli del BLE.

Nel main potremmo controllare se sono presenti dispositivi centrali, interrogandoli, con:

BLEDevice central = BLE.central();

E quindi controllare se eventuali dispositivi client sono connessi controllando il valore assunto dalla proprietà connected del BLEDevice:

central.connected();

una volta capito se siamo connessi, possiamo fare tutte le nostre operazioni: inviare dati e/o riceverli. Se abbiamo impostato una caratteristiche in modalità scrittura e il client ha scritto (inviato) un valore su questa caratteristica, ce ne possiamo accorgere e recuperare il dato inviato con:

if (myCharacteristic.written())
           {
           command = myCharacteristic.value();

Se invece vogliamo inviare un valore al client:

myCharacteristic1.writeValue(0);

Oltre al polling come ho appena mostrato è possibile, in alternativa, impostare degli eventHandlers per richiamare in automatico delle funzioni allo scatenarsi di determinati eventi (lettura, scrittura, connessione, disconnessione) in modo anche da scrivere il codice in maniera più pulita (setEventHandler caratteristiche, setEventHander classe BLE). Nel codice di esempio troverete il sistema in polling come ho appena esposto perchè a mio avviso più facile da capire per i meno esperti, anche se personalmente non lo preferisco.

Demo trasmissione dati via BLE

Per mettere insieme tutte queste cose di cui vi ho appena parlato, mi sono imposto un esercizio da svolgere come faccio sempre quando studio un dispositivo nuovo:

  • Alimentare la Arduino Nano 33 IOT con un batteria utilizzando il pin Vin e leggere la tensione della batteria utilizzando un partitore su un  ingresso analogico.
  • Riportare il valore della batteria via BLE sia come tensione che come percentuale di batteria.
  • Leggere i dati della IMU un tot di volte al secondo facendone la media e inviarli via BLE.
  • Realizzare una semplice applicazione Android che si collega a questo dispositivo per riporta i valori letti sulla schermata del cellulare.

Il codice sorgente di questo esempio si trova all’ultimo paragrafo di questo articolo. Lo schema da realizzare è il seguente:

Questo è il pinout della Arduino Nano 33 IOT:

Vi consiglio comunque di andare pagina ufficiale, sezione Documentation per scaricare il PDF del pinout, che comprende anche il pinout esteso nonchè lo schema elettrico.

Valutazione tensione batteria

La Nano 33 IOT accetta, sul pin Vin, una tensione minima di 5V, per cui 4 batterie AA vanno bene ma non appena scendono a 5V (1.25V per batteria) devo trasmettere una segnalazione di batteria scarica altrimenti il dispositivo rischia di non funzionare più. La tensione della batteria la riporto su di un ingresso analogico (ho usato A7 perchè posizionato vicino Vin) utilizzando un partitore di tensione costituito da R1 (47kΩ) e R2 (33kΩ) questo perchè, ricordo se non fosse ancora chiaro, i pin del SAM D21 sono a 3.3V, per cui non posso applicare più di 3.3V agli I/O. Con questi valori di resistenza avrò sul pin analogico una tensione Van pari a:

Considerando le batterie tutte cariche (Vbat=6V) sul pin analogico avrò una tensione Van pari a 2.47V, rientro così nei 3.3V massimi e, anzi, con questi valori di R1 e R2 posso applicare fino a (3.3/0.4125)=8V.

Nel codice di esempio, per essere il più preciso possibile sulla tensione letta, ho riportato i valori misurati delle due resistenze, il rapporto del partitore (che ho chiamato arbitrariamente VDK) e i valori di tensione minima (BAT_LOW) e massima (BAT_FUL) per i quali voglio indicare 0% e 100%:

#define R1  46.2F
#define R2  32.86F
#define VDK  (R2/(R1+R2))
 
#define BAT_FUL 6
#define BAT_LOW 5
 
#define AN_BAT  A7

Il condensatore da 1nF messo in parallelo tra GND e l’ingresso analogico mi permette di non avere fluttuazioni durante la lettura. La lettura della tensione di batteria è stabile sia per via del condensatore, sia per via del fatto che nel codice faccio la media su 500 letture consecutive.

Il valore restituito dal modulo ADC (anBa) è un numero da 0 (0V) a 1023 (3.3V) per cui ottengo il valore di tensione espresso in Volt (voltage) con:

Questa, ricordo, è la tensione in arrivo sul pin analogico scalata dal partitore di tensione. La tensione originaria, ovvero quella applicata all’ingresso del partitore, ovvero la tensione di batteria è pari al valore in uscita dal partitore diviso il rapporto (VDK = R2/(R1+R2)) del partitore:

Trasmetto questo valore in maniera da leggere sul display del cellulare la tensione della batteria. Per avere un riscontro di batteria carica/scarica invio anche un valore percentuale tenendo conto che, nel mio caso, voglio inviare il valore di 100% se la tensione misurata è pari a 6V (o più) e 0% se la tensione misurata è pari a 5V. Per ottenere il valore percentuale sottraggo quindi dalla tensione misurata il mio valore minimo (5V) e divido il risultato per la differenza tra il mio valore massimo e il mio valore minimo (1). Nel mio caso le operazioni sono semplici e molte di queste inutili, ma ho lasciato le formule e le spiegazioni nel codice perchè, potendo alimentare la scheda fino a 21V, abbiamo tante possibilità (scegliete sempre dei valori di R1 e R2 in modo che all’ingresso analogico non arrivino mai, in nessun caso, più di 3V, tenetevi sempre al di sotto di tale valore).

Trasmissione dati IMU

Anche per l’IMU non invio i valori appena letti ma faccio una media su 10 valori ogni 10mS, questo sia per avere valori più stabili, sia per inviare dati ogni 100mS perchè al di sotto ho avuto l’impressione che la ricezione sul cellulare si “ingolfasse” portando l’app a bloccarsi. I dati dei sensori IMU non vanno letti subito ma dopo aver verificato che essi siano disponibili mediante i metodi:

IMU.accelerationAvailable()
IMU.gyroscopeAvailable())
che restituiscono true se i valori relativi sono stati computati. I valori vengono quindi trasferiti in variabili  mediante le funzioni:
IMU.readAcceleration(ax1, ay1, az1);
IMU.readGyroscope(gx1, gy1, gz1);
dove i valori scritti nelle parentesi sono variabili float definite prima nelle quali i dati dei 3 assi saranno resi disponibili.

App Android

Come dicevo sopra, ho preparato anche una semplice demo per Android, utilizzando MIT App Inventor 2 prima (per il quale trovate il sorgente in formato .aia alla fine dell’articolo) e Kodular dopo (a cui ho messo mano successivamente e con il quale ho invece fatto degli aggiustamenti a livello estetico e generato l’ .apk che potete installare sul cellulare se avete attive le opzioni sviluppatore).

Non ho messo l’applicazione sul Play Store perchè dopo tanto tempo che non realizzavo un’applicazione Android da mettere sullo store, ho scoperto che Google adesso per poter pubblicare un’app ha messo tante di quelle norme da rispettare, impossibili da gestire per uno sviluppatore a tempo perso come me, che mi ha fatto passare anche la voglia di leggerle tutte.

Utilizzando il BLE, l’app sul dispositivo client deve utilizzare un componente che non sia il bluetooth classico ma che sia proprio un componente BLE. Se utilizzate, ad esempio, MIT App Inventor 2 o Kodular, di default è presente unicamente il Bluetooth classico ed è possibile importare il componente BLE scaricandolo prima dalle extensions.

Nel sorgente ho definito delle variabili globali che contengono i GUUID del servizio e delle caratteristiche a cui voglio iscrivermi per leggere i dati e che chiaramente devono essere uguali ai valori che ho definito nel programma caricato sulla board:

Quando si preme il pulsante di scansione Bluetooth, viene richiamato il metodo StartScanning del BLE. In automatico, quando il componente BLE trova un dispositivo Bluetooth, lo aggiunge ad un componente lista presente sull’interfaccia:

Sull’App che trovate già compilata, invece ho fatto in modo che il modulo BLE avvii la scansione cercando solo i dispositivi che pubblicizzano il servizio che mi interessa utilizzando il metodo ScanForService e passandogli il GUUID del servizio, per cui se compilate il sorgente AIA e lo fate partire, se avete altri dispositivi Bluetooth nella stanza, quella lista si riempirà con altri nomi, mentre l’app già compilata avraà sempre e solo un’unica riga perchè cerca solo QUEL dispositivo.

Una volta eseguita la connessione al dispositivo va eseguita la registrazione alle caratteristiche che vogliamo ricevere e per questo ho creato una funzione che esegue la registrazione a tutte le caratteristiche, di cui qui metto un pezzetto:

Dato che le caratteristiche, come dicevo sopra, possono essere di tanti tipi, va scelto il metodo giusto: RegisterForFloats nel mio caso specifico: vanno impostati il GUUID del servizio e quello della caratteristica, che ho memorizzato in variabili globali come detto sopra. Nel caso della registrazione ai float, MIT App Inventor 2 richiede un terzo parametro che specifica se la variabile float è di tipo “corto” (false = float a 16bit) o “lungo” (true = float a 32bit, unico valore utilizzato da Arduino).

Se l’app prova a registrarsi ad una caratteristica il cui GUUID non è trasmesso dal server, va in crash.

Così come si esegue la registrazione è necessario eseguire la de-registrazione nel momento in cui ci si disconnette. Nel momento in cui un valore viene ricevuto, sono disponibili tanti metodi quanti sono i tipi di variabile, utilizzerò quindi il metodo FloatsReceived:

Qui la situazione si fa più complessa. Vediamo innanzitutto che il metodo in questione (ma tutti i metodi BLE che ricevono dati) è in grado di restituire i GUUID del servizio e della caratteristica, nonchè il valore appena letto. Per non sbagliarmi sul valore appena ricevuto faccio due if in sequenza prima per verificare che il servizio sia quello che mi interessa, e successivamente per discernere quale caratteristica ho appena ricevuto.

I valori vengono ricevuti dall’App in formato lista: un array insomma, questo è il motivo per cui ho utilizzato il blocco blu select list item con indice 1: perchè il valore che mi interessa viene messo in posizione 1 nella lista.

Non nascondo che questa è stata una cosa su cui ho sbattuto la testa non poco, perchè prendendo il valore restituito direttamente con get floatValues a video mi si presentavano parentesi quadre con dentro uno zero e un valore float perchè mi veniva stampata la lista ricevuta e il fatto che sia presente il nome di funzione al plurale (values e non value) avrebbe dovuto condurmi subito alla soluzione del problema.

Il valore viene quindi trasferito in una variabile globale e stampato a video da un’altra funzione. In questo video è illustrato cosa fa questa demo:

In realtà, avendo utilizzato nella versione definitiva dell’APP la funzionalità di scansione del preciso servizio BLE, avrei potuto evitare di far comparire la lista con un solo elemento per poi selezionarlo e connettersi: potevo eseguire direttamente la connessione.

Per installare l’APK è necessario avere impostati i privilegi di sviluppatore sul cellulare e magari un’app per sfogliare le cartelle in maniera più agevole in modo da trovare subito il file dopo che lo avete caricato manualmente sul cellulare tramite USB. Durante l’installazione vi saranno fatte un sacco di domande per chiedervi se siete sicuri di voler installare l’app e se eventualmente segnalarla: vi chiedo chiaramente di non segnalarla perchè non contiene nulla di pericoloso.

Links

Downloads

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)