Utilizzare le EEprom I2C da un microcontrollore PIC, con MPLAB X

Questo articolo vuole essere un aggiornamento della vecchia lezione sull’utilizzo delle EEprom I2C dai PIC, risalente a ben 10 anni fa, ma utilizzando il compilatore XC8 dall’ide MPLAB X più un aiuto dall’MPLAB Code Configurator.

In un articolo precedente vi ho già mostrato come poter leggere un accelerometro I2C utilizzando le librerie generate dal code configurator: faremo più o meno la stessa cosa, ma questa volta quelle librerie andranno modificate per poter essere utilizzate con una EEprom e vedremo tra un po’ il perchè.

Prima di continuare, quindi, per evitare di ripetere cose già dette, è assolutamente necessario rileggere gli articoli precedenti dal momento che spiegano i concetti di base, soprattutto relativamente all’ indirizzamento delle EEprom I2C che è la nota dolente quando si lavora con questi componenti, dal momento che sembrano tutte uguali ma invece non lo sono affatto. Le lezioni da ripetere sono quindi le seguenti:

Aggiornare le librerie dell’MCC 

Sottotitolo paragrafo: “…e provare ad utilizzare delle librerie che non funzionano, oppure che davvero non ho capito come vanno utilizzate

Abbiamo visto che, dal Code Configurator, in base al modello di microcontrollore utilizzato, è possibile generare automaticamente le librerie per utilizzare la comunicazione su bus I2C. Di librerie I2C generabili, purtroppo, ce ne sono più di una. Qui vi illustro il sistema che non sono stato capace di far funzionare in nessun modo e per il quale ho il sospetto che Microchip non abbia aggiornato alcune cose.

Apriamo il Code Configurator da un progetto qualsiasi, oppure creiamo un progetto apposta per un PIC che vogliamo utilizzare. Io nel mio esempio qui utilizzerò sempre la scheda PIC16F15376 Curiosity Nano: cogliamo l’occasione per aggiornare un po’ i vari componenti che abbiamo a disposizione.

Andrò a fare delle operazioni che però non utilizzeremo. Lo faccio semplicemente per due motivi in particolare:

  • Ricordarsi come si fa ad aggiornare le librerie a disposizione per la generazione automatica
  • Capire se qualcuno è riuscito a risolvere i vari problemi che elencherò, perchè nei forum tutti hanno problemi con questa roba e attualmente non trovo soluzioni e considerazioni migliori di quelle che ho fatto in questo articolo

Nei riquadri di sinistra del Code Configurator, in basso, c’è una finestra Versions. Da qui è possibile aggiornare il plugin del Code Configurator e delle Librerie che possono essere generate in automatico:

Espandendo Libraries > Microchip Technology, Inc. > Microcontrollers and Peripherals > PIC10 / PIC12 / PIC16 / PIC18 MCUs è possibile vedere quale versione delle librerie di base stiamo utilizzando, ed eventualmente aggiornarla:

Nel momento in cui scrivo, vedete che l’ultima è la 1.81.0. ma appaiono anche librerie più vecchie ed eventualmente quelle più aggiornate se avete consentito ad MPLAB X di accedere ad Internet. Per caricare la libreria più aggiornata basta fare doppio click sulla versione che volete utilizzare. Dopo aver dato il doppio click compare un pulsante Load Libraries, dopo averlo premuto, la libreria viene eventualmente scaricata automaticamente (se non l’avete già) e viene caricata per l’utilizzo.

Sullo stesso livello della cartella Microcontrollers and Peripherals c’è la cartella Software, espandiamola:

Ci sono due voci che potrebbero interessare. La prima è Foundation Services Library. Aggiornate eventualmente anche questa: si tratta di librerie di base. Le Foundation Libraries sono un pacchetto che contiene varie librerie di base, tra cui anche una libreria molto semplice per l’I2C, Può fare comodo averla anche se non la utilizzeremo. L’altra voce interessante è I2C EEPROM, attualmente è presente un’unica versione, la 1.00, e questo mi mette i il dubbio che non sia stata mai aggiornata.

Adesso andiamo al riquadro sopra questo: Device Resources

Nella cartella Libraries vediamo che c’è Foundation Services (espandendolo troviamo I2C Simple insieme ad altra roba) e Serial Memory, che contiene I2C EEPROM.

