La tecnologia LoRa – La scheda di sviluppo TTGO LoRa32 – Parte 2 – Comunicazione punto-punto

Nell’articolo precedente abbiamo visto cosa sono il LoRa e il LoRaWAN e abbiamo analizzato a fondo la scheda di sviluppo TTGO LoRa32 2.1_1.6 : abbiamo capito perfettamente cosa c’è a bordo e come utilizzarla correttamente soprattutto per quanto riguarda le questioni di alimentazione esterna, USB e batteria. In questo articolo realizziamo qualcosa di pratico facendo comunicare tra loro due di questi moduli via LoRa: implementeremo, cioè, una semplice comunicazione punto-punto utilizzando Arduino IDE.

Sponsor

Il dispositivo di cui si parla nell’articolo, insieme ad altri componenti, è stato gentilmente offerto da DigitSpace – Negozio online di componenti elettronici e kit ad ottimi prezzi situato in Cina. Pagamento con paypal e spedizioni con corriere espresso.

Digitspace

Librerie da installare

I programmi, uno per ciascun modulo, come anticipato, li scriveremo e caricheremo da Arduino IDE, ed è necessario quindi installare alcune librerie prima di continuare. Per prima cosa bisogna aggiungere il supporto per ESP32 ad Arduino IDE, ovvero l’Arduino Core for ESP32.  Se avete letto articoli precedenti relativi all’ESP8266 sapete già la procedura.

Da Arduino IDE è necessario andare in File > Impostazioni e al campo URL aggiuntive per il Gestore schede bisogna includere questa riga:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Se avete già altre URL inserite in questo campo (è probabile che abbiate già quella per l’ESP8266 ad esempio), possono essere separate utilizzando una virgola oppure premendo il tastino alla fine del campo: in questo caso compare una finestra e li possiamo scrivere gli URL ognuno su una riga diversa:

Diamo OK per uscire da qui e andiamo nel menù Strumenti > Scheda [qui ci sarà scritta l’ultima scheda usata] > Gestore schede. Compare la finestra con l’elenco delle schede installate. Scriviamo ESP32 nella casella di testo, al di sotto deve comparire esp32 by Espressif Systems, nel momento in cui scrivo l’ultimo pacchetto disponibile è l’1.0.4.

Clicchiamo sul tasto Installa e aspettiamo che termini. Ora dobbiamo installare due librerie: una che ci servirà per gestire il modulo LoRa e l’altra per il display OLED. Andiamo nel menù Strumenti > Gestione Librerie. Bisognerà cercare prima LoRa e poi ESP32 Oled. Dato che ci sono molte librerie simili vi indico qui quali installare precisamente (fate caso al by che sia lo stesso)

Ho messo anche il numero di versione per dire che le prove le ho fatte con queste versioni in particolare (le ultime nel momento in cui scrivo), ma se trovate versioni più aggiornate, provate con quelle.

Quando andremo a programmare la scheda, oltre alla porta seriale, dovremo scegliere tra le schede TTGO LoRa32-OLED V1, anche se si tratta di una versione diversa non cambia nulla. Lasciate tutte le altre impostazioni di default.

Disposizioni delle periferiche sui bus

Anche se l’ho già detto nell’articolo precedente, indico nuovamente qui a quali pin sono collegate le periferiche disponibili sulla scheda TTGO Lora32 2.1_1.6:

UtilizzatorePin UtilizzatoreGPIO ESP32
Chip LoRaSCK5
MISO19
MOSI27
SS18
RESET14
DI026
Display OLEDSDA21
SCL22
MicroSDMOSI15
MISO2
SCK14
CS13
DAT212
DAT14
Led Verdeanodo25
Tensione batteriauscita da partitore 1:235

Il modulo OLED sfrutta l’I2C sui gpio di default 21 (SDA) e 22 (SCL). Dato che ogni periferica I2C ha il suo indirizzo, dovrebbe essere possibile, senza problemi, collegare altri dispositivi (purchè funzionanti a 3.3V) a questi due pin che sono disponibili sull’header esterno.

Modulo LoRa e scheda di memoria microSD sfruttano la comunicazione SPI ma sono collegati su gpio differenti. In particolare l’ESP32 ha 4 bus SPI: i bus 0 e 1 (o FSPI) sono utilizzati dalla memoria flash interna/esterna e, da quanto leggo sulla documentazione, non è possibile utilizzarli nelle proprie applicazioni; gli altri due bus, SPI2 e SPI3, sono invece a disposizione dell’utente. Il bus SPI2 viene anche chiamato HSPI e il bus SPI3 è il VSPI. Sebbene tali bus abbiano dei pin di default (HSPI gpio 12,13,14,15 e VSPI gpio 5,18,19,23) è possibile rimappare tali periferiche su qualsiasi altro pin (vedi core ESP32, esp32-hal-spi.h).

