 
                            Alcuni dei sensori che costituiscono la dotazione della FRDM-KL46Z comunicano tramite il bus I2C. Comodo per alcuni, eccessivamente "semplice" per altri, comunque lo vogliate vedere si tratta di un'interfaccia di comunicazione utile e che può essere impiegata con grande successo. Oggi vediamo il dettaglio del suo utilizzo in funzione dei fenomeni di stallo cui va spesso in contro la MCU. Siete pronti?
Il nostro corso sta andando avanti e si addentra sempre di più nelle applicazioni con lo scopo di diventare sempre più specialistico e di farvi vedere casi implementativi reali di grande interesse. L'argomento di oggi riguarda l'utilizzo del bus di comunicazione I2C, uno dei più gettonati quando si lavora con sensori "semplici" e sicuramente tutti gli amanti di Arduino lo conosceranno molto bene.
- ACK -> Segnale di acknowledge, ovvero il segnale generato dal dispositivo che riceve i dati;
- ISR -> Interrupt Service Routine, ovvero una porzione del codice che viene eseguita non appena ci sia un interrupt;
- NACK -> in qualche modo, il duale del primo. È una condizione in cui sono sorti dei problemi di interpretazione o di ricezione.
Il bus
Dell'I2C ci siamo già occupati in passato, in diverse occasioni; l'Inter-Integrated Circuit è un'interfaccia di comunicazione a due fili bidirezionale. SDA ed SCL, ovvero Serial Data e Serial Clock, che trasportano l'informazione tra dispositivi connessi tramite il bus. Tutti i sensori in questione verranno poi alimentati con la relativa tensione di funzionamento. Ciascun dispositivo viene riconosciuto ed identificato all'interno dell'intera rete di connessione grazie all'utilizzo di un indirizzo univoco specifico. Un dispositivo, che funge da master, si occupa di inizializzare il trasferimento dei dati generando il segnale di clock che sincronizza il trasferimento stesso. Al tempo della trasmissione, ovvero non appena comincia, ciascun dispositivo viene considerato come slave nel momento stesso in cui venga indirizzato.
- segnale di start;
- indirizzo del dispositivo, seguito dal "write" bit;
- trasmissione dell'indirizzo del registro all'interno del quale il dato verrà scritto;
- trasmissione dei dati;
- segnale di stop.

- segnale di start;
- indirizzo dello slave seguito dal "read" bit;
- trasmissione dell'indirizzo del registro da cui viene eletto il dato;
- nuovo segnale di start;
- trasmissione dell'indirizzo dello slave seguito dal "write" bit;
- lettura dei primi dati dal data register;
- trasmissione dei dati (multipla);
- nessun segnale di acknowledge;
- segnale di stop.

Non-Blocking
typedef enum
{
I2C_TRM_STAGE_NONE = 0,
I2C_TRM_STAGE_WRITE_DATA,
I2C_TRM_STAGE_WRITE_DEV_ADDRESS_W,
I2C_TRM_STAGE_WRITE_DEV_ADDRESS_R,
I2C_TRM_STAGE_WRITE_REG_ADDRESS,
I2C_TRM_STAGE_READ_DUMMY_DATA,
I2C_TRM_STAGE_READ_DATA,
I2C_TRM_STAGE_NACK,
} tI2C_trm_stage;
typedef enum
{
I2C_MODE_READ = 0,
I2C_MODE_WRITE,
} tI2C_mode;
typedef enum
{
I2C_FLAG_NONE = 0,
I2C_FLAG_TRANSMISSION_PROGRESS,
} tI2C_flag;
typedef enum
{
I2C_NO_FAULT = 0,
I2C_BUS_BUSY,
I2C_TIMEOUT,
I2C_PERMANENT_BUS_FAULT,
} tI2C_fault;
L'algoritmo

