I2C in C

I2C e Raspberry Pi

In questa pagina verranno presentati alcuni brevi programmi scritti in C per utilizzare periferiche I2C con Raspberry Pi tramite le funzioni standard Linux.

Il bus I2C di Raspberry Pi è già stato discusso; se ancora non lo avete fatto, leggete la pagina introduttiva su Raspberry e I2C prima di proseguire in quanto non verrà qui ripreso nulla su come configurare il modulo i2c-dev e come assegnare i permessi agli utenti. I pin utilizzati dal bus I2C sono disponibili sul connettore GPIO.

La configurazione hardware

Il circuito utilizzato per le prove è costituito da due dispositivi, tra di loro assolutamente indipendenti e scelti solo perché "erano nel cassetto":

Ovviamente siete liberi di usare altri circuiti integrati, purché con interfaccia I2C a 3.3 V.

Il circuito è semplicemente costituito dai due integrati con le linee SDA e SCL collegate direttamente al connettore GPIO del Raspberry Pi. Le due resistenze di pull-up, obbligatorie, sono già presenti sul circuito stampato del Raspberry Pi.

I2C: LM92 e MCP23017 collegati al Raspberry Pi

Per una prima verifica del funzionamento potete utilizzare il comando i2cdetect, già descritto in questa pagina.

Esempio 1: LM92

Il programma legge direttamente da LM92 la temperatura rilevata, utilizzando la modalità indicata sui fogli tecnici come 2-Byte Read From Preset Pointer Location (Figura 10), cioè utilizzando una singola operazione di lettura.

vv@vvrpi ~ $ ./LM92
[...]
Reading from LM92 at address 0x4B on /dev/i2c-1
Data read from Power-up default register: 0x0EB8
Temperature: 29.4 °C