Bene, includendo I2C EEPROM nel nostro progetto (basta premere il pulsante verde +), compare una finestra che ci rassicura che tutto andrà per il meglio perchè ci fa settare un sacco di cose che ci piacciono relativamente alla EEprom che vogliamo utilizzare:

Partiamo innanzitutto dal fatto che in Select Device c’è davvero un sacco di roba: tanti modelli di EEprom che mi erano sconosciuti, tra cui quelli alla fine della interminabile lista con la dicitura MAC nella sigla che hanno la caratteristica di avere all’interno un codice univoco per l’identificazione. Tutto bello ma manca l’EEprom AT24CM02, l’unica che mi risulti sia da 2Mib (2 Megabits = 256kiB), eppure tale memoria esiste già da almeno 3 anni come si evince dalla revisione del datasheet che si trova sul sito Microchip (qui). Anche questa mancanza mi fa pensare ad un continuo mancato aggiornamento. Insomma settiamo tutto l’ambaradan scegliendo anche il modulo MSSP da utilizzare (il PIC che ho scelto ne ha 2) e poi bisogna cliccare per forza il checkbox Enable I2C EEPROM.

Se proviamo, a questo punto a generare le librerie, ci viene comunque il dubbio del come facciano a funzionare, dal momento che in questo riquadro non ci viene nemmeno chiesto il valore della velocità del bus I2C, giusto? Poi andando a vedere nel Pin Manager salta subito all’occhio che non sono presenti i due pin per la comunicazione, SDA e SCL. Ecco il primo inghippo: è decisamente chiaro a questo punto che questo modulo genera una libreria che deve per forza di cose appoggiarsi ad una libreria I2C: il fatto di scegliere il modulo, MSSP1 o MSSP2 serve appunto a generare dei nomi di funzioni che richiamano a loro volta le funzioni I2C dell’uno o dell’altro modulo e che devono essere state, quindi, per forza preventivate… Quindi… quale libreria I2C va utilizzata con questo modulo? A me viene spontaneo da subito pensare di utilizzare la I2CSIMPLE presente in Foundation Services, perchè dopo tutto questa roba si trova tutta nella stessa sottocartella Libraries di Device Resources…. Allora decido di includere anche questa:

Aggiungendo I2CSIMPLE, automaticamente in Project Resources viene anche aggiunto il modulo MSSP1:

Nella finestra dei settaggi di I2CSimple ci sono solo due impostazioni: una permette di selezionare il modulo MSSP da utilizzare (selezionando il 2, viene aggiornato anche il project resources) e l’altra è *sorpresa* la possibilità di generare automaticamente un esempio Select use case example to generate e in questa dropdown box c’è solo un esempio che potrebbe anche starci bene:  Communication with EEprom 24LC256.

A questo punto si solleva un altro dubbio: questo esempio viene mostrato perchè abbiamo caricato anche la libreria I2C EEPROM? La risposta è presto detta: NO. Questa scelta sarebbe presente comunque anche se non si seleziona I2C EEPROM.

Potete provare a rimuovere tutto (eliminando MSSP1 tra l’altro non scompare I2CSIMPLE, che non ha motivo di esistere senza il modulo MSSP… ma vabbè magari sta cosa la correggeranno) e mettere solo I2CSIMPLE: vedete che questo esempio è possibile generarlo lo stesso anche senza aggiungere I2C EEPROM al progetto: la confusione aumenta .

Ad ogni modo proviamo a lasciare tutto come abbiamo messo. Premiamo su MSSP1 giusto per vedere i suoi settaggi: di default a me ha messo Serial Protocol : I2CMode: Master e I2C Clock Frequency: 100kHz. Lascio tutto così e faccio generare il codice dal configurator.

A questo punto vediamo che nel progetto c’è un macello totale:

 