#define SENSOR_I2C_ADDRESS 0x5A
#define IICWRITE(iicaddress) ((iicaddress<<1) & 0xFE)
#define IICREAD(iicaddress) ((iicaddress<<1) | 0x01)
#define SENSOR_I2C_ADDRESS_W IICWRITE( SENSOR_I2C_ADDRESS ) //0xB4
#define SENSOR_I2C_ADDRESS_R IICREAD( SENSOR_I2C_ADDRESS ) //0xB5
typedef enum
{
SENSOR_NO_FAULT = 0,
SENSOR_INIT_FAILED,
SENSOR_READ_FAILED,
} tSensor_fault;
typedef enum
{
SENSOR_NO_TRANSFER = 0,
SENSOR_TRANSFER_PROGRESS,
SENSOR_TRANSFER_FINISHED,
} tSensor_data_transfer;
typedef struct
{
tSensor_fault eSensor_fault;
tSensor_data_transfer eSensor_data_transfer;
unsigned char measured_data;
} tSensor_com_ctr;
tSensor_com_ctr sTMPR_com_ctr;
unsigned char iic_data[10];
void TemperatureSensingInit (void)
{
sI2C_com_ctr.device_address_w = SENSOR_I2C_ADDRESS_W;
sI2C_com_ctr.device_address_r = SENSOR_I2C_ADDRESS_R;
sI2C_com_ctr.eI2C_flag = I2C_FLAG_NONE;
sI2C_com_ctr.eI2C_fault = I2C_NO_FAULT;
if (Sensor_Init() == SENSOR_INIT_FAILED)
{
sTMPR_com_ctr.eSensor_fault = SENSOR_INIT_FAILED;
return;
}
ENABLE_TIMER_INTERRUPT;
}
tSensor_fault Sensor_Init (void)
void Timer1_Isr(void)
{
// initialization of I2C control structure
sI2C_com_ctr.register_address = 0x04;
sI2C_com_ctr.data_size = 1;
sI2C_com_ctr.eI2C_flag = I2C_FLAG_NONE;
sI2C_com_ctr.eI2C_trm_stage = I2C_TRM_STAGE_WRITE_DEV_ADDRESS_W;
if (I2C_read_data(&sI2C_com_ctr, iic_data) != I2C_NO_FAULT)
sTMPR_com_ctr.eSensor_fault SENSOR_READ_FAILED;
else
{
sTMPR_com_ctr.eSensor_data_transfer = SENSOR_TRANSFER_PROGRESS;
sTMPR_com_ctr.eSensor_fault = SENSOR_NO_FAULT; }
CLEAR_TIMER_INTERRUPT_FLAG;
}
typedef enum
{
I2C_TRM_STAGE_NONE = 0,
I2C_TRM_STAGE_WRITE_DATA,
I2C_TRM_STAGE_WRITE_DEV_ADDRESS_W,
I2C_TRM_STAGE_WRITE_DEV_ADDRESS_R,
I2C_TRM_STAGE_WRITE_REG_ADDRESS,
I2C_TRM_STAGE_READ_DUMMY_DATA,
I2C_TRM_STAGE_READ_DATA,
I2C_TRM_STAGE_NAK,
} tI2C_trm_stage; // transmission stages
typedef enum
{
I2C_MODE_READ = 0,
I2C_MODE_WRITE,
}tI2C_mode;
typedef enum
{
I2C_FLAG_NONE = 0,
I2C_FLAG_TRANSMISSION_PROGRESS,
} tI2C_flag;
typedef enum
{
I2C_NO_FAULT = 0,
I2C_BUS_BUSY,
I2C_TIMEOUT,
I2C_PERMANENT_BUS_FAULT,
}tI2C_fault;
typedef struct
{
tI2C_trm_stage eI2C_trm_stage;
tI2C_flag eI2C_flag;
tI2C_mode eI2C_mode;
tI2C_fault eI2C_fault;
unsigned char device_address_w;
unsigned char device_address_r;
unsigned char register_address;
unsigned char data_size;
unsigned char data_index;
} tI2C_com_ctr;
#if defined (TOWER)
#define I2C_MASTER_SDA_PIN_1 GPIOF_DR |= GPIOF_DR_D_3
#define I2C_MASTER_SDA_PIN_0 GPIOF_DR &= ~GPIOF_DR_D_3
#define I2C_MASTER_SCL_PIN_1 GPIOF_DR |= GPIOF_DR_D_2
#define I2C_MASTER_SCL_PIN_0 GPIOF_DR &= ~GPIOF_DR_D_2
#define I2C_MASTER_SDA_PIN_AS_IN GPIOF_DDR &= ~GPIOF_DDR_DD_3
#define I2C_MASTER_SDA_PIN_AS_OUT GPIOF_DDR |= GPIOF_DDR_DD_3
#define I2C_MASTER_SCL_PIN_AS_IN GPIOF_DDR &= ~GPIOF_DDR_DD_2
#define I2C_MASTER_SCL_PIN_AS_OUT GPIOF_DDR |= GPIOF_DDR_DD_2
#define I2C_MASTER_SDA_PIN_AS_GPIO GPIOF_PER &= ~GPIOF_PER_PE_2
#define I2C_MASTER_SCL_PIN_AS_GPIO GPIOF_PER &= ~GPIOF_PER_PE_3
#define I2C_MASTER_SDA_PIN_AS_I2C GPIOF_PER |= GPIOF_PER_PE_2
#define I2C_MASTER_SCL_PIN_AS_I2C GPIOF_PER |= GPIOF_PER_PE_3
#endif
#define I2C_START_SIGNAL (I2C_C1 |= I2C_C1_MST)
#define I2C_STOP_SIGNAL (I2C_C1 &= ~I2C_C1_MST)
#define I2C_REPEAT_START_SIGNAL (I2C_C1 |= I2C_C1_RSTA)
#define I2C_WRITE_BYTE(data) (I2C_D = data)
#define I2C_READ_BYTE (unsigned char)I2C_D
#define I2C_GET_IRQ_FLAG (I2C_S & I2C_S_IICIF)
#define I2C_CLEAR_IRQ_FLAG (I2C_S |= I2C_S_IICIF)
#define I2C_SET_RX_MODE (I2C_C1 &= ~I2C_C1_TX)
#define I2C_SET_TX_MODE (I2C_C1 |= I2C_C1_TX)
#define I2C_SET_NACK_MODE (I2C_C1 |= I2C_C1_TXAK)
#define I2C_CLEAR_NACK_MODE (I2C_C1 &= ~I2C_C1_TXAK)
extern void I2C_Isr(void);
extern tI2C_fault I2C_write_data(tI2C_com_ctr *psI2C_tr_ctrl, unsigned char *data);
extern tI2C_fault I2C_read_data(tI2C_com_ctr *psI2C_tr_ctrl, unsigned char *data);
void I2C_Init();
void I2C_DeInit();
tI2C_fault I2C_Restore();
void I2C_delay(void);
tI2C_fault I2C_isr_Callback (tI2C_com_ctr *psI2C_tr_ctrl,unsigned char *data);
#endif
#include "i2c.h"
unsigned char iic_data[0x80];
tI2C_com_ctr sI2C_com_ctr; 
void I2C_Init(void)
{
I2C_F = 0x27; // clock divider 56, I2C frequency: 80 kHz
I2C_C1 = I2C_C1_IICIE | I2C_C1_IICEN;
// enable interrupt in INTC module
INTC_IPR6 |= INTC_IPR6_IIC0_0 | INTC_IPR6_IIC0_1;
}
void I2C_DeInit(void)
{
I2C_C1 = 0;
}
void I2C_delay(void) // delay of 200 us @50MHz CPU clock (creates period of 5 kHz)
{
unsigned int cnt;
I2C Non-blocking Communication, Rev 0, 10/2013
18 Freescale Semiconductor, Inc.
for ( cnt = 0;cnt < 10000; cnt++)
{ asm(nop); };
}
tI2C_fault I2C_Restore(void)
{
unsigned char tmp = 0;
I2C_STOP_SIGNAL;
I2C_DeInit();
I2C_MASTER_SDA_PIN_AS_GPIO;
I2C_MASTER_SDA_PIN_GPIO_HIGH_DRIVE;
I2C_MASTER_SCL_PIN_AS_GPIO;
I2C_MASTER_SDA_PIN_AS_OUT;
I2C_MASTER_SDA_PIN_0;
I2C_MASTER_SCL_PIN_AS_OUT;
for(tmp = 0; tmp <9; tmp ++) // nine clock for data
{
I2C_MASTER_SCL_PIN_0;
I2C_delay();
I2C_MASTER_SCL_PIN_1;
I2C_delay();
}
I2C_MASTER_SCL_PIN_0;
I2C_MASTER_SDA_PIN_AS_OUT; //SDA pin set to output
I2C_MASTER_SDA_PIN_1; //negative acknowledge
I2C_delay();
I2C_MASTER_SCL_PIN_1;
I2C_delay();
I2C_MASTER_SCL_PIN_0;
I2C_delay();
I2C_MASTER_SDA_PIN_0; //stop
I2C_delay();
I2C_MASTER_SCL_PIN_1;
I2C_delay();
I2C_MASTER_SDA_PIN_AS_IN;
tmp = 0;
if (!((GPIOC_DR>>14) & 1))
{
while (!((GPIOC_DR>>14) & 1) && (tmp < 30))
{
I2C_MASTER_SCL_PIN_0;
I2C_delay();
I2C_MASTER_SCL_PIN_1;
I2C_delay();
tmp++;
};
if (tmp == 30)
return I2C_PERMANENT_BUS_FAULT; // giving up, permanent
I2C Non-blocking Communication, Rev 0, 10/2013
Freescale Semiconductor, Inc. 19
// error, reset required
I2C_MASTER_SCL_PIN_0;
I2C_MASTER_SDA_PIN_AS_OUT; //SDA pin set to output
I2C_MASTER_SDA_PIN_1; //negative acknowledge
I2C_delay();
I2C_MASTER_SCL_PIN_1;
I2C_delay();
I2C_MASTER_SCL_PIN_0;
I2C_delay();
I2C_MASTER_SDA_PIN_0; //stop
I2C_delay();
I2C_MASTER_SCL_PIN_1;
I2C_delay();
}
I2C_MASTER_SDA_PIN_AS_I2C;
I2C_MASTER_SCL_PIN_AS_I2C;
I2C_Init();
return I2C_NO_FAULT;
}
Conclusioni
L'argomento di oggi era, in effetti, davvero specifico e specialistico. Abbiamo cercato di scendere nel dettaglio e di andare a vedere gli aspetti fondamentali sia della programmazione sia del funzionamento del bus I2C per comprenderne al meglio problematiche ed aspetti salienti. Quello che abbiamo trovato è un metodo universale e versatile grazie al quale potremo rendere sempre più utile un bus di comunicazione il cui utilizzo è ormai giornaliero. La prossima volta, nella prossima puntata, ci occuperemo di operazioni matematiche e di applicazioni di misura mentre nell'ultima puntata di questo corso torneremo a mettere le mani sulla scheda, e sulla programmazione più specificatamente, per completare alcune nozioni utili a restituirvi un panorama completo di tutto ciò che dovete sapere. Come sempre, lo ricordiamo, i commenti a questi articoli sono il mezzo attraverso cui potete esprimere non soltanto apprezzamenti o critiche rispetto a quello che state leggendo ma anche fare proposte per quello che volete che venga trattato. Se avete argomenti, suggerimenti o qualunque altra idea da proporre, siamo qui per questo. Alla prossima.
Credits: l'immagine principale è una libera rielaborazione di questa.
 
                    
Per ulteriori informazioni potete scrivere a questa email: [email protected]
 
								
    		
    		    		
    		
    		
    		    		
    					
							



 
							
Una libreria da provare!
Un bell’articolo, molto interessante!
Grazie Piero, più vado avanti con questo corso e più diminuiscono i punti oscuri 🙂
Ciao
Mario
Beh, ti dirò: lo scopo, neanche tanto occulto, è proprio questo. Quindi sono molto felice di leggere questo! 😀
Vogliamo fare in modo che tutto sia più chiaro per tutti. È questo il senso di essere “open” 🙂
spero davvero di essere chiaro però come tutte le cose che chiunque decida di studiare, c’è necessità di pazienza, molta pazienza.
Bisogna leggere, rileggere, leggere una terza volta ed una quarta ancora.
Provare, sbattere la testa…
Noi abbiamo pensato ad un blog in cui si interagisce con gli autori proprio per minimizzare il “dolore” però nulla si IMPARA senza fatica! 🙂
E non crediate che per scrivere questi articoli non ci sia voluto MOLTO impegno e molto studio…! E nonostante questo, ci sono tante altre cose che andrebbero dette e che si possono approfondire proprio utilizzando i commenti! 😀
Sono contento che i tuoi dubbi si stiano diradando e proprio per questo motivo ancora di più, invito te e tutti gli altri ad usare i commenti! 🙂
Piero sei un grande! Grazie per queste nozioni!