Corso programmazione PICMicro in C – Lezione 12 (Parte 1/3) – Il modulo MSSP e la comunicazione I2C – Principi di funzionamento

In una lezione precedente abbiamo visto come si  utilizza la periferica USART, utile per comunicazioni seriali asincrone di tipo full-duplex tra solo due dispositivi. Abbiamo inoltre visto come la periferica USART viene interfacciata con le comunicazioni RS232 tramite opportuni traslatori di livello per adattare il livello logico TTL al livello logico EIA232.

In questa lezione ci occuperemo della periferica MSSP (Master Synchronous Serial Port). Anche in questo caso si tratta di una periferica in grado di effettuare una comunicazione seriale. Tale periferica, però, si occupa unicamente di trasmissioni seriali sincrone (ovvero abbiamo in aggiunta una linea che fornisce il clock) secondo i protocolli I2C ed SPI.

Il protocollo I2C è un protocollo di comunicazione seriale sincrona half-duplex (vi è una sola linea di comunicazione, oltre al clock, sulla quale possono inviare i dati un dispositivo alla volta), l’SPI è invece full-duplex (due dispositivi possono inviare dati contemporaneamente in quanto, oltre al clock, vi sono due linee di comunicazione). Tali protocolli sono utilizzati da svariati tipi di dispositivi e permettono la comunicazione anche tra più dispositivi sullo stesso bus: nei due protocolli menzionati, difatti, ci sono diversi sistemi di indirizzamento che permettono di selezionare solo il dispositivo al quale sono destinate le informazioni: tutti gli altri dispositivi presenti sulla linea, quindi, ignoreranno l’informazione. Questa cosa abbiamo visto che non è possibile con la comunicazione secondo lo standard RS232.

Estratto dallo schema della Freedom II di Mauro Laurenti. Vediamo come due dispositivi I2C (una eeprom serie24 e un Orologio Realtime PCF8563) sono connessi in parallelo sulla stessa linea di comunicazione
Sensore ad ultrasuoni SRF10 della Devantech. Comunica in I2C e fornisce una misura di distanza dagli oggetti.

Sui microcontrollori PIC vi sono alcuni registri che permettono di impostare la periferica MSSP per il funzionamento in modalità I2C o SPI. In questo articolo ci occuperemo del funzionamento in I2C, che è forse il più diffuso e permette l’interfacciamento con numerosi dispositivi di uso comune (eeprom esterne della serie 24, orologi real clock come il DS1307, bussole, sonar e tanto altro ancora).

Prima di continuare, però, dovete necessariamente leggere l’ottimo tutorial di Mauro Laurenti sull’ I2C, in maniera tale che io possa evitare di dire cose già dette (e tra l’altro dette anche molto bene ed in maniera semplice ed esaustiva), per cui proseguirò supponendo che sappiate già com’è strutturata una comunicazione I2C, anche se per forza di cose ogni tanto dovrò comunque ripetere dei concetti.

Sappiamo quindi che in una comunicazione I2C c’è un dispositivo identificato come Master che gestisce la comunicazione (ovvero: fornisce il segnale di clock, avvia e termina le comunicazioni) e uno o più dispositivi identificati come Slave che rispondono ai comandi impartiti dal dispositivo Master. Il modulo MSSP può implementare il protocollo I2C in varie modalità: Master, Multi-Master e Slave. In questo articolo mi occuperò unicamente della modalità Master, che è forse quella più usata.

Non mi occuperò in questa sede del modulo SSP (presente ad esempio nei  pic16F88, pic16F819 ecc), che ha sempre e comunque la funzione di gestire le comunicazioni I2C e SPI. Nei pic con modulo SSP, però, la modalità master non è supportata appieno e molte cose vanno implementate via software. Non per niente nel modulo MSSP la “M” sta appunto per Master e nelle caratteristiche viene appunto dichiarato “Full Master” e vi assicuro che quel full fa molta differenza!