La libreria per il modulo LoRa di base tiene conto dell’utilizzo del modulo SPI di default, che su ESP32 dovrebbe combaciare con il modulo HSPI (perchè numericamente è il primo disponibile all’utente e perchè nella libreria SPI dell’ESP32 spi.h così c’è scritto: SPIClass(uint8_t spi_bus=HSPI)), e gli esempi che si trovano in giro normalmente utilizzano proprio il modulo SPI di default.

Ho però incontrato dei problemi nell’utilizzo del LoRa e della scheda microSD contemporaneamente: a volte il programma si bloccava sulla trasmissione del LoRa o si resettava in concomitanza di qualche operazione sul filesystem o ancora qualcosa non funzionava correttamente se la scheda SD non era inserita. Di default, dicevamo, il LoRa negli esempi viene utilizzato con l’HSPI e quindi la scheda di memoria sarà istanziata sul modulo VSPI: ho difatti trovato anche un esempio, in qualche repo della LilyGO che non ricordo, che associava la SD al modulo VSPI, ma alla fine il programma in questione non utilizzava realmente la scheda ma si limitava solo a rilevarla indicandone la grandezza, e basta.

Se in realtà andiamo a vedere i pin ai quali sono collegati tali dispositivi pare che i due moduli nei vari esempi siano stati proprio invertiti: cioè negli esempi viene utilizzato il LoRa con il modulo di default (HSPI) ma rimappato sui pin che normalmente appartengono al VSPI e, viceversa, la scheda SD veniva istanziata con il VSPI rimappato sui pin che normalmente usa l’HSPI. Ripeto: è vero che è possibile rimappare i pin delle due periferiche a piacimento… ma in casi come questo non capisco perchè farlo dato che i pin, insomma, sono proprio quelli. E mi viene anche il dubbio che possa esserci qualche problema proprio perchè a volte il programma presentava dei malfunzionamenti che, dopo queste mie considerazioni e opportune modifiche, sono scomparsi.

Per fare le cose in maniera pulita, ho deciso quindi di specificare precisamente, senza lasciare niente di default, ogni dispositivo quali moduli deve utilizzare. Per tale motivo non uso la classe SPI di default ma istanzio due classi globali separate: una per il modulo LoRa e uno per la SD card:

SPIClass sdSPI(HSPI);
SPIClass loraSPI(VSPI);

Le classi le istanzio sui moduli che ritenevo più logici da usare dal momento che, come appena detto, usano i loro pin di default anche se arrangiati in maniera differente. Inizializzo quindi il modulo SPI usato dal LoRa specificando i pin su cui deve essere rimappata la periferica e della libreria LoRa uso anche il metodo setSPI per dire quale modulo/classe usare:

loraSPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS);
LoRa.setPins(LORA_SS, LORA_RST, LORA_DI0);
LoRa.setSPI(loraSPI);
if (!LoRa.begin(LORA_BAND)) 
    {

All’inizializzazione dell’SPI vedete che segue quella specifica del modulo LoRa che fa uso di altri due piedini, Reset e DI0, oltre ai 4 classici dell’SPI. DI0 in particolare viene utilizzato per la notifica degli interrupt. Il metodo begin del LoRa accetta 4 valori: 433E6, 866E6, 915E6 che si riferiscono alle 3 frequenze (il numero esprime la frequenza in Hertz ed è scritto nella notazione scientifica: 433E6 = 433*(106). Noi utilizzatori Italiani, come detto nell’articolo precedente, utilizzeremo i moduli a 433MHz o a 866MHz.

La stessa cosa faccio con la SD card inizializzando la classe che ho definito più in alto e che si collega all’HSPI:

sdSPI.begin(SDCARD_SCK, SDCARD_MISO, SDCARD_MOSI, SDCARD_CS);
if (!SD.begin(SDCARD_CS, sdSPI))
{

SD è la libreria per la scheda SD i di cui abbiamo incluso l’header, questa libreria include da sola anche la libreria FS per il filesystem, nonostante questo molti esempi in giro la includono lo stesso, inutilmente. Il metodo begin della SD richiede il pin di Chip Select e il modulo/la classe SPI utilizzata.

La libreria SD, quando prova ad avviare la card di memoria, non fa differenza se questa è guasta o formattata con un filesystem non compatibile o è addirittura assente, questo probabilmente perchè non viene utilizzato lo switch che rileva la presenza della schede di memoria che ci aiuterebbe a capire se la card non è inserita oppure ha semplicemente problemi.

La scheda di memoria da utilizzare deve essere formattata in FAT32 altrimenti non viene rilevata, personalmente ho provato con card fino a 16GB e funzionano tutte correttamente, quindi dovrebbe funzionare anche con card più grandi ma per semplici files di testo credo che non ce ne sia proprio bisogno.

Da schema elettrico lo slot per la microSD è collegato ad altri due pin identificati come DAT1 e DAT2 che servono a sfruttare funzionalità aggiuntive per la memoria (come la protezione da scrittura) ma in realtà non vengono utilizzati, per cui i due GPIO 12 e 4 sono a disposizione sull’header esterno e dovremmo poterli utilizzare senza problemi.

Come dicevo, proprio sulla gestione della SD ho avuto una valanga di problemi dal momento che in realtà non ci sono esempi buoni che ne illustrano un utilizzo completo su questa scheda di sviluppo, per cui il codice che vi presento è probabilmente uno degli esempi più completi attualmente presenti per questa scheda.

Problemi durante l’upload del codice su ESP32

Quando c’è una scheda SD inserita nello slot si hanno problemi durante l’upload del codice via UART dal momento che i GPIO 12 e 15, utilizzati dalla SD, sono anche utilizzati al bootstrap per modificare il comportamento del bootloader. Ero già arrivato alla conclusione che togliendo la SD dallo slot andava tutto bene ma volevo capire il perchè! Quindi: ricordatevi di togliere l’eventuale scheda microSD dallo slot prima di caricare il codice.

La libreria LoRa e gli interrupt

Vedendo negli esempi della libreria che abbiamo installato (File > Esempi > LoRa), sono illustrate tante modalità di utilizzo tra cui sono sicuramente interessanti quella duplex (un solo modulo riceve e trasmette in contemporanea) e quelle delle comunicazioni non-bloccanti.

Il problema è che le funzioni che fanno utilizzo di callback mi creavano problemi con il modulo I2C (usato dal display Oled). In particolare è possibile impostare delle callback in ricezione e in trasmissione ovvero vengono generati degli interrupt quando vengono ricevuti dati (onReceive) e quando la trasmissione è completa (onTxDone), in questo modo è possibile richiamare automaticamente una funzione su questi eventi. Ho provato a sfruttare queste caratteristiche ma o si hanno reset improvvisi dell’ESP32 (nel dump su seriale compaiono degli errori che fanno riferimento al modulo I2C) o il display ha dei comportamenti strani tipo parti che non vengono visualizzate: la comunicazione I2C può presentare problemi dato che le tempistiche sono strette, ad ogni modo mi sono fermato ad un primo livello di approfondimento e momentaneamente ho deciso di non utilizzare le callback per non incorrere in problemi col display, poi più in la cercherò di capire meglio anche questa questione.

In più la trasmissione dei dati può anche essere eseguita in maniera asincrona, mi spiego. Normalmente l’invio dei dati avviene con questa sequenza di istruzioni:

  LoRa.beginPacket();
  LoRa.print("hello"); // dato da inviare
  LoRa.endPacket(true);

Il metodo endPacket serve a terminare la trasmissione: se come parametro viene passato true, l’esecuzione del programma continua alla riga successiva mentre il modulo trasmette (metodo non-bloccante), bisognerà quindi intercettare l’interrupt onTxDone con una funzione di callback per capire quando la trasmissione è terminata e quindi poterne iniziare un’altra. Se invece il parametro è false (o il parametro viene lasciato vuoto), il programma si ferma in quel punto fino a che il dato non viene trasmesso (metodo bloccante). Inutile dire che dovendo intercettare l’interrupt per capire se la trasmissione era terminata, mi sono ritrovato con i problemi di cui dicevo sopra.

Il metodo print della libreria LoRa  deriva dalla classe print di Arduino per cui è possibile passargli qualsiasi tipo di dato.

La libreria ha molti metodi interessanti, che vi invito a leggere nella documentazione ufficiale. In particolare è possibile mettere il modulo in stand-by per risparmiare corrente.

Funzioni per la Scheda di memoria

Le funzioni che ho incluso nell’esempio per lavorare con la card di memoria sono quelle standard della libreria SD dell’ESP32 quindi non mi dilungo nello spiegarle. L’unica cosa a cui bisogna stare attenti è il formato dei dati da passare per la scrittura: a differenza della classe print che usiamo col LoRa, che accetta tutto, qui è possibile passare esclusivamente dati in formato array di char, per cui la cosa più conveniente da fare è trasformare tutti i dati in String che ci permette di includere anche numeri senza complicate operazioni di conversione, e poi usare il metodo c_str() della classe String per trasformarla in array.

Programma di esempio

I programmi sono due: uno che trasmette e uno che riceve, sono necessari quindi 2 moduli LoRa. Ho fatto un file board_defs.h in cui ci sono i pin e in cui dovete cambiare la costante LORA_BAND per adattarla al modulo in vostro possesso. Entrambi i programmi si avviano con uno splash screen che mostra il logo di settorezero e il logo di digitspace, che mi ha fornito i moduli per lavorarci su, dopodichè il programma passa a fare dei check di sistema: viene verificato il funzionamento del modulo LoRa e se questo task non va a buon fine, il programma si mette in loop, bloccandosi. Viene quindi verificata la presenza della scheda di memoria: se la scheda è presente ne viene mostrata la capacità sul display e viene settata una variabile globale che ne indica appunto la presenza in maniera tale che altre funzioni potranno scrivere su un file.

Infine viene mostrata la tensione della batteria in Volts; come detto nell’articolo precedente, è chiaro che se la scheda non ha la batteria collegata e/o sta funzionando con alimentazione esterna o USB, qui verrà mostrata la tensione di carica che si aggira intorno ai 4.2V. In particolare, come dicevo sempre nell’articolo precedente, se la scheda viene alimentata dalla 5V sull’header e l’interruttore si trova in posizione OFF, la scheda funziona ma la batteria, che è collegata sulla linea VBUS, non riceve la tensione di carica (l’interruttore mette in comunicazione la linea VBUS con la linea +5V) e quindi sul display viene mostrato 0 come tensione di carica.

Dopo di questo, il programma si avvia.

Nel programma che trasmette ho voluto disegnare il logo del LoRa, che prende la metà superiore del display, viene quindi mostrata la tensione di batteria, questa volta espressa come media su più misure per rendere il valore più stabile, e viene mostrato il pacchetto di dati inviati, che è un semplice counter che incrementa. In realtà il pacchetto trasmesso è @[counter]# : ho incluso questi due caratteri all’inizio e alla fine come semplicissimo, elementare, controllo del pacchetto ricevuto dal momento che quando l’RSSI (la potenza del segnale) arriva al minimo possibile (ho trovato -120 come valore limite) i pacchetti cominciano ad arrivare danneggiati, per cui se non sono costituiti in questo modo li scarto. Sarebbe comumque meglio includere un controllo di checksum.

Quando il pacchetto viene trasmesso si accende il led verde sulla scheda. Se è presente la scheda di memoria viene creato un file di testo con la lista dei pacchetti inviati. Contemporaneamente è presente un debug sulla seriale impostata a 115200bps.

Il programma che riceve, all’inizio fa le stesse cose, dopodichè quando parte, sul display mostra il livello della batteria, il livello di forza del segnale (RSSI, Received Signal Strength Indicator : 0 è il valore massimo), il numero di bytes ricevuti e il pacchetto ricevuto, anche qui, se è presente la scheda di memoria, vengono memorizzati i pacchetti in un file di testo.

Come vedete, rispetto anche a tutti gli altri esempi che ci sono in giro, ho anche realizzato un font più carino e più leggibile. Se avete trovato questi due articoli sul LoRa utili, vi invito a leggere  questa pagina.

Downloads

Di seguito c’è il file Zip con i due sorgenti di esempio. Vi invito, cortesemente, a non diffondere il codice o sue parti ma piuttosto di mettere un link a questa pagina. EDIT: ho aggiornato il download, c’era un piccolo errore sull’include dei due headers.

Case 3D

Il case 3D che vedete nelle foto l’ho trovato su Thingiverse. Vi avviso però che è davvero molto sottile (0.8mm di spessore) per cui cercate di stamparlo a bassa velocità e con un riempimento del 100%: ci vuole circa una mezz’oretta. La versione che vedete nelle foto è una modifica che ho fatto togliendo la nuvola e aggiungendo il foro per il led verde. Potete scaricare qui la mia versione del case, mentre per la parte posteriore, dal momento che rispetto il copyright, la potete scaricare da thingiverse:

Links

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 :)