Skip to content

noearchimede/RFM69

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

160 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Driver Arduino per i moduli radio RFM69

Autore Noè Archimede Pezzoli
Data Febbraio 2018

Contatto noearchimede@gmail.com


Introduzione

La libreria RFM69 permette di collegare due microcontrollori tramite una coppia di moduli radio della famiglia RFM69 di HopeRF, in particolare del modello RFM69HCW. È stata testata sui microcontrollori ATmega238P e ATmega1284 (con il framework MightyCore).

[TODO determinare se e in che modo la libreria dipende da MightyCore, e in generale creare una lista esplicita di dipendenze]

I moduli radio devono essere collegati al microcontrollore tramite un'interfaccia SPI. Qualora questo non fosse possibile è anche possibile usare un chip di conversione tra I2C e SPI e collegare quest'ultimo tramite I2C al microcontrollore. Questa libreria contiene il codice necessario per controllare la radio tramite il chip SC18IS602B di NXP Semiconductors.

Il codice della libreria è ampiamente commentato in italiano. I commenti normali (// ...), presenti soprattutto nei files di implementazione (.cpp), forniscono dettagli sull'implementazione. I commenti "speciali" (//! ... o /*! ... */) sono concentrati nel file header e costituiscono una documentazione per l'utilizzatore della libreria che non desidera conoscere i dettagli del suo funzionamento. Questa documentazione può essere riunita da Doxygen in una pagina html.


Indice

1. Caratteristiche del modulo radio
2. Protocollo di comunicazione
3. Collisioni
4. Hardware
5. Struttura dei messaggi
6. Impostazioni
7. Esempio di utilizzo

Documentazione



1. Caratteristiche del modulo radio

Caratteristiche principali dei moduli radio RFM69HCW:

  • frequenza: 315, 433, 868 oppure 915 MHz (esistono quattro versioni per adattarsi alle bande utilizzabili senza licenza in diversi paesi)
  • potenza di emissione: da -18dBm a +20dBm (100mW)
  • sensdibilità: fino a -120dBm (con bassa bitrate)
  • bitrate fino a 300'000 Baud
  • modulazioni: FSK, GFSK, MSK, GMSK, OOK

I messaggi possono includere un controllo CRC16 di due bytes che riduce drasticamente la probabilità di errore durante la trasmissione. Possono inoltre essere criptati secondo l'algoritmo Avanced Encryption Standard AES-128 con una chiave di 16 bytes per impedirne la lettura da parte di eventuali terze radio. La possibilità offerta dal modulo di assegnare ad ogni modulo un indirizzo unico in modo da creare una rete con fino a 255 dispositivi nonb è sfruttata, ma un risultato simile è ottenibile creando una rete in cui ogni radio ha una sync word unica che può sostituire temporaneamente con quella di un'altra radio (ottenuta da una tabella pubblica) per inviare un messaggio a quella radio. Questo permette di creare una rete di dimensione arbitraria (è possibile impostare fino a 8 byte di sync word, per un totale di 2^64 indirizzi possibili) ma non di inviare messaggi broadcast, come invece il sistema di addressing incluso nel modulo permetterebbe.


Corrente di alimentazione richiesta (a 3.3V), per modalità:
  • Sleep: 0.0001 mA
  • Standby: 1.25 mA
  • Rx: 16 mA
  • Tx: 16 - 130 mA a seconda della potenza di trasmissione


2. Protocollo di comunicazione

Il protocollo di comunicazione alla base di questa classe presuppone che in una stessa banda di frequenza siano presenti esattamente due radio che condividono la stessa sync word. La stessa frequenza può quindi essere utilizzata anche da altri dispositivi; naturalmente, però, se dispositivi trasmittenti sulla stessa frequenza trasmettono dati nell stesso momento nessuno di essi riceverà un mesaggio valido (a meno che la differenza nella potenza trasmessa sia abbastanza grande da permettere al segnale più forte di "coprire" il più debole, in tal caso solo il dispositivo ricevente il più forte otterrà un messaggio).

Alla lettura di ogni messaggio la radio ricevente può trasmettere automaticamente un segnale di ACK se la radio trasmittente lo ha richiesto. In questo modo se l'utente deve essere certo che un messaggio trasmesso sia stato ricevuto e letto (quindi certamente anche utilizzato, visto che la lettura avviene solo su richiesta dell'utente e non automaticamente come la ricezione) non deve né implementare un sistema di ACK né modificare il codice ricevente, e il segnale di ACK sarà il meno dispendioso possibile in termini di tempo del programma.