La modalità I2C prevede l’utilizzo di soli due pin: quello indicato come SCL (Serial CLock), che ha il compito di fornire il segnale di clock necessario per la comunicazione sincrona e il pin SDA (Serial DAta) sul quale transitano i dati ed è usata sia da chi trasmette che da chi riceve (half-duplex). Sul 16F877A questi due pin si trovano rispettivamente su RC3 e RC4, fate riferimento al datasheet del picmicro in vostro possesso per identificare quali pin sono usati per la comunicazione I2C.

La modalità I2C prevede l’impostazione e l’utilizzo di 5 registri:

SSPCON – Primo registro di controllo del modulo MSSP

Su alcuni picmicro tale registro potrebbe essere identificato come SSPCON1, in altri le due definizioni sono equivalenti, tutto dipende dal file header del pic utilizzato.

SSPCON2 – Secondo registro di controllo (e stato) del modulo MSSP
SSPSTAT – Registro di stato (e controllo)
SSPBUF – Buffer di ricetrasmissione
SSPADD – Registro di indirizzamento (solo in modalità slave) / Impostazione velocità di trasmissione (meglio noto come Baud Rate Generator, solo in modalità master)

I pic con modulo SSP anzichè MSSP, ad esempio, non hanno il registro SSPCON2 che invece noi qui andremo a sfruttare appieno per realizzare una comunicazione full master interamente gestita in hardware.

In aggiunta c’è un sesto registro non accessibile, SSPSR, che funziona da shift register per il registro SSPBUF: quando un dispositivo riceve, i dati vengono caricati prima in SSPSR e quindi spostati in SSPBUF creando quindi un doppio buffer di ricezione. In trasmissione invece il registro SSPBUF viene caricato direttamente.

Alcuni pic16 (ma non solo), appartenenti alla serie Enanched (ovvero PIC16Fxxxx cioè con quattro cifre nella parte finale della sigla), hanno 2 registri aggiuntiviSSPCON3 e SSPMSK che servono per impostare caratteristiche avanzate, che qui non andremo ad utilizzare e non sono strettamente necessarie per una comunicazione I2C di base. Altri pic, sempre appartenenti alla serie Enanched (prendo ad esempio il PIC16F1827) hanno ben due moduli MSSP (MSSP1 e MSSP2), indipendenti tra loro e configurabili individualmente, che permettono, ad esempio, di implementare su un unico picmicro sia la comunicazione I2C che quella SPI (o anche due comunicazioni I2C nel caso che su una abbiamo raggiunto il limite massimo di periferiche ammissibili e abbiamo necessità di inserirne ancora), cosa che, avendo un unico modulo, normalmente non è possibile a meno di non implementarle via software e non via hardware come stiamo facendo adesso. In questi picmicro tutti i registri associati ai moduli MSSP avranno in aggiunta il numero 1 o il 2 per indicare se appartengono al primo o al secondo modulo. Avremo quindi un SSP1CON e un SSP2CON, un SSP1STAT e un SSP2STAT e così via. Fate riferimento al datasheet del pic in vostro possesso e al file H dell’Hitec-C relativo al vostro picmicro. Inoltre su tali pic i nomi dei bit potrebbero avere un nome leggermente differente, come ad esempio il bit RW che è stato cambiato in R_nW.

Inizializzazione del modulo MSSP in modalità I2C master

Prima di tutto è necessario porre in stato di alta impedenza (ovvero: impostare come ingressi) i due pin destinati alla comunicazione I2C, ovvero RC3 e RC4 nel caso del 16F877A:

TRISC3=1;
TRISC4=1;

oppure

TRISC=0bxxx11xxx; // sostituire le x

Inutile ripetere che su altri picmicro, specie su quelli con doppio modulo, i due pin si possono trovare in posizioni differenti.

Ponendo i due I/O in stato di alta impedenza dovremo quindi provvedere delle resistenze di pull-up  sulle 2 linee di comunicazione per consentire le operazioni di transito dei dati; il valore delle resistenze varia in funzione della velocità di trasmissione  e dell’impedenza della linea, abbiamo già letto nel tutorial di Mauro quali valori scegliere in base alle nostre esigenze.

I principali bit di inizializzazione si trovano nel registro SSPCON, ed in particolare:

SSPEN (bit 5): Synchronous Serial Port Enable. Impostato ad 1 abilita la porta seriale e configura i pin indicati SCL e SDA come linee di comunicazione per il modulo I2C. Questa impostazione prevede che abbiamo già settato il registro tristato per impostare tali pin come ingressi.

SSPM3:SSPM0 (bit 3:0): Synchronous Serial Port Mode. L’impostazione di tali bit permette di selezionare la modalità di funzionamento del modulo I2C, in questo caso, dovendo utilizzare il modulo I2C in modalità Master, tali bit andranno posti al valore 0b1000.

Impostazione dei bit SSPM sul pic16F877A. Sui pic con modulo SSP, l’impostazione ob1000 non è presente e l’unico modo per implementare una comunicazione I2C master è l’impostazione 0b1011, ovvero Master controllato via firmware…

Essendo il master il dispositivo che decide la frequenza del clock, tale impostazione prevede anche che il clock del modulo I2C, il quale determina la velocità di comunicazione sul bus, sia calcolato con la formula:

dove SSPADD è appunto il valore che andremo a dare al registro di indirizzamento (che nel caso della modalità master è detto Baud Rate Generator) in maniera tale da ottenere la frequenza di clock desiderata.

Nel nostro caso quindi l’impostazione da dare al registro SSPCON (o SSPCON1) sarà:

SSPCON=0b00101000; // 0x28

Dobbiamo quindi impostare la velocità di comunicazione. Come abbiamo visto, lavorando in modalità master, il nostro picmicro genererà il clock secondo la formula vista poc’anzi. Dobbiamo quindi impostare il registro SSPADD per poter scegliere la velocità di comunicazione. Tutti i dispositivi I2C possono lavorare alla velocità standard (standard mode), che prevede una frequenza di 100KHz, altri possono lavorare anche in modalità alta velocità (400KHz – Fast Speed). Dalla formula vista prima possiamo ricavare il valore da dare al registro SSPADD:

Lavorando con un quarzo da 20MHz e volendo impostare il clock del modulo I2C a 100KHz abbiamo che:

Per completare il settaggio del modulo I2C dovremo ancora impostare qualche altro bit, questa volta nel registro SSPSTAT:

SMP (bit 7): Slewrate control. Tale bit va impostato ad 1 per disabilitare il controllo dello slew rate e lavorare a 100KHz. Se vogliamo invece lavorare a 400KHz tale bit va posto a zero.

Il bit SMP va posto ad 1 anche nel caso che la frequenza di lavoro sia di 1MHz, che è ad esempio supportata dalle EEProm esterne di tipo 24FC. Inutile dire che se su un unico bus ci sono più dispositivi, per semplificarci le cose bisognerebbe fare in modo che tutti comunichino alla stessa velocità e cioè quella del dispositivo con velocità più bassa, a meno di non fare delle piccole “acrobazie” via software. Oppure ancora possiamo scegliere periferiche I2C che abbiano le più alte velocità possibili: il mercato è molto vasto.

CKE (bit 6): SMBus select. Tale bit serve per indicare al picmicro se vogliamo lavorare in modalità compatibile SMBus. L’SMBus è un protocollo, poco usato, praticamente uguale all’I2C nel quale però la frequenza di clock non va oltre 100KHz e i livelli di tensione sono più bassi. Va posto a zero per lavorare con lo standard I2C.

Infine, anche se non è strettamente necessario in quanto assume già valore zero all’accensione, azzeriamo il flag di interrupt associato alla comunicazione seriale del modulo MSSP:

SSPIF (bit 3 del registro PIR1) – Synchronous Serial Port Interrupt Flag

Tale flag verrà difatti monitorato per gestire correttamente la comunicazione.

Funzionamento di una comunicazione I2C in un picmicro

Abbiamo quindi visto i passaggi necessari per impostare il nostro modulo MSSP per funzionare in modalità I2C master. Vediamo ora come è strutturata la comunicazione.

Quando il picmicro deve iniziare una trasmissione, terminarla o inviare dei dati, deve attendere che il bus si trovi in condizione idle, ovvero non occupato da altre trasmissioni in corso.

Il bus è in stato idle quando SDA e SCL si trovano a livello logico alto

Tale condizione viene determinata eseguendo un OR tra il bit 2 (RW – ricordo che nei pic enanched è indicato come R_nW) del registro SSPSTAT e i bit [4:0] (ACKEN, RCEN, PEN, RSEN, SEN) del registro SSPCON2 : fino a che l’una o l’altra condizione non si verifica, il pic deve rimanere in attesa.

Quindi si capisce che non possiamo fare questa operazione su un modulo SSP in quanto tale registro non c’è

In particolare il bit RW, quando si trova a zero, indica che non vi sono trasmissioni in corso, i bit [4:0] di SSPCON2 quando posti ad 1 indicano che in qualche modo la linea è ancora occupata. Quindi in C, per far rimanere il pic in attesa nel frattempo che il bus si liberi,  andremo a controllare queste condizioni nel modo seguente:

while ((SSPCON2 & 0x1F) | RW)
{
continue;
}

Una volta che il bus è libero, il pic lo deve occupare per poter segnalare che ha intenzione di iniziare a trasmettere e viene quindi inviata la sequenza di start: questo si fa ponendo ad 1 il bit 0 (SENStart Condition Enabled) del registro SSPCON2 (azzeriamo, per sicurezza, anche il flag di interrupt – SSPIF). Nella sequenza di start la linea SDA viene portata a livello logico basso mentre SCL è a livello alto. In questa condizione il bus risulta occupato. Il nostro pic comincia quindi a generare il clock per permettere alle periferiche di sincronizzarsi e quindi prepararsi a ricevere le eventuali richieste da parte del master.

Il microcontrollore deve quindi, per prima cosa, segnalare con chi ha intenzione di comunicare, per cui viene inviato sulla linea l’indirizzo della periferica di destinazione, generalmente l’indirizzo delle periferiche è a 7 bit, un’ottavo bit viene inviato in coda per segnalare se si vuole ricevere un dato dalla periferica in questione (bit posto a 1) o si vuole scrivere un dato sulla periferica (bit posto a 0). L’indirizzo della periferica, così come i dati, viene trasmesso caricandolo nel registro SSPBUF. I dati vengono inoltre inviati a partire dal bit più significativo a quello meno significativo.

Si capisce quindi che sul bus I2C non possono esistere due dispositivi con lo stesso indirizzo: difatti molti dispositivi I2C (ad esempio le memorie, come vedremo nella seconda parte della lezione) hanno una parte di indirizzo fissa e un’altra parte modificabile dall’utente mettendo alcuni pin del dispositivo a massa o a Vdd. L’indirizzo delle periferiche I2C e le modalità con cui può essere variato è sempre chiaramente indicato sul loro datasheet ed è un dato assolutamente essenziale.

Altri dispositivi hanno invece un indirizzo a 10bit, per cui l’indirizzo con cui comunicare verrà inviato in due parti  successive da 8 bit (essendo due byte composti da 16 bit, 5 bit avranno sempre un valore fisso indipendentemente dal dispositivo e l’ultimo bit restante è quello che decide la lettura/scrittura, sul tutorial di Mauro questo argomento è esposto in maniera molto chiara).

Nel caso di invio di dati a più di 8 bit, i bytes vengono sempre inviati in ordine dal più alto verso il più basso.

Dopo che il master ha inviato l’indirizzo della periferica con cui comunicare, si aspetta che qualcuno risponda: in pratica la periferica che ha riconosciuto il suo indirizzo invierà un segnale di Acknowledge (conferma) sulla linea. Tale segnale di conferma è possibile verificarlo controllando lo stato del bit 6 (ACKSTATAcknowledge Status) del registro SSPCON2. Quando tale bit è posto a zero, vuol dire che si è ricevuta una conferma e possiamo continuare la comunicazione.

Quindi fate molta attenzione: la logica di risposta è in un certo senso invertita: c’è il consenso quando tale bit è a zero, il mancato consenso quando tale bit è a 1. Nella libreria in fondo all’articolo, però,  il consenso viene restituito con un 1 perchè mi sembrava più logico.

E’ comunque pratica comune non controllare tale condizione includendo un ritardo e continuare la trasmissione.

Vengono quindi inviati i dati in sequenza:

  • nel caso che stiamo comunicando in scrittura (ovvero: vogliamo impostare un registro della periferica su un determinato valore), invieremo prima l’indirizzo del registro in cui si vuole scrivere e quindi in seguito il valore da scrivere. Si eseguiranno quindi le operazioni:
    1. Sequenza di Start
    2. Invio indirizzo periferica in scrittura
    3. Invio indirizzo del registro in cui scrivere
    4. Invio del valore da scrivere nel registro
    5. Sequenza di stop
Esempio di scrittura di un dato in una eeprom di tipo 24. Il “Control Byte” è l’indirizzo dell’EEProm (7 bit+bit di lettura/scrittura, vale 0 dato che vogliamo scrivere). Dopo l’indirizzo viene inviato l’indirizzo della cella in cui scrivere (indirizzo a 16 bit, quindi inviato in 2 byte successivi), infine viene inviato il dato ad 8 bit da scrivere nella cella. Tra l’invio di un byte e il successivo c’è l’acknowledge
  • nel caso che stiamo comunicando in lettura (ovvero: vogliamo che la periferica ci restituisca un valore), viene inviato l’indirizzo del registro della periferica che si vuole leggere. In pratica se vogliamo leggere un registro dalla periferica si eseguiranno le seguenti operazioni:
    1. Sequenza di Start
    2. Invio indirizzo periferica in scrittura
    3. Invio indirizzo registro da cui prelevare il dato
    4. Sequenza di start ripetuto (verrà spiegato più in basso cosa significa)
    5. Invio indirizzo periferica in lettura
    6. Lettura del valore (il valore sarà contenuto nel registro SSPBUF)
    7. Sequenza di stop
Esempio di lettura eeprom. Lo start inviato dopo l’indirizzo è in realtà uno start ripetuto.

Le periferiche I2C, quindi, hanno più registri dai quali possiamo prelevare i dati o scrivere informazioni. L’indirizzo dei registri e la loro funzione è sempre indicata sui datasheet.

Tutti i dati (valori, registri) come già detto vanno caricati nel registro SSPBUF: appena caricati vengono trasmessi in automatico. I dati vengono sempre inviati/ricevuti in pacchetti da 8 bit e dopo ogni pacchetto si attende il segnale di acknowledge per poter continuare.

Tra un invio e il successivo bisogna sempre controllare il flag di interrupt SSPIF per evitare di generare conflitti di trasmissione e quindi inficiare la trasmissione dei dati: quando tale bit è posto ad uno vuol dire che la trasmissione è terminata, quindi lo azzeriamo e riprendiamo a trasmettere.

Viene quindi terminata la trasmissione inviando la sequenza di stop: si pone a 1 il bit 2 (PENStop Condition Enable) di SSPCON2 e si da l’acknowledge (anche il master deve dare l’acknowledge!) ponendo a 1 il bit 4 (ACKENAcknowledge Sequence Enable) di SSPCON2. In questa condizione il bus si libera e si mette quindi in stato idle.

La trasmissione può anche essere interrotta e ripresa subito inviando la cosiddetta sequenza di start ripetuto: è simile alla sequenza di start ma serve a terminare la comunicazione e riprenderne un’altra senza inviare la sequenza di stop che farebbe perdere al Master il diritto di comunicazione costringendo ad aspettare altro tempo prima che il bus si liberi. La sequenza  di start ripetuto viene effettuata poneno ad 1 il bit 1 di SSPCON2 (RSENRepeated Start Condition Enable). Questa funzione è molto sfruttata, e richiesta, quando si devono ad esempio trasferire dati in lettura.

Downloads

Ai miei lettori lascio qui due librerie, quella originale l’ho trovata con Google e dal momento che mi è sembrata fatta molto bene, ho deciso di utilizzarla. Ho apportato delle modifiche alla libreria per renderla più snella, in particolare sulla selezione della frequenza di funzionamento, del quarzo e dei pin da utilizzare, che andrà fatta nel file header (i2c.h) – penso non siano necessarie molte spiegazioni:

// Impostare la frequenza del clock del modulo MSSP espressa in KHz
#define CLOCK 100 // (100, 400 o 1000)
 
// Impostare la frequenza del quarzo espressa in Hertz, lasciare la L dopo il valore
#define FOSC 20000000L
 
// Impostare i pin usati per la comunicazione I2C
#define SCL_PIN TRISC3
#define SDA_PIN TRISC4
/*
PIC-SCL-SDA
PIC16F877A-TRISC3-TRISC4
PIC16F887-""-""
PIC16F1939-""-""
PIC16F1827 (mssp1)-TRISB1-TRISB4
*/

In aggiunta ho modificato la libreria i2c.c per poterla adattare ai pic serie enanched con doppio modulo MSSP (file i2c_mssp1.c). In particolare la libreria per doppio modulo è pensata per utilizzare unicamente il modulo MSSP1, se avete la necessità di utilizzare il modulo MSSP2, con le informazioni che ho dato nell’articolo dovreste essere in grado di riuscire a modificarla da soli (basta sostituire gli 1 dei registri con il 2: SSP1CON1 diventerà SSP2CON1 e così via).

Chi è abituato a lavorare con i pic di fascia base, troverà delle stranezze nel codice i2c_mssp1.c in quanto i singoli bit vengono indicati facendo uso di strutture, ad esempio il bit SEN verrà indicato come:

SSP1CON2bits.SEN

In pratica abbiamo il nome del registro a sinistra, un punto, e il nome del bit a destra. Questo modo di indicare i bit è usato anche nei pic di fascia superiore alla 16 e a mio avviso è un sistema migliore in quanto non fa perdere di vista il registro su cui si sta lavorando e permette di utilizzare lo stesso nome dei bit nel caso ci fossero due registri con funzioni uguali, come appunto nei moduli MSSP doppi: ci sono due registri SSPCON2 (il SSP1CON2 e il SSP2CON2), entrambi hanno dei bit con lo stesso nome, non facendo uso di strutture avremmo dovuto trovare un altro nome per il secondo bit SEN, e questo avrebbe reso molto difficile il districarsi in questi concetti che tra l’altro possono risultare già molto complicati di per sè.

A seconda del pic che utilizzate quindi includerete l’una o l’altra libreria.

Alcune funzioni verranno descritte in un prossimo articolo, anche se leggendo il codice penso non ci sia poi molto da chiarire. In particolare la libreria supporta sia la modalità master che la slave, per cui ci saranno funzioni di lettura e scrittura sia per la modalià master che per la modalità slave.

Ringrazio Luca Gallucci per avermi aiutato a testare questa libreria sul PIC16F1827.

Download

Con l’avvento di MPLAB X e della funzionalità del code configurator, le librerie I2C qui presenti sono obsolete e sicuramente con difetti di funzionamento, ma, dal momento che in molti continuate a chiederle, lascio qui tutti i downloads:

Ricordo ancora una volta che queste librerie non possono essere usate nei pic che hanno il modulo SSP al posto dell’MSSP.

Un programma di esempio aggiornato, che fa uso delle librerie create da MPLAB Code Configurator da MPLAB X e che utilizza il compilatore XC8 è illustrato in questo articolo più recente.

Nota: i programmi di esempio sono stati sviluppati con una versione precedente dell’Hitec-C Compiler, per cui compilati con la nuova versione, restituiscono errori. Fate riferimento a questo articolo per maggiori informazioni su come adattare i vecchi programmi. Consiglio spassionato se volete davvero imparare a programmare: non utilizzate l’include legacy headers, ma imparate a cambiare i nomi mnemonici.

Approfondimenti

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