.Net Micro Framework : usiamo il bus I2C !

Alla pari dell’SPI, analizzato in un articolo precedente, l’I2C (Inter-Integrated Circuit) è un bus di comunicazione sincrono utilizzato per la connessione e lo scambio dati tra un microprocessore e le periferiche esterne; sviluppato dalla Philips, oggi NXP, è diventato uno standard “de facto”.

Descrizione del bus

l’I2C è anche noto come bus two-wire in quanto è caratterizzato a tutti gli effetti da due soli “fili” :

  • SDA (Serial Data Line) : linea per il trasferimento dati;
  • SCL (Serial CLock) : clock per il sincronismo nello scambio dati;

Le linee suddette sono sempre caratterizzate da una resistenza di pull-up che ha il compito di mantenere il segnale “alto” (1 logico) in condizioni di idle mentre i componenti interconnessi (master e slave) hanno il compito di abbassarne il livello per trasferire uno 0 logico e di rilasciarlo per riportarlo in idle e trasferire un 1 logico; questo comportamento è tipico delle linee open-drain.

7506.425px-I2C_svg_thumb_0C0994EE

Analogamente all’SPI, è possibile avere più slave connessi al bus ed un unico master con cui comunicare; la differenza principale è che non esiste un segnale di SS (Slave Select) ma il master seleziona lo slave con cui comunicare attraverso un indirizzamento. Infatti, il master trasmette l’indirizzo dello slave sulla linea SDA prima di iniziare il trasferimento dei dati veri e propri; tale indirizzo è tipicamente a 7 bit (fino a 128 slave) ma è prevista un’estensione fino a 10 bit (fino a 1024 slave).

Una caratteristiche fondamentale dell’I2C è che permette la presenza di più master sul bus a differenza dell’SPI (modalità multi master).

Il protocollo di comunicazione

Il protocollo di comunicazione è caratterizzato dai seguenti passi :

  1. START Condition : il master abbassa l’SDA tenendo ancora alto l’SCL per indicare la condizione di START allo slave e quindi l’inizio di una trasmissione;
  2. Indirizzamento : il master invia un byte (MSB first) sul bus, in cui i primi 7 bit rappresentano l’indirizzo dello slave con cui comunicare e l’ultimo bit indica il tipo di operazione da voler effettuare (0 = write, 1, = read);
  3. Slave acknowledge : se sul bus esiste uno slave con tale indirizzo, esso risponde con un bit di ACK (0 logico);
  4. Comunicazione : a questo punto, il master può inviare e/o ricevere dati dallo slave in maniera sincrona grazie al movimento dell’SCL. Per ogni byte scambiato è sempre previsto un ACK dalla controparte;
  5. STOP Condition : il master alza l’SCL tenendo ancora basso l’SDA per indicare la STOP condition allo slave e quindi il termine della trasmissione;

4762.798px-I2C_data_transfer_svg_thumb_763FAC90

Il clock è sempre pilotato dal master ma in alcuni casi lo slave può mantenerne il valore basso per introdurre del delay ed evitare che il master gli invii altri dati (magari ha bisogno di più tempo per elaborare i dati già ricevuti) : questa funzionalità si chiama “clock stretching”.

.Net Micro Framework : le classi per utilizzare il bus

Il .Net Micro Framework semplifica notevolmente l’utilizzo del bus I2C mediante la classe I2CDevice (namespace Microsoft.SPOT.Hardware, assembly Microsoft.SPOT.Hardware.dll), il cui costruttore prevede un parametro del tipo I2CDevice.Configuration per poter essere opportunamente configurata; tale configurazione permette di impostare :

  • Address : indirizzo dello slave con cui comunicare;
  • ClockRateKhz : frequenza del clock;

Tutti i parametri di configurazione suddetti sono sempre strettamente legati al device con il quale si intende comunicare e vanno ricercati all’interno del datasheet.

I2CDevice.Configuration config =
      new I2CDevice.Configuration(I2C_ADDRESS, I2C_CLOCK_RATE_KHZ);

I2CDevice i2c = new I2CDevice(config);

La classe I2CDevice mette a disposizione un unico metodo Execute() per poter effettuare una o più “transazioni” sul bus che rappresentano le operazioni di lettura e scrittura con lo slave. Tale metodo prevede in ingresso un array di oggetti I2CDevice.I2CTransaction ed un timeout. La classe I2CDevice.I2CTransaction è la classe base per la classe I2CDevice.I2CReadTransaction, nel caso di una transazione di lettura, e per la classe I2CDevice.I2CWriteTransaction, nel caso di una transazione di scrittura.

