Memoria I2C esterna: EEPROM 24LC256 con ESP32

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

Imprenditore, maker, MBSE enthusiast e co-autore di Web App Portal. Collegati su LinkedIn.