Tutorial: utilizzo moduli radio con PIC

Questo tutorial mira a chiarire il modo in cui i diffusissimi moduli radio super-rigenerativi a 433.92 MHz (tipo Aurel) possono essere utilizzati per gestireuna comunicazione wireless fra microcontrollori.

Alla fine del tutorial vi è un esempio in cui sono utilizzati due PIC16F628A, di bassa potenza ma sufficienti per questo compito, assieme a due generici moduli radio.

Questi moduli radio devono essere del tipo comunemente usati nei dispositivi wireless a corta-media distanza, come gli apricancelli. Vanno ovviamente accoppiati in modoche il ricevitore ed il trasmettitore lavorino alla stessa frequenza e con lo stesso tipo di modulazione (tipicamente ASK o FSK).

I moduli radio utilizzati in questo esempio sono quelli senza alcun tipo di codifica/decodifica onboard, quelli cioe' (solitamente utilizzati perche' a basso prezzo) che delegano il compito di codificare l'informazione da trasmettere ai microcontrollori che li comandano. L'utilizzo dei moduli radio con codifica automatica e' estremamente piu'semplice perche' e' sufficiente, solitamente, dialogarci mediante porta seriale. Per questo motivo non vengono presi in considerazione in questo articolo.

Alcuni dei moduli utilizzati sono prodotti, ad esempio, dalla Aurel o da molte altre case (come quella da cui ho preso i moduli che uso ora: TX, RX)


La comunicazione fra questi tipi di moduli radio non e' elementare e richiede qualche importante considerazione:

Ho gia' trattato due volte l'utilizzo di questi moduli in contesti diversi, legati a specifiche progettuali differenti.
Potete trovare qui queste trattazioni:

In questo articolo esporro' invece un modo piu' generale di utilizzare i moduli radio, un modo valido per un buon numero di diversi progetti.

Innanzitutto, se provassimo ad inviare al trasmettitore un segnale modulato in uno dei protocolli generalmente usati (tipo RS232), ci accorgeremmo che all'uscita del ricevitore il segnalesi presenterebbe terribilmente distorto ed inutilizzabile. Questo perche' la fascia di moduli radio utilizzati dispone di uno stadio di ingresso che richiede un segnale modulato in modo da avere molte transizioni di livello e componente continua minima.

Il motivo di questa caratteristica e' dovuto all'architettura interna del trasmettitore e del ricevitore, ed e' da attribuire principalmente alla necessita' di mantenereil prezzo piu' basso possibile. Moduli di fascia piu' alta non hanno questo problema.

Ad ogni modo, per assecondare questo problema, le scelte sono semplicemente tre: utilizzare il protocollo Manchester, una modulazione ASK-OOK o una modulazione FSK. Tutte queste alternative rispondono ai due requisiti appena esposti, ed ognuno ha i suoi pro e i suoi contro.

- Protocollo Manchester: il protocollo Manchester e' particolarmente indicato per questo tipo di trasmissione poiche' con questo tipo di codifica l'informazione trasmessa risiede nelle transizioni di livello del segnale piuttosto che nella temporizzazione dei suoi livelli logici. Ad esempio, se a meta' bit vi e' una transizione da alto a basso, il bit e' un "1" logico, se la transizione e' in senso opposto allora il bit e' uno "0" logico.

Di fatto questo e' il protocollo piu' utilizzato nei dispositivi che utilizzano commercialmente moduli radio di questo tipo.

Esempio tratto da Wikipedia: dato "11011000100" codificato in Manchester
Come si puo' osservare, il valore del bit e' dato da una transizione a meta' dello stesso.

Esistono numerose librerie che rendono facile implementare una modulazione Manchester, ed in ogni caso sarebbe semplice anche implementarla da zero in qualsiasi linguaggio. Purtroppo, nonostante il largo utilizzo, questa codifica presenta qualche problema nel nostro caso.

In primo luogo, l'aggancio del segnale ricevuto da parte del ricevitore e' difficoltoso; ma soprattutto il problema e' dato dal fatto del cosiddetto "tempo di attivazione".

