iot

Lo voglio ricordare a me stesso: come utilizzare le procedure I2C automaticamente create da MCC per agevolare la costruzione del programma.

Prima di tutto abilitare il bus I2C come ad esempio fatto nel post I2CDetect. Generando il codice tramite MCC si ottengono due files es: i2c1.h/i2c1.c; In questo post descrivo come utilizzare questi files.

L'inizializzazione del bus (I2C1_Initialize()) viene inserita dal tool all'interno della funzione SYSTEM_Initialize() che viene a sua volta messa nel loop main. Questa funzione imposta opportunamente i registri e l'IRQ per far funzionare il bus: in sostanza non mi devo preoccupare di inizializzare il bus in quanto lo fa MCC.

MCC crea poi delle funzioni per leggere e scrivere bytes su di un dispositivo I2C che non sono così facilmente comprensibili.

In i2c1.h si possono vedere due typedef che creano rispettivamente un tipo enumerativo e una struttura.

Schermata 2018 09 07 alle 17.13.57 

I2C1_MESSAGE_STATUS come suggerisce il nome indica lo stato della trasmissione in corso.

La seconda definizione di tipo:

Schermata 2018 09 07 alle 17.14.23

chiamata anche TRB è una struttura in cui vengono messe tutte le informazioni necessarie ad aprire e chiudere una transazione.

 

Scrittura di un singolo byte in una locazione di memoria.

Realizzo la funzione WriteRegister(addr, reg, val) utilizzando le funzioni disponibili nella libreria creata da MCC.

Schermata 2018 09 08 alle 15.50.21

I parametri della funzione sono l'indirizzo I2C dello slave, il numero del registro da scrivere e il dato. Per prima cosa si da un valore di partenza alla variabile status che tiene conto del punto in cui la transazione è arrivata. I2C1_MESSAGE_PENDING è il valore di partenza di qualsiasi transazione. Poi creo la variabile dei blocchi transazione (TRB). Infine creo l'array writeBuffer in cui inserisco i dati da inviare allo slave. Nel caso specifico sarà il numero del registro e il valore che voglio scriverci.

La funzione I2C1_MasterWriteTRBBuild() non fa altro che caricare la struttura TRB precedentemente creata con i valori necessari alla transazione. Alla funzione viene passato il puntatore alla struttura creata in precedenza (riga 9), il puntatore al buffer dei dati da inviare (riga 10), il numero dei byte da inviare (che in questo caso corrisponde al numero di elementi del buffer) e infine l'indirizzo dello slave.

A questo punto non resta che monitorare la variabile status durante l'inserimento nella coda di trasmissione della struttura precedentemente creata e riempita con i dati, cosa che viene fatta dalla funzione I2C1_MasterTRBInsert(). Quando status assume il valore I2C1_MESSAGE_COMPLETE è possibile uscire dal loop.

E' importante capire come utilizzare la funzione I2C_MasterTRBInsert(). Il primo parametro della funzione è il numero delle TRB da inviare. Nel caso specifico è il valore 1 in quanto si instaura una sola transazione, ma nel caso in cui si voglia inviare una sequenza di byte senza dover ogni volta aprire e chiudere un dialogo con lo slave tramite Start e Stop è possibile separare i byte trasmessi con Start ripetuti. In pratica se scrivo più registri adiacenti dello slave mando un solo Stop e separo le varie locazioni con Start ripetuti. Questa cosa è possibile creando un array writeTRB con più record e inserendo il numero di record come primo parametro della funzione I2C_MasterTRBInsert().

Chiamando la funzione in questo modo: WriteRegister(0x68, 0x6B, 0x00) si ottiene:

Schermata 2018 09 08 alle 10.21.11Il trattino giallo è l'ACK dello slave (attivo basso).  Modifico adesso la funzione in modo da inviare due byte dati nella stessa stransazione.

 

Scrittura di due byte in una singola transazione.

Modifico la funzione WriteRegister in modo da trasmettere due byte nella stessa TRB. Questo è il caso in cui voglio scivere un registro a 16 bit (la cosa la posso estendere anche a 32 bit).

Schermata 2018 09 08 alle 16.11.55

 

Lettura di un registro.

La lettura di un registro prevede la scrittura del valore del registro che si vuole leggere seguito da una sequenza di lettura.

Schermata 2018 09 08 alle 17.06.10

In figura mi rivolgo al dispositivo di indirizzo 0x68 e leggo il registro 0x75. Nella seconda sequenza il dispositivo viene indirizzato in lettura e risponde con il valore del proprio indirizzo 0x68 seguito da NACK (trattino rosso).

La sequenza sopra visualizzata viene effettuata dalla seguente funzione:

Schermata 2018 09 08 alle 18.03.03

A differenza delle due procedure write esposte precedentemente, in questa funzione la variabile readTRB è un array in quanto deve contenere due transazioni: quella per la scrittura e quella per la lettura. Le due strutture TRB vengono riempite nelle righe 117 e 118. Le due funzioni Write e Read servono a specificare nel TRB se si tratta di scrittura o lettura, questo lo fa impostando l'opportuno bit nell'indirizzo in fase di scrittura del TRB.

A questo punto è sufficiente chiamare la funzione TRBInsert passandogli il numero delle strutture TRB che deve inviare, a differenza di quanto visto prima questa volta sono due record.

 

Lettura di più byte in sequenza.

Voglio ad esempio leggere due byte di memoria da due registri contigui. Dal datasheet del dispositivo si ricava:

Schermata 2018 09 08 alle 18.29.31

 Si tratta nuovamente di inserire due transazioni TRB nell'array di lettura. Ma il TRB per la lettura dei dati ha una differenza rispetto alla funzione precedente:

Schermata 2018 09 08 alle 18.24.22

Il puntatore alla variabile pData passato alla funzione punta ad un array di nCount elementi che verrà riempito con i valori restituiti dallo slave. In tutto il resto la funzione è uguale a quella di lettura di un singolo registro. Questo è il risultato che si ottiene dalla funzione:

Schermata 2018 09 08 alle 18.23.03

Nella prima transazione viene indirizzato il dispositivo 0x68 in scrittura sul registro 0x41. La sequenza successiva di lettura legge due byte il primo è 0xFB (contenuto nel registro 0x41) il secondo 0xC0 (contenuto nel registro 0x42). La sequenza termina con NACK.