Memoria I2C esterna: EEPROM 24LC256 con ESP32
La memoria interna di un ESP32 (flash NVS) è comoda ma limitata in cicli di scrittura e condivisa con firmware e configurazioni di sistema. Quando serve memoria non volatile dedicata — log di eventi, contatori, configurazioni utente scritte spesso — una EEPROM I2C esterna è la soluzione classica, economica e robusta. In questo tutorial usiamo la popolare 24LC256 (256 Kbit = 32 KB).
Cosa ti serve
- Una dev board ESP32-S3
- Modulo o chip EEPROM 24LC256 (disponibile anche in breakout pronti)
- Resistenze di pull-up 4.7kΩ su SDA/SCL (spesso già presenti sui breakout)
Indirizzamento I2C
La 24LC256 ha tre pin di indirizzo (A0, A1, A2) che permettono di mettere fino a 8 EEPROM identiche sullo stesso bus, ciascuna con un indirizzo diverso (0x50-0x57) a seconda di come questi pin sono collegati a GND o VCC. Il pin WP (Write Protect), se collegato a VCC, blocca la scrittura: utile per proteggere dati critici, va collegato a GND per poter scrivere normalmente.
| Pin EEPROM | Funzione | Collegamento tipico |
|---|---|---|
| A0, A1, A2 | Bit di indirizzo | GND (indirizzo 0x50) |
| WP | Write Protect | GND (scrittura abilitata) |
| SDA, SCL | Bus I2C | Ai pin I2C della scheda |
| VCC, GND | Alimentazione | 3.3V, GND |
Il codice
#include <Arduino.h>
#include <Wire.h>
#define EEPROM_ADDR 0x50
#define SDA_PIN 8
#define SCL_PIN 9
void eepromWrite(uint16_t addr, uint8_t data) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write((addr >> 8) & 0xFF); // byte alto dell'indirizzo
Wire.write(addr & 0xFF); // byte basso dell'indirizzo
Wire.write(data);
Wire.endTransmission();
delay(5); // tempo di scrittura interno della EEPROM (write cycle time)
}
uint8_t eepromRead(uint16_t addr) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write((addr >> 8) & 0xFF);
Wire.write(addr & 0xFF);
Wire.endTransmission(false); // restart, non stop: necessario per la lettura sequenziale
Wire.requestFrom(EEPROM_ADDR, 1);
return Wire.available() ? Wire.read() : 0xFF;
}
void setup() {
Serial.begin(115200);
delay(200);
Wire.begin(SDA_PIN, SCL_PIN);
eepromWrite(0x0000, 42);
uint8_t value = eepromRead(0x0000);
Serial.printf("Valore letto dalla EEPROM: %d\n", value);
}
void loop() {}
Come funziona
- L’indirizzo di memoria interno alla EEPROM (16 bit, per i suoi 32KB) va inviato in due byte separati, alto e basso, dopo l’indirizzo I2C del chip
- Il
delay(5)dopo ogni scrittura è essenziale: la EEPROM impiega alcuni millisecondi per completare fisicamente la scrittura di un byte, e scrivere di nuovo prima che sia pronta viene ignorato silenziosamente - Per la lettura,
endTransmission(false)invia un “restart” invece di uno “stop”: dice al chip di restare pronto per la successiva richiesta di lettura sullo stesso indirizzo appena impostato
Scrittura a pagine: l’ottimizzazione che conta
La 24LC256 supporta la scrittura a pagine (64 byte alla volta in una singola transazione I2C, invece di un byte per volta): se devi scrivere blocchi di dati, usarla riduce drasticamente il tempo totale e i cicli di usura del chip (una EEPROM tipica sopporta circa 1 milione di cicli di scrittura per pagina). Per un firmware che salva pochi byte di configurazione ogni tanto, la scrittura byte-per-byte mostrata sopra è più che sufficiente.
Quando usare una EEPROM esterna invece della NVS interna
- NVS interna (flash): comoda, nessun componente aggiuntivo, ma condivide l’usura con partizioni di sistema e OTA
- EEPROM I2C esterna: dedicata, endurance nota e documentata, utile quando il firmware stesso viene aggiornato via OTA (la EEPROM esterna non viene toccata dagli aggiornamenti) o quando servono più dispositivi identici sullo stesso bus