Il tipo di ricevitori che stiamo usando (superrigenerativi), infatti, richiede un certo tempo per iniziare a ricevere correttamente il segnale. Questo significa che trasmettendo un pacchetto di dati, i primi bit potrebbero essere persi fino a quando il ricevitore non avra' agganciato completamente il segnale. Per evitare cio' si trasmette solitamente un "preambolo" formato da un'onda quadra pura, che ha lo scopo di "svegliare" il ricevitore, e solo successivamente si inviano i dati.

In definitiva, questo protocollo va utilizzato solamente su moduli radio con un tempo di aggancio molto breve. Solitamente i moduli radio di bassa fascia non rispondono a tali requisiti, ragion per cui e' necessario esplorare anche le altre possibilita' elencate in precedenza.

- Modulazione ASK OOK: la modulazione ASK ("Amplitude-shift keying") consiste nel variare l'ampiezza dell'onda portante a seconda del livello logico del bit da trasmettere. Quando i possibili livelli sono solo 2 ("1" e "0"), allora questa modulazione prende il nome di OOK ("On - off keying"). In questo caso, per trasmettere un 1 logico viene inviata l'onda portante alla sua ampiezza standard, per inviare invece uno "0" logico l'onda portante non viene inviata.

Per sfruttare questo tipo di modulazione nel nostro caso, il PIC del trasmettitore deve inviare al modulo radio trasmettitore un'onda quadra (ad una certa frequenza per un certo tempo) per trasmettere un "1", e non deve inviare niente (per lo stesso tempo) per inviare uno "0". La figura sottostante rappresenta il segnale NRZ che si vuole trasmettere, e sotto l'onda come il PIC dovrebbe inviarla al modulo trasmettitore.

