In questa pagina verrà mostrato come scrivere il codice per utilizzare un PIC18 come periferica I2C. Non verrà descritto il bus I2C, ma Internet è ricca di materiale sull'argomento..
Come master è stato utilizzato un Raspberry Pi, ma evidentemente è possibile utilizzare qualunque altro dispositivo capace di pilotare un bus I2C a 3.3 V.
Il funzionamento mostrato nell'esempio è il seguente:
Il PIC utilizzato è un PIC18F25K50, ma il codice è sostanzialmente indipendente dal particolare modello di PIC18 scelto. In particolare è stata utilizzata la scheda PIC-USB, ma evidentemente il circuito può essere interamente realizzato su breadboard.
Il bus è costituito da soli due fili:
Non sono state utilizzate le due resistenze di pull-up su SDA e SCL, obbligatorie, ma già presenti all'interno del Raspberry Pi.
Sono inoltre visibili i due fili rosso e nero per portare dal Raspberry Pi alla breadboard rispettivamente la tensione di alimentazione di 3.3 V e la massa, utilizzate da tutte le periferiche (nota 1)
Sono infine presenti alcuni LED, collegati a PORTA (solo quattro nella fotografia...)
Il protocollo I2C è piuttosto complesso ed è quindi decisamente utile disporre di hardware specializzato per la sua gestione. Nel PIC18 questo hardware interno è indicato con il nome di MSSP, Master Synchronous Serial Port, lo stesso utilizzato per il bus SPI. Ovviamente i due bus non possono essere usati contemporaneamente.
La struttura interna del modulo MSSP configurato come Slave I2C non è semplice:
I vari eventi presenti sul bus attivano opportuni flag del modulo MSSP e, se configurato, permettono di generare interruzioni. Esaminando il valore dei flag è possibile individuare lo stato in cui la trasmissione si trova, come di seguito mostrato.
In questo contesto occorre dire che l'uso delle interruzioni è in pratica una scelta obbligata; quando il modulo hardware MSSP rileva un evento interessante, invoca la ISR che provvede ad identificare lo stato ed agire di conseguenza.
Il codice di esempio è disponibile a fondo pagina.
Innanzitutto occorre attivare la modalità di funzionamento del modulo MSSP. Per quanto riguarda I2C slave ne esistono ben quattro:
In questo esempio verrà utilizzata la prima modalità, di gran lunga la
più usata:
SSPCON1bits.SSPM = 0b0110;
Occorre poi configurare i pin di ingresso SCL e SDA, da impostare
inizialmente come ingressi digitali e successivamente gestiti dal modulo
MSSP:
TRISBbits.RB0 = 1;
TRISBbits.RB1 = 1;
ANSELBbits.ANSB0 = 0;
ANSELBbits.ANSB1 = 0;
Inoltre è necessario rilasciare il clock (nel caso sia bloccato per un
errore precedente), connettere i pin di ingresso
al registro a scorrimento e configurare lo slew rate in
base alla frequenza del clock I2C:
SSPCON1bits.CKP = 1;
SSPCON1bits.SSPEN = 1;
SSPSTATbits.SMP = 0;
Uno slave I2C può rallentare la trasmissione dei dati, al fine di avere un
tempo sufficiente per elaborare le informazioni ricevute o da trasmettere.
In genere alcune operazioni possono essere fatto in hardware (e quindi senza
ritardi significativi). Questa è l'opzione di default, ma, nel caso di clock
particolarmente bassi, questa scelta causa errori di trasmissione. Per
questo motivo nel codice di esempio è attivato il cosiddetto clock
stretching, che permette di inserire un'attesa sufficiente dopo ogni
byte trasmesso o ricevuti. Se il clock del processore è sufficiente, questa
opzione non ha comunque significativi impatti negativi sulle prestazioni:
SSPCON2bits.SEN = 1;
L'indirizzo usato dallo slave è gestito in hardware e quindi va memorizzato
in un apposito registro del MSSP:
SSP1ADD = I2C_ADDR << 1;
Occorre quindi configurare il modulo per generare le interruzioni, in questo
esempio ad alta priorità:
IPR1bits.SSPIP = 1;
PIE1bits.SSPIE = 1;
Infine occorre attivare globalmente le interruzioni, in questo esempio solo
ad alta priorità:
RCONbits.IPEN = 1;
ei();
... ed alimentare il modulo MSSP:
PMD1bits.MSSPMD = 0;
La ISR viene invocata, in questo esempio, nei seguenti casi:
L'hardware del MSSP permette di discriminare tra queste situazioni, impostando opportuni flags. Occorre sottolineare che l'interrupt è generato solo se il master ha precedentemente trasmesso l'indirizzo dello slave.
Tre operazioni devono sempre essere effettuate all'interno della ISR:
i2c_data = SSPBUF;
PIR1bits.SSPIF = 0;
SSP1CON1bits.CKP = 1;
Per evitare fraintendimenti è bene precisare è il master che legge il dato; Di conseguenza lo slave deve scrivere.
Questa situazione viene rilevata attraverso il flag
SSP1STATbits.R_nW, che non è altro che il valore dell'ultimo bit
dell'ultimo indirizzo trasmesso dal master:
if (SSP1STATbits.R_nW) { }
Due sono le situazioni possibili:
if ((SSP1STATbits.D_nA) && (SSP1CON2bits.ACKSTAT))
{ // Nessuna azione}
else
{SSPBUF = i2c_counter++;}
Il byte trasmesso in questo esempio è i2c_counter, una variabile locale statica incrementata ogni volta di una unità e azzerata solo dal reset.
Per evitare fraintendimenti è bene precisare è il master che scrive il dato; Di conseguenza lo slave deve leggere.
In questo caso, viene verificato se l'ultimo byte ricevuto è un indirizzo (spesso in questo caso lo slave deve semplicemente predisporsi a ricevere i successivi dati, se necessario) o di un dato. In quest'ultimo caso, il codice d'esempio semplicemente visualizza sui LED il byte ricevuto dal master
if (!SSP1STATbits.D_nA)
{ // Nessuna azione }
else
{LATA = i2c_data;}
Il collaudo è stato effettuato usando i comandi bash standard presenti in Raspbian. Qui la presentazione.
Preliminarmente è opportuno verificare se lo slave PIC18 viene rilevato con l'indirizzo corretto (0x03 nell'esempio, ma evidentemente sostituibile con qualunque indirizzo valido):
vv@vvrpi ~ $ i2cdetect -y 1
Il test più semplice è la lettura "semplice" di un byte.
vv@vvrpi ~ $ i2cget -y 1 0x03
Il frame "teorico" scambiato tra master e slave è mostrato nello schema seguente dove il significato di lettere e simboli è il seguente:
Inoltre in blu i segnali generati dal Raspberry Pi (master), in giallo quelli generati dal PIC18 (slave).
Si notino in particolare il valore del bit di lettura / scrittura (freccia rossa) ed il riscontro negativo al termine della comunicazione (freccia verde).
Di seguito il diagramma temporale reale:
I vari componenti, meglio leggibili all'interno del blocco più scuro, coincidono con lo schema teorico. Si noti il lungo periodo (indicativamente da 0,1 a 0,2 ms) per il quale il clock viene mantenuto basso dallo slave, in corrispondenza della A su fondo giallo (clock stretching), bloccando di fatto la comunicazione. Questo diagramma temporale è stato misurato eseguendo il codice con una frequenza di clock pari ad 1 MHz.
Un frame pi complesso è stato ottenuto con il comando:
vv@vvrpi ~ $ i2cget -y 1 3 0x05 w
In questo caso il master effettua prima una scrittura del byte 0x05 (in genere è l'identificativo di un registro interno dello slave) e quindi, dopo uno Start Ripetuto, la lettura di due byte. Si notino in rosso i due bit, prima di scrittura e poi di lettura (freccia rossa), ed i vari riscontri subito dopo la trasmissione di ciascun byte (freccia verde).
Il diagramma temporale non presenta particolari sorprese:
Una nota interessante riguarda l'influenza della frequenza del clock del PIC18. Il seguente diagramma, ottenuto con l'identica configurazione del master, presenta ritardi non rilevabili in corrispondenza dei riscontri trasmessi dallo slave. Ovviamente la frequenza di SCL è rimasta invariata (100 kHz). Il codice dello slave è identico, se non nell'impostazione iniziale del clock del PIC18 a 48 MHz anziché 1 MHz.
Data di creazione di questa pagina: aprile 2016
Ultima modifica: 20 aprile 2017
PIC18 in C - Versione 0.991 - luglio 2019
Copyright 2014-2019, Vincenzo Villa (https://www.vincenzov.net)
PIC18 in C di Vincenzo Villa è distribuito con Licenza Creative Commons Attribuzione 4.0 Internazionale