Gli schemi sottostanti illustrano la trasmissione di un mesasggio.

1 - con ACK:

mod       ?   |    tx    |            rx             |        def
fz          INVIA       ISR                         ISR
A       ------|----------|---------------------------|-------------------->
RF             |||MESS|||                    ^^^^^^^
|              vvvvvvvv                      ||ACK||
B       ------------------|-----------------|-------|--------------------->
fz                       ISR              LEGGI    ISR
mod             rx        |       stby      |  tx   |          def

2 - senza ACK:

mod       ?   |    tx    |              def
fz          INVIA       ISR
A       ------|----------|--------------------------------->
RF             |||MESS|||
|              vvvvvvvvv
B       ------------------|-----------------|--------------->
fz                       ISR              LEGGI
mod             rx        |       stby      |       def
  • A, B: Programma delle stazioni radio, evoluzione nel tempo
  • fz: funzioni chiamate. invia() e leggi() sono chiamate dall'utente, isr() è l'interrupt service routine della classe
  • mod: modalità della radio. tx = trasmissione, rx = ricezione, stby = standby, def: la modalità che l'utente ha scelto come default per quella radio
  • RF: presenza di segnali radio e loro direzione


3. Collisioni

Le funzioni di questa classe non impediscono che le due radio trasmettano dei messaggi contemporaneamente. Questo problema deve essere gestito come possibile dal codice dell'utente. Tuttavia le funzioni della classe in caso di conflitto impediscono la perdita di entrambi i messaggi (cosa che potrebbe portare a un blocco senza uscita se entrambi i programmi cercassero di reinviare subito il proprio messaggio). Dà quindi la priorità ai messaggi già arrivati a scapito di quelli in uscita, che potrebbero perdersi.

Lo schema sottostante illusta la trasmissione di un mesaggio evidenziando i momenti in cui non si può o non si dovrebbe trasmetterne altri.

1 - con ACK:

stato tx                    |*********|############ 1 ############|
A           ----------------|---------|---------------------------|---------->
|                         INVIA      ISR                         ISR
|                                    ISR              LEGGI    ISR
B           ---------------------------|-----------------|-------|----------->
stato tx            |####### 2 ########|                 |*******|

2 - Senza ACK:

stato tx                    |*********|######## 3 #######|
A           ----------------|---------|----------------------------->
|                         INVIA      ISR
|                                    ISR              LEGGI
B           ---------------------------|-----------------|----------->
stato tx            |######## 2 #######|
  • [ ]: nessuna restrizione, è il momento giusto per trasmettere un messaggio
  • [***]: impossibile trasmettere, invia() aspetta che sia di nuovo possibile (ma al massimo 50 ms)
  • [###]: la funzione invia() non dovrebbe mai essere chiamata qui.
    • [1]: In teoria non bisognerebbe trasmettere (l'altra radio non è in modalità rx), ma in realtà se l'utente chiama invia() mentre la classe aspetta un ack per il messaggio precedente significa che l'utente ha rinunciato a controllare        quell'ack (se così non fosse basterebbe implementare una flag di "messaggio in uscita" o controllare la funzione ackRicevuto() prima di inviare). In tal caso invia() si comporta come se il messaggio precedente non avesse contenuto una richiesta di ack. Probabilmente questo messaggio andrà perso, ma il compito della funzione invia() non è aspettare l'ack precedente (quello è compito dell'utente, anche se lo aspettasse per un certo tempo invia() non potrebbe segnalare se è arrivato o no). La sequenza corretta sarebbe:

      inviaConAck();
      aspettaAck(); 		// contiene un timeout
      if(ackRicevuto())
      	invia(); 		// prossimo messaggio

      oppure

      inviaConAck();
      delay(x); 			// potrebbero essere altre funzioni
      if(ackRicevuto())
      	invia(); 		// prossimo messaggio  
      rinunciaAck(); 		// smetti di aspettare l'ACK
    • [2]: Momento critico: se si chiama invia() qui ci sarà una collisione con l'invia() della radio A e entrambi i messaggi saranno persi, ma questa classe non ha modo di evitarlo. Spetta all'utente impedire queste collisioni o saperle gestire.

    • [3]: I messaggi inviati qui saranno persi. È un difetto dei messaggi senza ACK.


Per costruire un sistema di trasmissione di dati bidirezionale e continua, dunque, è necessario creare un sistema di gestione di queste possibili collisioni, cercando di evitarle il più possibile. Invece i un programma che utilizza la radio solo di tanto in tanto è sufficiente tenere presente la possibilità di perdere un messaggio (quindi ad es. controllarne la ricezione tramite il sistema di ACK ed eventualmente reinviarlo).

La probabilità di perdere un messaggio è relativamente bassa. Il grafico sottostante raffigura la percentuale di messaggi trasmessi con successo rispetto al numero medio di messaggi inviati in un minuto, in un sistema in cui due radio si inviano reciprocamente messaggi di 12 bytes (4 bytes di dati) con richiesta di ACK ad intervalli di tempo casuali ma in media ad una stessa frequenza. La tabella è stata generata dal programma Test collisioni (composto dai file Esempi/Test_collisioni_master.cpp e Esempi/Test_collisioni_assistente.cpp, uno per ciascuna radio).

 %
100 |  *                                                                                        
    |       *                                                                                   
    |      *  *                                                                                 
    |               *                                                                           
    |           * *   **                                                                         
 75 |   *               *      **   *                                                            
    |                   *                                                                       
    |                       *      *       *                                                    
    |                                 * *                                                       
    |                                         *                                                 
 50 |                                       * *      *                                          
    |                                               **                                          
    |                                              *   *     **                                  
    |                                                           * * **                          
    |                                                      *            *        *              
 25 |                                                                    *  *   **               
    |                                                                                    *      
    |                                                                        *         * ** *   
    |                                  *                                                  *     
    |                                                                     *              **      
  0 +------------------------------------------------------------------------------------------  mess/min
    0          200          400          600          800          1000          1200          1400


4. Hardware

Come già detto ho scritto questa classe in particolare per il modulo RFM69HCW di HopeRF, in commercio sia da solo sia inserito in altri moduli che offrono, ad esempio, un logic level shifting da 5V a 3.3V (ad es. Adafruit vende https://www.adafruit.com/product/3071 per la maggior parte dei paesi, tra cui tutti quelli europei, e https://www.adafruit.com/product/3070 per gli USA e pochi altri).

Il modulo comunica con il microcontrollore tramite SPI, deve poter chiamare un'interrupt su quest'ultimo e può "affidargli" il proprio pin di reset (non veramente sfruttato da questa classe, ma se è già connesso deve essere gestito per evitare reset indesiderati). Deve essere alimentato con una tensione di 3.3V.

RFM69 uC
MISO MISO
MOSI MOSI
SCK SCK
NSS I/O *
DIO0 INT **
RESET & I/O *

La classe va poi istanziata usando il constructor RFM69(<pinSS>, <pinInterrupt>, <pinReset>).

Questa libreria permette anche di connettere la radio al microcontrollore attraverso il ponte I2C - SPI SC18IS602B di NXP Semiconductors. L'uso di questo sistema è sconsigliato se un'interfaccia SPI è disponibile perché è nettamente più lento, ma non limita alcuna funzione fornita da questa libreria. Per usare questo sistema servono i seguenti collegamenti:

RFM69 uC
SDA SDA
SCL SCL
DIO0 INT **
RESET & I/O *

Inoltre serve l'indirizzo del chip ponte, che nel caso di SC18IS602B dipende dalla connessione di diversi pin fisici. Il constructor in questo caso è RFM69(<indirizzo>, <numeroSS>, <pinInterrupt>, <pinReset>);, dove numeroSS è il numero della porta SPI di SC18IS602B a cui la radio è connessa (ce ne sono quattro).

&: Opzionale

*: Qualsiasi pin di input/output (sarà configurato come output dalla classe)

**: Un pin capace di attivare un interupt del microcontrollore. Ad esempio su Atmega328p, il microcontrollore di Arduino UNO, si possono usare i pin 4 e 5, cioé rispettivamente 2 e 3 nell'ambiete di programmazione Arduino.


5. Struttura dei messaggi

Tutti i messaggi inviati con le funzioni di questa classe hanno la seguente struttura:

Preamble Sync word Lunghezza Intestazione Contenuto CRC
PREAMBLE_SIZE SYNC_SIZE 1 1 lunghezza 2
01010101... SYNC_VAL lunghezza intestazione messaggio crc

La prima riga è la lunghezzza della sezione in bytes, la seconda è il suo contenuto.

  • PREAMBLE_SIZE, SYNC_SIZE e SYNC_VAL sono costanti definite nel file "RFM69_impostazioni.h".
  • lunghezza e messaggio sono gli argomenti della funzione invia().
  • intestazione è un byte generato dalle funzioni di invio e letto da quelle di ricezione, inaccessibile all'utente.
  • crc è un Cyclic Redundancy Checksum generato dalla radio.


6. Impostazioni

Il modulo RFM69 offre all'utente ampie possibilità di impostazione. Alcune di queste impostazioni sono richieste dalla classe, come ad esempio il modo di trasmissione dei dati (che deve essere "a pacchetti"). La maggior parte resta però a disposizione dell'utente.


7. Esempio di utilizzo

#include <Arduino.h>
#include "RFM69.h"


// #define MODULO_r o MODULO_t per compilare rispettivamente il programma per la
// radio ricevente o per quella trasmittente.
//------------------------------------------------------------------------------
#define MODULO_r
// #define MODULO_t
//------------------------------------------------------------------------------




// telecomando
#ifdef MODULO_r
// Pin SS, pin Interrupt, (eventualmente pin Reset)
RFM69 radio(2, 3);
// Un LED, 0 per non usarlo
#define LED 4
#endif

// quadricotetro
#ifdef MODULO_t
// Pin SS, pin Interrupt, (eventualmente pin Reset)
RFM69 radio(A2, 3, A3);
// Un LED, 0 per non usarlo
#define LED 7
#endif




void setup() {

    Serial.begin(115200);
    if(LED) pinMode(LED, OUTPUT);

    // Inizializza la radio. Deve essere chiamato una volta all'inizio del programma.
    // Restituisce 0
    int initFallita = radio.inizializza(4);
    if(initFallita) {
        // Stampa l'errore riscontrato (questa funzione pesa quasi 0.5 kB)
        radio.stampaErroreSerial(Serial, initFallita);
        // Inizializzazione fallita, blocca il progrmma
        while(true);
    }
}




#ifdef MODULO_t


void loop(){

    // crea un messaggio
    uint8_t lung = 4;
    uint8_t mess[lung] = {0,0x13, 0x05, 0x98};

    unsigned long t;
    bool ok;

    while(true) {

        // Aggiorna messaggio
        mess[0] = (uint8_t)radio.nrMessaggiInviati();

        Serial.print("Invio...");
        if(LED) digitalWrite(LED, HIGH);

        // Registra tempo di invio
        t = millis();

        // Invia
        radio.inviaConAck(mess, lung);
        // Aspetta fino alla ricezione di un ack o al timeout impostato nella classe
        while(radio.aspettaAck());
        // Controlla se è arrivato un Ack (l'attesa può finire anche senza ack, per timeout)
        if(radio.ricevutoAck()) ok = true;  else ok = false;

        // calcola il tempo trascorso dall'invio
        t = millis() - t;

        if(LED) digitalWrite(LED, LOW);

        if(ok) {
            Serial.print(" mess #");
            Serial.print(radio.nrMessaggiInviati());
            Serial.print(" trasmesso in ");
            Serial.print(t);
            Serial.print(" ms");
        }
        else {
            Serial.print(" messaggio #");
            Serial.print(radio.nrMessaggiInviati());
            Serial.print(" perso");
        }
        Serial.println();

        delay(1000);
    }
}

#endif



#ifdef MODULO_r


void loop(){

    // metti la radio in modalità ricezione
    radio.iniziaRicezione();

    // aspetta un messaggio
    while(!radio.nuovoMessaggio());

    if(LED) digitalWrite(LED, HIGH);

    // ottieni la dimensione del messaggio ricevuto
    uint8_t lung = radio.dimensioneMessaggio();
    // crea un'array in cui copiarlo
    uint8_t mess[lung];
    // leggi il messaggio
    int erroreLettura = radio.leggi(mess, lung);
    // ora `mess` contiene il messaggio e `lung` corrisponde alla lunghezza del
    // messaggio (in questo caso corrispondeve anche prima, ma avrebbe anche
    // potuto essere più grande, ad. es. se mess. fosse stato un buffer generico
    // già allocato alla dimensione del messaggio più lungo possibile)

    if (erroreLettura) {
        Serial.print("Errore lettura");
    }
    else {
        Serial.print("Messaggio (");
        Serial.print(lung);
        Serial.print(" bytes): ");
        for(int i = 0; i < lung; i++) {
            Serial.print(" 0x");
            Serial.print(mess[i], HEX);
        }
        Serial.print("  rssi: ");

        // Ottieni il valore RSSI del segnale che ha portato questo messaggio
        Serial.print(radio.rssi());
    }
    Serial.println();

    delay(50);
    if(LED) digitalWrite(LED, LOW);

}
#endif

About

Driver Arduino per i moduli radio RFM69

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors