Salta la barra di navigazione [1] - Vai alla barra di navigazione [3] - Scrivimi una mail [4]

Slave I2C

PIC18 in C - Sommario - Novità - Tutorial - Progetti - Risorse - Non solo elettronica

PIC18: slave I2C

In fase di sviluppo Stesura preliminare In fase di sviluppo

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:

Hardware

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 software

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:

MSSP slave I2C

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.

Configurazione di MSSP

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

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;

Lettura

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.

Scrittura

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;
}

Diagrammi temporali

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

I2C: semplice lettura di un byte

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:

I2C: semplice lettura di un byte - Diagramma temporale

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

I2C: lettura di due byte, scrittura di un byte

Il diagramma temporale non presenta particolari sorprese:

I2C: lettura di due byte, scrittura di un byte - Diagramma temporale

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.

I2C: lettura di due byte, scrittura di un byte - Diagramma temporale - 48 MHz

Codice

Note

  1. Durante lo sviluppo del codice è necessario impostare il PICkit per non fornire la tensione di alimentazione al PIC. Se invece, per qualche ragione, si preferisce alimentare il PIC attraverso il PICkit, è necessario non collegare nulla tra la Vcc del Raspberry Pi e la Vcc del PIC18 (comunque entrambe devono essere di circa 3.3 V)
  2. Il PIC18 è in grado di rispondere a più indirizzi, utilizzando opportune maschere sui bit del proprio indirizzo.
  3. In realtà il timeout è presente solo se il master è conforme alle specifiche SMBUS. Questa è la situazione in assoluto più comune
  4. Questa opzione è necessaria, nel caso di I2C a 100 kHz, con il clock di sistema impostato a 1 MHz (valore predefinito), non più per la frequenza di 16 MHz o 48 MHz, frequenze attivabili con le seguenti impostazioni (qui un approfondimento sul generatore di clock):
    OSCCONbits.IRCF = 0b111;
    OSCCON2bits.PLLEN = 1;

Ultima modifica di questa pagina: 20 aprile 2016


PIC18 in C

Appendici:

Qualche utile informazione integrativa: C per sistemi embedded

Licenza Creative Commons Attribuzione 4.0 Internazionale


EN - Pagina principale - Sommario - Accessibilità - Note legali e privacy policy - Posta elettronica

XHTML 1.0 Strict - CSS 3