Tralasciando nella descrizione seguente l’estensione (c, h) e la cartella logica di appartenenza (Source Files, Header Files), e i file vari di configurazione di sistema e delle altre periferiche, abbiamo:

  • i2c1_masterexamples/i2c1_master_example che, come già visto nell’articolo sull’accelerometro, sono generati dalla semplice inclusione del modulo MSSP1
  • drivers/i2c_simple_master è invece generato dall’inclusione della libreria I2CSIMPLE e, in pratica, si appoggia ad i2c1_master, anzi, andando a confrontare i2c_simple_master.cexamples/i2c1_master_example.c possiamo renderci conto che sono quasi uguali! Ci sono le stesse funzioni ma organizzate in maniera diversa e i nomi delle funzioni simple non contengono l’indice del modulo: ecco allora dove sta la semplicità?!
  • examples/i2c_simple_example è il famoso esempio di utilizzo dell’EEprom 24LC512. Contiene in pratica una sola funzione I2CSIMPLE_example() che andrebbe richiamata nel main e che scrive un blocco di dati, lo rilegge e potrebbe riportarlo su seriale se uno sapesse a priori di dover includere anche l’UART nel progetto e ridirezionare stdio, dato che fa uso di printf. La scrittura su EEprom che realizza questo esempio viene eseguita in blocco (bulk write) ma senza specificare l’indirizzo da cui partire: si fa affidamento all’ultima locazione di EEprom utilizzata e che l’EEprom tiene in memoria. Insomma funzioni abbastanza approssimative, anche perchè non tengono conto del vero problema: le EEprom dalla 24C32 in poi hanno l’indirizzo delle celle di memoria a 16bit e io ho trovato problemi con la libreria generata dal modulo MSSP dal momento che tiene conto di registri ad 8bit.
  • I2C_EEPROMDrivers/i2c_eeprom_app questo è generato dall’inclusione della libreria I2C EEPROM e il file header di questa specie di libreria contiene le funzioni che si dovrebbero utilizzare con l’eeprom, tra cui un’utilissima funzione per la scrittura in blocco con la specifica dell’indirizzo da cui partire. MA: vediamo subito che non può funzionare perchè nel file C ci sono richiami di costanti non definite da nessuna parte: I2C1_MESSAGE_STATUS che probabilmente doveva essere dichiarato in un ipotetico file header i2c1.h dal  momento che l’header di tale libreria possiede questo #include, ma fatto sta che questo i2c1.h non viene generato e non ho idea a cosa faccia riferimento, dal momento che, come dicevo più in alto, questa libreria EEprom è chiaro che richiede una libreria I2C, ma non si capisce quale: le ho incluse tutte come avete visto ma non è nessuna di quelle a disposizione.

Problemi

Da tutto questo macello ho capito che: includere I2CSimple e I2C EEprom personalmente non mi serve a nulla perchè:

  • I2CSimple di simple pare che non abbia un bel nulla dal momento che si appoggia alla libreria di default creata dal modulo MSSP e usa le stesse funzioni
  • I2CSimple, così come I2CMaster generata da MSSP1, non tiene conto di indirizzi cella a 16bit: le funzioni create, difatti, accettano gli indirizzi in formato ad 8bit  e a nulla serve cambiare il tipo nel parametro e mettere ‘2’ (2 bytes) al posto di ‘1’ nelle funzioni di setBuffer (vedremo dopo). Questa cosa mi pare decisamente anomala dal momento che nella scelta di includere l’esempio si fa riferimento ad una eeprom di tipo 24LC256, che ha gli indirizzi di cella a 16bit. L’esempio sarebbe utilizzabile, in realtà, solo con eeprom fino alla 24C16 compresa, che hanno gli indirizzi ad 8bit.
  • I2C EEPROM a me pare bacata di default perchè già solo provando a compilare vengono generati errori tutti causati probabilmente dall’inclusione di un header che non viene generato. Si tratta probabilmente di una libreria mai aggiornata (cosa che io evinco dal fatto che ne è presente una sola versione e nella scelta delle memorie non compare la memoria da 2Mib, esistente già da 3 anni minimo). 

Quindi, dopo che abbiamo perso tempo, ma imparato a smanettare nel code configurator capendo anche quanto tempo ci perde Giovanni in queste cose per poter scrivere un articolo decente e tirare fuori un codice funzionante, possiamo procedere col sistema che ho fatto e che funziona.

Utilizzo della libreria MSSP1

Anche se alla fine di tutto metto il link col progetto già pronto e funzionante, cerchiamo di capire come ho risolto questo problema di utilizzare l’eeprom sfruttando soltanto le librerie incluse dal modulo MSSP1, senza includere null’altro.

Dopo aver settato tutte le cose di base (clock di sistema, modulo I2C come master e alla frequenza di 400kHz, e scelto i pin su cui deve essere direzionato l’I2C), i miei problemi principali erano soprattutto due:

  • Le funzioni andavano perfettamente con le eeprom più piccole di 32kib (ovvero fino alla 24C16 compresa) ma cominciavano ad impazzire usando eeprom più grandi (dalla 24C32 compresa a salire).
  • Non era possibile eseguire una scrittura bulk specificando l’indirizzo da cui partire

Problemi che, abbiamo visto, affliggono anche la libreria I2C Simple con il suo eventuale esempio. Tenendo conto che mi baso sul modulo i2c1_master_example già usato con successo con l’accelerometro, il primo problema sta nel come sono definite le funzioni di lettura e scrittura:

uint8_t I2C1_Read1ByteRegister(i2c1_address_t address, uint8_t reg)
void I2C1_Write1ByteRegister(i2c1_address_t address, uint8_t reg, uint8_t data)

Vediamo chiaramente che il parametro reg che punta al registro in cui scrivere/da cui leggere è ad 8 bit. Questo va bene per le eeprom “piccole” ma non per quelle grandi. E sinceramente, nel 2020, è piuttosto difficile trovare quelle piccole per cui bisogna modificare per utilizzare un indirizzo a 16bit: premetto che però poi le funzioni non andranno più bene per le eeprom con indirizzo ad 8bit.

Diamo un occhio alla funzione che esegue la lettura:

while(!I2C1_Open(address)); // sit here until we get the bus..
I2C1_SetDataCompleteCallback(rd1RegCompleteHandler,&returnValue);
I2C1_SetBuffer(&reg,1);
I2C1_SetAddressNackCallback(NULL,NULL); //NACK polling?
I2C1_MasterWrite();
while(I2C1_BUSY == I2C1_Close()); // sit here until finished.

Vediamo che, anche se si tratta di lettura, c’è il richiamo ad una funzione I2C1_MasterWrite(), questo perchè, come dicevo nell’articolo sull’accelerometro, queste librerie sono basate su una macchina a stati: l’operazione di scrittura richiamata, in realtà è quella per la scrittura dell’indirizzo da cui leggere: sappiamo infatti che per leggere dal bus I2C si richiama prima l’indirizzo del dispositivo (che viene scritto dalla funzione I2C1_Open) segue quindi la scrittura dell’indirizzo da cui leggere e infine viene la lettura. Le operazioni successive dopo la scrittura dell’indirizzo registro MasterWrite, vengono richiamate dalla macchina a stati e impostate mediante la funzione I2C1_SetDataCompleteCallback: è li che c’è l’operazione di lettura. Dopo aver capito questo in un primo momento ho fatto il furbo: ho cambiato il parametro del registro da 8 a 16 bit e impostato:

I2C1_SetBuffer(&reg,2);

Per indicare che il buffer era di due Bytes. Facendo così in tutte le funzioni sembrava che tutto filasse liscio… ma usando l’analizzatore logico mi sono accorto che in realtà l’indirizzo della cella veniva inviato sul bus al contrario: quando scrivevo un indirizzo 0x0002 veniva invece inviato come 0x0200 : ovvero la parte alta e quella bassa invertita. Ddato che avevo impostato così tutte le funzioni era chiaro che non avrei mai potuto notare l’errore se non avessi usato un analizzatore logico,almeno in fase iniziale: poi in un’applicazione reale senza i dovuti test sarebbe stato un pasticcio colossale. 

La cosa spontanea che mi è venuta da fare, la più rapida, è stata aggiungere una riga per invertire l’indirizzo di cella … certo forse è una mezza schifezza di soluzione ma funziona.

Il secondo problema era quello di eseguire una scrittura bulk specificando l’indirizzo: la lettura bulk con indirizzo c’è, la scrittura invece no perchè, in realtà, la scrittura bulk prevede che i primi due bytes da inviare (o il primo nel caso di eeprom più piccole) costituiscono l’indirizzo della cella da cui partire, ma questo per me è un po’ scomodo nel momento in cui ho un array già pronto da scrivere… Per fare questa cosa ho dovuto spulciare a fondo il codice generato e ne sono venuto fuori.

Ho praticamente preso i due moduli di esempio nella cartella examples creata dal modulo MSSP1, tirati fuori rinominandoli i2c1_eeprom e ho incluso delle funzioni per eseguire queste operazioni. Ho lasciato tutte le altre funzioni che già c’erano nel caso in cui sullo stesso bus voglia mettere un altro dispositivo che comunica con indirizzi a 8bit.

Esempio di lettura e scrittura EEprom I2C

Come ho già detto, l’esempio l’ho fatto per il PIC16F5376, e in particolare per la Curiosity Nano con su questo microcontrollore, ed esegue in sequenza queste operazioni, una per volta dopo la pressione del pulsante SW0 sulla scheda stessa:

  • Scrittura di due valori di esempio, 77 e 15, nelle locazioni 0 e 1
  • Lettura delle locazioni 0 e 1
  • Scrittura bulk di un array a partire dalla locazione 2
  • Lettura bulk a partire dalla locazione 2

Lo schema da realizzare è il seguente:

Le 3 linee di indirizzo e il pin di Write Protect sono messi a GND. Sulle linee del bus ci sono resistenze da 2.2KΩ, che sono consigliate dal datasheet per una frequenza di comunicazione a 400kHz. Sull’alimentazione dell’EEprom c’è il solito condensatore da 100nF. Nel progetto, se usate la Curiosity Nano, è impostata l’alimentazione a 5V dato che, ricordo, questa scheda ha il regolatore programmabile che consente di far funzionare il sistema a 3.3V o a 5V, e dato che l’eeprom usata funziona a 5V…

Le operazioni eseguite vengono elencate sulla seriale disponibile dalla porta USB e visualizzabili utilizzando un qualsiasi programma terminale (io in genere uso Coolterm di Roger Meier):

Di seguito metto le catture dell’analizzatore logico. Questa è la prima parte, ovvero la scrittura dei due valori nelle locazioni 0 e 1 dell’eeprom:

Vedete nella finestra del Bus Packet List la forma condensata della comunicazione I2C: viene inviato impulso di Start e l’indirizzo della EEprom che qui viene evidenziato nelle sue due parti: Control Code=A e Chip Select=0. L’eeprom l’ho difatti collegata con A0, A1 e A2 a GND, per cui il chip select è 0, mentre il control code è 0x0A (1010 in binario) che è quello delle EEprom, a Control Code+Chip Select segue il bit che indica lettura o scrittura.

In realtà le EEprom dalla 24C128 alla 24C512 comprese, non utilizzano A2 e al suo posto va messo sempre zero. Le 24C32 e la 24C64 invece lo utilizzano.

Segue quindi un’operazione di write con la quale specifico l’indirizzo in cui scrivere, evidenziato in due parti: Address High e Address Low, vedete che viene scritto correttamente! C’è infine il dato: 4D è il valore 77. La seconda riga è uguale e viene scritto 0F (15).

Questa è la successiva operazione di lettura di quelle due locazioni:

Ogni singola operazione ha due righe: nella prima riga c’è l’invio in scrittura dell’indirizzo che verrà utilizzato per l’operazione successiva, segue un nuovo start (start ripetuto) e l’operazione di lettura che restituisce il dato. L’operazione di lettura ovviamente prevede il nuovo invio dell’indirizzo dell’EEprom ma questa volta in lettura. Questo tipo di lettura, cioè di un solo dato, si chiama Random Read.

Seguono quindi i due esempi di scrittura e lettura bulk. La scrittura bulk sappiamo che si esegue semplicemente inviando l’indirizzo in scrittura e continuando a inviare dati fino a che non si da stop:

Vedete che invio l’indirizzo 2 e seguono i valori da scrivere che l’EEprom scriverà automaticamente uno dopo l’altro a partire dall’indirizzo 2. E’ importante però sapere che non tutte le EEprom gestiscono la scrittura/lettura bulk allo stesso modo. In particolare nel mio esempio sto usando una 24C512 che può gestire massimo 128Bytes per scrittura bulk.

L’esempio prosegue e termina con la lettura bulk:

Dopo aver inviato in scrittura l’indirizzo da cui cominciare a leggere, si esegue la lettura: per eseguire la lettura bulk il master fornisce un segnale ACK che dallo slave viene interpretato come “continua”. Quando il master da il NACK, l’eeprom si ferma e tiene in memoria l’ultimo indirizzo fornito.

Download

Il codice di esempio è disponibile sul mio Github:

Se questo articolo ti è piaciuto, condividilo su un social:
  • 19
  •  
  •  
  •  
  •  
  •  
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)