(ebbene sì: il laboratorio non ha l'aria condizionata...)

A fondo pagina il codice sorgente. Le operazioni effettuate sono, in estrema sintesi, le seguenti:

  1. fd = open(device, O_RDWR);
    Inizializza il dispositivo, attraverso l'operazione di apertura del "file" /dev/i2c-1
  2. ioctl(fd, I2C_SLAVE,I2C_ADDR);
    Assegnazione dell'indirizzo del dispositivo LM92
  3. read(fd,buffer,2);
    lettura dei due byte contenenti la temperatura. In realtà sul bus I2C sono trasmessi tre byte: occorre infatti anche aggiungere l'indirizzo del dispositivo, operazione fatta automaticamente dalla funzione read().
  4. data = buffer[0];
    data = data << 8;
    data = data | buffer[1];
    data = data >> 3;
    temperature = LM92_RES * data;

    "Riordino" dei bit e conversione nel numero reale corrispondente alla temperatura in gradi centigradi.
  5. close(fd);
    Chiusura del "file"

Occorre osservare che il protocollo I2C prevede il riscontro di ogni singolo byte trasmesso o ricevuto. Questo permette di identificare errori nella comunicazione quali indirizzo errato oppure periferica spenta, assente o guasta. In questo casi il programma stampa un codice di errore come il seguente:

Failed to read from the i2c bus: Input/output error

Occorre notare che questo programma funziona correttamente solo se non sono state modificate le configurazioni di LM92 dopo l'accensione.

Esempio 2: MCP23017

Il programma, dopo aver configurato il circuito integrato MCP23017, manda in uscita alla porta B gli otto bit letti dalla linea di comando e legge otto bit dalla porta A, visualizzandoli. Rispetto al precedente esempio di aggiunge un livello di difficoltà in più che deriva dalla necessità di dover scrivere dati verso la periferica.

vv@vvrpi ~ $ ./MCP23017 10
[...]
Write 0xA to PortB
Data read from PortA: 0xBB

A fondo pagina il codice sorgente. Descriviamo brevemente il programma

  1. fd = open(device, O_RDWR);
    Inizializza il dispositivo, attraverso l'operazione di apertura del "file" /dev/i2c-1
  2. ioctl(fd, I2C_SLAVE,I2C_ADDR);
    Impostazione dell'indirizzo del dispositivo MCP23017
  3. buffer[0] = MCP23017_IODIRB;
    buffer[1] = 0x00;
    write(fd,buffer,2);

    Per scrivere un registro occorre inviare due byte: il primo è l'indirizzo del registro, il secondo il contenuto. Per i dettagli sull'uso dei vari registri occorre consultare il foglio tecnico; all'interno del codice sorgente è comunque presente una brevissima descrizione di questo e degli altri comandi utilizzati. Si osservi che verranno scritti complessivamente 3 byte: il primo contenente l'indirizzo della periferica (aggiunto automaticamente), il secondo con l'indirizzo del registro (in questo caso), il terzo con il dato da scrivere
  4. buffer[0] = MCP23017_GPIOA;
    write(fd,buffer,1);
    read(fd,buffer,1);
    La lettura di un registro avviene in due fasi: prima viene scritto l'indirizzo del registro (2 byte complessivi: indirizzo del dispositivo + indirizzo del registro), quindi viene letto il "risultato" (2 byte complessivi)
  5. close(fd);
    Chiusura del "file"

i2c_smbus_*()

In alternativa alle funzioni read() e write() è possibile  l'utilizzo di un altro gruppo di funzioni, specifiche per il bus I2C. Questa seconda strada è quella consigliata nella documentazione del kernel. E' utile anche consultare il file  /usr/include/linux/i2c-dev.h.

Le funzioni disponibili permettono di leggere e scrivere byte, word o blocchi da dati:

__s32 i2c_smbus_write_quick(int file, __u8 value);
__s32 i2c_smbus_read_byte(int file);
__s32 i2c_smbus_write_byte(int file, __u8 value);
__s32 i2c_smbus_read_byte_data(int file, __u8 command);
__s32 i2c_smbus_write_byte_data(int file, __u8 command, __u8 value);
__s32 i2c_smbus_read_word_data(int file, __u8 command);
__s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value);
__s32 i2c_smbus_process_call(int file, __u8 command, __u16 value);
__s32 i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 length, __u8 *values)
__s32 i2c_smbus_write_block_data(int file, __u8 command, __u8 length, __u8 *values);

L'uso di queste funzioni è piuttosto intuitivo. L'unica avvertenza riguarda il valore ritornato:

A fondo pagina i codici di esempio, simili nella funzionalità a quelli già presentati.

In parallelo alle funzioni i2c_smbus_*() sono presenti anche le corrispondenti chiamate ioctl. Tra queste è bene citare:

I segnali

Di seguito è riportato il diagramma temporale misurato durante l'esecuzione del programma dimostrativo che utilizza MCP23017.

Lettura/scrittura I2C

Scorrendo il diagramma temporale in parallelo al codice è possibile riconoscere, partendo da sinistra le seguenti operazioni:

La durata complessiva è in linea con le attese: sono infatti trasmessi 4 * 3 + 2 * 2 = 16 byte = 128 bit alla frequenza di 100 kHz (quindi 1,28 ms) a cui vanno aggiunte le condizioni di start e stop tipiche del protocollo I2C, il tempo necessario per l'esecuzione del codice e il tempo richiesto dai passaggi di contesto tra kernel-mode e user-mode.

Un osservatore attento si dovrebbe accorge di una incongruenza: il codice realmente eseguito durante questa misura è infatti stato leggermente modificato rispetto a quanto riportato, togliendo le due righe con le funzioni printf e itoa, particolarmente lente nell'esecuzione...

Il codice

 

Data di creazione di questa pagina: luglio 2013


Licenza "Creative Commons" - Attribuzione-Condividi allo stesso modo 3.0 Unported


Pagina principaleAccessibilitàNote legaliPosta elettronicaXHTML 1.0 StrictCSS 3

Vai in cima