(clicca sull'immagine per vederla più grande)

Qual'e' il punto di forza di questa tecnica? Essenzialmente, il fatto che il segnale cosi' ottenuto dispone di numerosissime transizioni di livello e di una componente continua minima, cosi' da giungere in uscita dal modulo ricevitore in modo quanto meno distorto possibile. Inoltre, la decodifica di un segnale del genere e' generalmente un'operazione semplice anche per MCU a bassa potenza.

La questione del "tempo di aggancio" permane comunque anche in questo caso, e sarebbe opportuno anche in questo caso prevedere un "preambolo" di attivazione prima dell'invio dei dati.

In seguito vedremo un esempio pratico di trasmissione utilizzando questo tipo di modulazione.

- Modulazione FSK: la modulazione FSK ("Frequency-shift keying") consiste nel variare la frequenza dell'onda portante a seconda del livello logico del bit da trasmettere. Nel nostro caso, questo è attuabile nello stesso modo della precedente modulazione: inviando al modulo trasmettitore, ad esempio, un'onda quadra a 3 kHz per indicare un "1", ed una a 1.5 kHz per indicare uno "0". Rispetto alla modulazione OOK, la presenza di segnale decodificabile anche per lo "0" logico comporta una maggiore immunita' dai disturbi ma una complicazione del sistema di decodifica che dovra' distinguere non solo la presenza di segnale, ma anche la frequenza dell'onda modulante in uscita dal modulo ricevitore.

Utilizzando questa modulazione si hanno comunque tutti i vantaggi della modulazione OOK (alto numero di transizioni e bassa componente continua). In definitiva e' consigliato l'utilizzo di questa modulazione quando la MCU delegata alla ricezione ha a disposizione una potenza necessaria alla decodifica (eventualmente sarebbe opportuna una MCU delegata alla decodifica) o quando l'ambiente e' fortemente disturbato, ad esempio dalla presenza di molti altri dispositivi che lavorino sulla stessa frequenza.

Per quanto riguarda le caratteristiche del segnale inviato, la frequenza utilizzata per la modulazione OOK o FSK deve rientrare nel range specificato dal datasheet dei moduli radio (solitamente non supera i 5 kHz) e il tempo di durata di un bit deve essere sufficientemente alto da poter essere individuato e interpretato con sicurezza, ma un tempo troppo alto allungherebbe eccessivamente i tempi totali di trasmissione. La frequenza scelta deve essere individuata in una via di mezzo a seconda delle specifiche progettuali.


La scelta di quale dei tre protocolli appena esposti utilizzare dipende unicamente da cio' che si vuole realizzare. Riassumendo:

  • In caso il canale utilizzato sia particolarmente libero da disturbi e i moduli radio utilizzati siano sufficientemente potenti da disporre di un tempo di aggancio molto ridotto, e' possibile tentare di implementare una comunicazione mediante modulazione Manchester.
  • In caso di tempi di aggancio significativi (la maggior parte dei casi) e' consigliabile una modulazione OOK o FSK, a seconda della MCU di cui si dispone e dei moduli radio.
  • Se i moduli radio dispongono di un ampio range di frequenze accettabili in ingresso (solitamente non oltre i 4 - 5 kHz) allora si possono individuare due frequenze abbastanza diverse per implementare una modulazione FSK con sufficiente sensibilità, ma solo se il ricevitore e' in grado di decodificare una modulazione FSK senza particolari problemi. Questo e' consigliato anche in caso di un canale particolarmente disturbato.
  • Se invece si desidera una modulazione meno impegnativa da decodificare per la MCU ricevente, la modulazione OOK e' consigliata.

Qualunque sia la modulazione scelta, e' opportuno introdurre sempre il cosiddetto "preambolo" di attivazione del ricevitore. Ad esempio, prima di inviare un segnale modulato OOK si potrebbe inviare per qualche decina di millisecondi un'onda quadra (della stessa frequenza usata per la modulazione), poi una pausa che indichi la fine del "preambolo" e solo dopo il segnale modulato.

Inoltre, in caso il canale sia disturbato e/o sia richiesta una particolare affidabilita', e' opportuno introdurre un efficace meccanismo di controllo degli errori (ed, eventualmente, di correzione).


Una questione cruciale di cui bisogna interessarsi nella progettazione di un sistema di comunicazione, soprattutto via radio ma non esclusivamente, è il controllo degli errori.Parlando specificatamente al riguardo della trasmissione via radio, la natura stessa del canale di trasmissione lo rende inaffidabile poiché:

  • Esso è soggetto ad attenuazione ed a interferenze provenienti da dispositivi che trasmettono sulla stessa frequenza utilizzata od a frequenze prossime;
  • Vi è un importante fattore di casualità nel percorso delle onde radio, il quale non è prevedibile né controllabile al di fuori della polarizzazione delle antenne;
  • I dispositivi trasmittenti e riceventi hanno, a causa delle loro caratteristiche costruttive, un funzionamento che spesso causa una seppur lieve variazione del segnale; ciò è particolarmente vero nel nostro caso poiché i moduli utilizzati per la radiotrasmissione introducono variazione, ad esempio, nella larghezza degli impulsi trasmessi. Inoltre, il modulo ricevitore necessità di un tempo di attivazione in cui ignora i segnali ricevuti per utilizzarli come segnali di sincronizzazione.

Considerati tutti questi fattori, una volta progettato un buon protocollo di comunicazione è spesso necessario implementare un sistema d’individuazione e, se possibile, di correzione degli errori per prevenire l’interpretazione errata dei dati trasmessi.

I protocolli di individuazione degli errori sono molteplici, distinguendosi soprattutto per efficacia, sicurezza e per la conseguente ridondanza e “pesantezza”.
Col termine “ridondanza” del protocollo d’individuazione degli errori s’intende l’aggiunta di dati a tal fine, non contenenti dunque informazione.
Un codice altamente ridondante invierà più dati di uno che non lo è, a parità di informazione trasmessa.
La scelta di un metodo d’individuazione degli errori è quindi una mediazione fra la necessità di sicurezza e quella di velocità di trasmissione, tenendo anche conto della necessità o meno di poter correggere gli errori individuati.

Una volta individuati gli errori nei dati ricevuti, un generico sistema può:

  • Ignorare i dati ricevuti se non in grado di correggerli;
  • Correggere i dati ricevuti se il codice di individuazione d’errore è di tipo FEC (Forward Error Correction), ossia capace di intervenire autonomamente per la correzione. Ciò implica spesso un’alta ridondanza del meccanismo di trasmissione.
  • Se non si utilizza una codifica di canale di tipo FEC o se l’entità dell’errore è troppo alta per permetterne la rigenerazione, il dispositivo in ricezione può chiedere al trasmettitore la ritrasmissione del dato errato. Questo implica la presenza di una codifica di canale di tipo ARQ (Automatic Repeat-reQuest), con conseguente incremento di complessità e costo del sistema.

I più diffusi codici di controllo errori sono i seguenti:

  • Codice a parità
    Consiste nel suddividere l’informazione da trasmettere in gruppi di bit (solitamente sette, otto o nove bit) e aggiungere ad ogni gruppo un bit che porti il numero di ”1” complessivi nel gruppo ad essere pari (parità pari) o dispari (parità dispari).
    Il ricevitore, noto il codice utilizzato, capisce se vi sono errori verificando questa parità.
    Questo codice, largamente utilizzato ad esempio nella comunicazione seriale RS232, nei bus SCSI e PCI e in alcune memorie RAM, presenta alcuni problemi: innanzitutto, non è in grado di correggere eventuali errori rilevati, rendendo necessaria l’installazione di un sistema di ritrasmissione se è necessaria una buona affidabilità. Inoltre, se il numero di bit errati è pari, cioè vi è più di un errore, il protocollo considera giusto il dato, anche se non lo è.
    In definitiva questo codice, dotato tra l’altro di una certa ridondanza, viene utilizzato laddove non sia richiesta una assoluta affidabilità, per esempio in semplici sistemi di trasmissione su canali non pesantemente disturbati.
    Utilizzo nei sistemi di radiotrasmissione: limitato a causa della scarsa efficacia.
  • Codice a parità incrociata
    Si tratta di un’evoluzione del codice a parità, più ridondante di quest’ultimo.
    Il suo funzionamento si basa sul calcolo della parità orizzontale (parità dei singoli gruppi di 7 o 8 bit) e della parità verticale calcolata sui bit della stessa posizione di vari gruppi di bit.
    In ricezione sono ricalcolate entrambe le parità ed eventuali errori sono individuati. Grazie al principio di funzionamento di questo codice, i bit errati possono essere trovati con precisione e corretti.
    Per contro, si tratta di un codice maggiormente ridondante di quello a parità e del codice CRC che vedremo in seguito.
    Utilizzo nei sistemi di radiotrasmissione: molto limitato a causa della eccessiva ridondanza.
  • Codice CRC
    Il codice CRC, acronimo di Cyclic Redundancy Check o Controllo a ridondanza ciclica, opera aggiungendo in coda ai dati da inviare un checksum, ossia una stringa di bit ottenuti mediante elaborazione matematica tra il dato da inviare ed un polinomio di grado variabile secondo il numero di bit da inviare e il grado del polinomio generatore.
    Per polinomio generatore s’intende il polinomio che caratterizza un dato codice CRC. Ad esempio, il codice CRC32 è caratterizzato da un polinomio di trentaduesimo grado.
    Il codice CRC è molto meno ridondante dei codici a parità semplice o incrociata quando è applicato ad un significativo numero di bit. Ad esempio, il CRC32 aggiunge ai dati un numero a 32 bit, variabile fra zero e . Se il numero di bit da inviare fosse, ad esempio, otto (come nella comunicazione RS232), questo codice sarebbe estremamente sconveniente e ridondante. Se il numero di bit da inviare fosse molto maggiore, diventerebbe conveniente utilizzare il codice CRC.
    A causa della sua ottima efficacia, questo codice è molto usato laddove sia richiesta un’individuazione degli errori quasi infallibile. Esiste tuttavia una possibilità che, nonostante la presenza di errori nell’informazione, il codice validi comunque la trasmissione. Si dimostra statisticamente che ciò avviene in casi estremamente rari.
    Fra le principali applicazioni del codice CRC troviamo la compressione dei file e la trasmissione su rete ethernet.
    L’unico punto debole di questo codice è la mancanza della possibilità di correggere gli errori individuati e la complessità del suo calcolo.
    Utilizzo nei sistemi di radiotrasmissione: molto limitato a causa della eccessiva potenza di elaborazione richiesta ad entrambe le MCU.
  • Codice a maggioranza
    Un altro semplice codice di controllo è il codice a maggioranza. Esso prevede l’invio multiplo di ciascun bit o dei byte in cui sono raggruppati.
    SI tratta di un codice estremamente ridondante, il numero di bit inviati subisce un incremento pari, come minimo, al 100%, permettendo però di individuare e correggere gli errori.
    Questo codice è utilizzato in quei casi in cui sia necessario disporre di un meccanismo di correzione degli errori potente ed efficace, anche a scapito della velocità di trasmissione che viene drasticamente ridotta.
    Utilizzo nei sistemi di radiotrasmissione: DIFFUSO per la buona efficacia e la possibilita' di correzione degli errori.

In definitiva, per il nostro caso il codice il meccanismo migliore e' quello del codice a maggioranza, applicato, ad esempio, inviando due o tre volte gli stessi dati. Il ricevitore controlla se i dati sono ricevuti e, in caso di discrepanza tra i vari dati, li scarta o tenta di correggerli (se i dati vengono inviati almeno tre volte). Nei miei precedenti progetti il codice di individuazione degli errori che ho utilizzato si basava sull'invio, in coda al byte dei dati, di un altro byte (la negazione di quello precedente) grazie al quale il ricevitore riusciva a distinguere una buona ricezione da una errata.

Bisogna comunque tenere conto che un codice del genere e' estremamente ridondante e aumenta significativamente il tempo richiesto alla trasmissione dei dati.

Vediamo ora un esempio pratico di trasmissione wireless secondo quanto detto finora.


Vediamo ora un esempio completo (e funzionante) di trasmissione wireless secondo quanto detto finora.
Colleghiamo attraverso due comuni moduli radio due PIC16F628A. Il trasmettitore agisce quando viene premuto un pulsante, inviando il byte "0x55" (01010101 in binario).
Il ricevitore decodifica il segnale e lo mostra su un display LCD.

Lo schema delle connessioni da effettuare e' davvero banale poiche' bisogna connettere solamente un pulsante a un PIC e un LCD ad un altro e i moduli radio TX/RX ai PIC rispettivi. Il circuito va alimentato a 5 V.

Nello schema sottostante i moduli radio sono rappresentati genericamente dalle due antenne, poiche' in genere questi moduli sono molto diversi fra loro e non e' possibile prevedere le modalita' di collegamento di ognuno. Per ulteriori informazioni sulla connessione dei moduli radio ai PIC, consultare i datasheet.


(clicca sull'immagine per vederla piu' grande)

Il firmware per i microcontrollori è stato scritto in C in ambiente mikroC. Per la comprensione di questo esempio si richiede una discreta conoscenza dei microcontrollori in generale, delle periferiche interne (GPIO e Timer soprattutto) e del linguaggio C.

Scegliamo di utilizzare la codifica OOK perche' molto diffusa in questo genere di applicazioni. Inoltre, per assecondare il problema del tempo di aggancio del ricevitore inviamo, prima dei dati, un'onda quadra per un periodo pari a 5 volte la durata di un bit e non inviamo niente (bit a 0) per un ulteriore tempo di durata di un bit, per permettere al ricevitore di sincronizzarsi.
La frequenza che utilizziamo come modulante sara' a 3 kHz (periodo 333 uS) perche' e' praticamente accettata da tutti i moduli di questo genere. La durata di un bit e' in questo caso di 10 millisecondi.
L'invio di un byte di informazione sara' quindi in uscita dal PIC trasmettitore (e, idealmente, in entrata al PIC ricevitore): 50 millisecondi di onda a 3 kHz, 10 ms di assenza segnale, poi il byte vero e proprio dall'LSB all'MSB. Ossia, essendo il byte 01010101, 10 ms di onda a 3 kHz, 10 ms di assenza segnale, e così via per quattro volte.

La figura seguente chiarisce questo concetto (dato lo zoom, l'onda quadra a 3 kHz viene rappresentata come area uniforme di colore giallo):

Come generare questa forma d'onda (da inviare al modulo radio trasmettitore che la inviera' a sua volta via radio) col microcontrollore trasmettitore?
Io per avere una maggiore precisione ho scelto di utilizzare il Timer0 interno e degli interrupt. Sono necessari alcuni brevi calcoli, ma la precisione del risultato ottenuto e' senza dubbio maggiore di quella ottenibile con molti altri metodi.

Abbiamo detto che la frequenza e' 3 kHz, quindi il periodo T = 1/f = 333 uS. Cio' significa che per generare un'onda quadra dobbiamo alterare il pin ogni 333 uS / 2 = 166 uS.

Utilizziamo il Timer0 interno con clock dal processore: F(timer0) = Fclock / 4 = 1 MHz visto che la frequenza di clock e' 4 MHz (oscillatore interno, o esterno per una maggiore precisione).
Tclock = 1 / Fclock = 1 uS. Quindi il Timer0 si incrementa ogni 1 uS. Siccome e' un timer ad 8 bit, dara' interrupt per overflow dopo 1 uS * (2 ^ 8) = 256 uS.
Per avere invece un interrupt ogni 166 uS, dopo un interrupt dobbiamo fare ripartire il Timer0 non da 0 ma da 256-166 = 90.

La routine di invio non dovra' fare altro che abilitare l'interrupt dal timer. Questo interrupt alterna lo stato del pin se e solo se il bit da inviare vale "1", carica nel Timer0 il valore 90 e ritorna al ciclo principale. Quando questo viene fatto un numero tale di volte da aver raggiunto i 10 ms (Tbit), si passa ad un nuovo bit. Nel nostro caso, perche' siano passati 10 ms occorre che il numero di interrupt gestiti sia di 10000 uS / 166 uS = 60.

Trovi qua di seguito il codice sorgente (scritto in C o compilato in hex) che mette in pratica questo ragionamento:



Codice sorgente (.c): Codice sorgente Trasmettitore

Codice gia' compilato (.hex): File compilato Trasmettitore




Anche il funzionamento del ricevitore e' semplice: esso controlla costantemente, in polling, se vi sono dati in arrivo dal modulo ricevitore. in caso ve ne siano, controlla l' "header", ossia i 5 bit alti seguiti dal bit basso. Se la temporizzazione del segnale in ingresso rientra nei limiti di tolleranza massimi (da tarare sperimentalmente) allora procede alla lettura dei bit seguenti. Altrimenti classifica il segnale come disturbo e lo scarta.

Grazie alle specifiche imposte dall' "header" di sincronizzazione e' quasi impossibile validare come dato leggibile un disturbo. Inoltre una buona taratura manuale delle tolleranze (visibili in seguito nel codice sorgente) permette una buona sensibilita' anche in condizioni difficili.

Quando il ricevitore capta un dato in arrivo, lo legge e, se considerato valido, lo mostra sul display alla prima linea. Sulla seconda linea, invece, mostra il numero di dati validi ricevuti finora.

Il funzionamento della routine di ricezione e' semplicemente quello di leggere piu' volte ad intervalli regolari il pin di ingresso, e considerare "attivo" il segnale in ingresso se il numero di letture a livello logico alto e' superiore ad una certa soglia.

Per entrambi i microcontrollori, i cosiddetti "fuses" vanno impostati per attivare il clock interno (a meno che si inserisca un quarzo esterno e si modifichi di conseguenza il codice) e disattivare il pin di RESET (in alternativa e' possibile inserirvi un pull-up con una resistenza del valore non critico di una decina di kOhm, come si vede nello schema).

Trovi qua di seguito il codice sorgente (scritto in C o compilato in hex) per il microcontrollore ricevente:



Codice sorgente (.c): Codice sorgente Ricevitore

Codice gia' compilato (.hex): File compilato Ricevitore






Con questo ho terminato l'esempio pratico ed il tutorial. In caso di dubbi o problemi potete ovviamente contattarmi direttamente.

Buona sperimentazione a tutti :)

Trovate ulteriori approfondimenti sui seguenti siti:

Tutorial codifica manchester
Codifica manchester su wikipedia
Codifica OOK su wikipedia
Codifica FSK su wikipedia
Mio precedente articolo con moduli radio
Altro mio precedente articolo con moduli radio