La creazione di un’istanza per ciascuna delle due classi suddette può essere effettuata attraverso i due seguenti metodi statici della classe I2CDevice :

  • CreateReadTransaction() : crea un’istanza della classe I2CDevice.I2CReadTransaction associando ad essa l’array di byte ricevuto in ingresso come buffer per la ricezione dati dallo slave (inizialmente vuoto);
  • CreateWriteTransaction() : crea un’istanza della classe I2CDevice.I2CWriteTransaction associando ad essa l’array di byte ricevuto in ingresso come buffer contenente i dati da trasmettere allo slave;

In definitiva, la procedura d’uso dell’I2C prevede di creare un array di “transazioni” di lettura e/o scrittura (ovviamente anche mixate) ed eseguire queste transazioni in un solo colpo, ritrovandosi i dati trasmessi allo slave ed i buffer di ricezione con i dati richiesti.

Immaginiamo di aver un componente I2C caratterizzato da una serie di registri interni e di voler leggere il contenuto di uno di essi. Questo tipo di comunicazione è caratterizzata da due “transazioni” I2C; la prima di scrittura per poter inviare allo slave l’indirizzo del registro da leggere (attenzione !! non parliamo dell’indirizzo dello slave stesso che viene inviato in precedenza) e la seconda di lettura per poterne leggere il contenuto.

byte[] write = { REG_ADDRESS };
byte[] read = new byte[1];

// create I2C write and read transaction
I2CDevice.I2CTransaction[] i2cTx = new I2CDevice.I2CTransaction[2];
i2cTx[0] = I2CDevice.CreateWriteTransaction(write);
i2cTx[1] = I2CDevice.CreateReadTransaction(read);

// execution
i2c.Execute(i2cTx, I2C_TIMEOUT);

Uno sguardo verso il basso : lo strato di HAL

Anche nel caso dell’I2C (come già visto per l’SPI), ciascun OEM deve implementare uno strato HAL (e PAL) che faccia da “ponte” tra la parte CLR (fino al codice managed ad alto livello) ed il particolare hardware sottostante.

Prendiamo come riferimento la board Netduino (generazione 1) che ha come microcontrollore un Atmel AT91. Scaricando i sorgenti del firmware (sono open source) dal sito ufficiale, possiamo individuare l’implementazione in codice managed C# della classe I2CDevice (Framework\Core\Native_Hardware\I2C.cs) all’interno della quale viene invocato il metodo GetI2CPins() sull’istanza corrente dell’HardwareProvider.

public I2CDevice(Configuration config)
{
 this.Config = config;

 HardwareProvider hwProvider = HardwareProvider.HwProvider;

 if (hwProvider != null)
 {
 Cpu.Pin scl;
 Cpu.Pin sda;

 hwProvider.GetI2CPins(out scl, out sda);

 if (scl != Cpu.Pin.GPIO_NONE)
 {
 Port.ReservePin(scl, true);
 }

 if (sda != Cpu.Pin.GPIO_NONE)
 {
 Port.ReservePin(sda, true);
 }
 }

 Initialize();

 m_disposed = false;
}

Dopo una serie di invocazioni a cascata attraverso il CLR fino all’implementazione dell’HAL, sarà invocato il metodo GetPins() sulla classe AT91_I2C_Driver(DeviceCode\Targets\Native\AT91\DeviceCode\AT91_I2C\AT91__I2C.cpp) che ritorna gli identificativi dei pin del processore associati alla porta I2C.

void AT91_I2C_Driver::GetPins(GPIO_PIN& scl, GPIO_PIN& sda)
{
 NATIVE_PROFILE_HAL_PROCESSOR_I2C();

 scl = AT91_TWI_SCL;
 sda = AT91_TWI_SDA;
}

Nel caso della board Netduino (generazione 2) che ha un processore STM32, la funzione di lettura dei pin I2C è I2C_Internal_GetPins() (DeviceCode\Targets\Native\STM32\DeviceCode\STM32_I2C\STM32_i2c_functions.cpp).

void I2C_Internal_GetPins(GPIO_PIN& scl, GPIO_PIN& sda)
{
 scl = I2Cx_SCL_Pin;
 sda = I2Cx_SDA_Pin;
}

Conclusione

Il bus I2C a differenza dell’SPI non è ovviamente full duplex essendo caratterizzato da una sola linea dati ed è anche più lento in termini di velocità. Il vantaggio principale è quello di non avere la complessità di un segnale di selezione dello slave e di poter lavorare in modalità multi master.

Il .Net Micro Framework permette di utilizzare questo bus con estrema semplicità con una sola classe ed il concetto di “transazioni” lettura/scrittura I2C in modo da eseguire la comunicazione in un solo “colpo”.

Molto presto vedrete un esempio reale di applicazione di questo bus (come per il bus SPI) con un managed driver che ho sviluppato per un chip NFC della NXP !

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s