Le memorie seriali spiegate in modo pratico! In questo episodio del corso di elettronica digitale vediamo cosa sono le memorie seriali come EEPROM e Flash e come integrarle nei tuoi progetti di elettronica digitale. Scopri nel dettaglio la differenza tra memorie parallele e seriali, perché le i protocolli I2C e SPI sono essenziali per Arduino, ESP32 e Raspberry Pi, e come funzionano a livello hardware e software.
Analizziamo il funzionamento delle memorie 24C08B (I2C) e CAT25640 (SPI) per capire le differenze tra i due protocolli/bus.
Approfondiamo poi il bus I2C e vediamo l’effettiva connessione fisica, le necessarie resistenze di pull-up e la gestione delle linee open drain/open collector sul bus. Utilizziamo gli sketch Arduino per dimostrare come leggere e scrivere byte e pagine, come gestire la selezione degli indirizzi e come affrontare le differenze di documentazione tra i produttori. Il video è pensato per chi sta sviluppando sistemi embedded, chi lavora con progetti retrocomputing, o chi vuole capire a fondo i dettagli dei protocolli I2C/SPI/Microwire direttamente sulla breadboard.
Codice mem_24c08_byte_rw.ino
/************************************************************
Lettura e scrittura di una memoria seriale 24C08
Codice mostrato nel video YouTube:
https://youtu.be/glFaGDb5_NE
Alexa Academy
Paolo Godino
@ 2025
*************************************************************/
#include "Arduino.h"
#define MEM_IO1 22
#define MEM_IO2 24
#define MEM_IO3 26
#define MEM_IO4 28
#define MEM_IO5 30
#define MEM_IO6 32
#define MEM_IO7 34
#define MEM_IO8 36
#define MEM_A0 23
#define MEM_A1 25
#define MEM_A2 27
#define MEM_A3 29
#define MEM_A4 31
#define MEM_A5 33
#define MEM_A6 35
#define MEM_A7 37
#define MEM_A8 39
#define MEM_A9 41
#define MEM_A10 43
#define MEM_A11 45
#define MEM_A12 47
#define MEM_A13 49
#define MEM_A14 51
#define MEM_WE 50
#define MEM_OE 46
#define MEM_CS 48
#define CMD_READ "r"
#define CMD_WRITE "w"
char buffer[1000]= {0}; // Buffer utilizzato per sprintf per visualizzare su seriale il contenuto della memoria
char buffer_ascii[1000]= {0}; // Buffer utilizzato per sprintf per visualizzare su seriale il contenuto della memoria nella parte ASCII
char tmp_buf[100] = {0};
#define READ_CHUNK_SIZE 300 // Dimensione pacchetto per l'invio del contenuto della memoria nella lettura binaria
char message[READ_CHUNK_SIZE];
bool isWriting = false;
unsigned int current_add = 0;
unsigned int final_add = 0;
byte data_read;
#define COMMANDSIZE 32
char cmdbuf[COMMANDSIZE];
void setDatabusOut(bool isOut) {
if (isOut) {
pinMode(MEM_IO1, OUTPUT);
pinMode(MEM_IO2, OUTPUT);
pinMode(MEM_IO3, OUTPUT);
pinMode(MEM_IO4, OUTPUT);
pinMode(MEM_IO5, OUTPUT);
pinMode(MEM_IO6, OUTPUT);
pinMode(MEM_IO7, OUTPUT);
pinMode(MEM_IO8, OUTPUT);
} else {
pinMode(MEM_IO1, INPUT);
pinMode(MEM_IO2, INPUT);
pinMode(MEM_IO3, INPUT);
pinMode(MEM_IO4, INPUT);
pinMode(MEM_IO5, INPUT);
pinMode(MEM_IO6, INPUT);
pinMode(MEM_IO7, INPUT);
pinMode(MEM_IO8, INPUT);
}
}
byte readData() {
byte d7 = digitalRead(MEM_IO8);
byte d6 = digitalRead(MEM_IO7);
byte d5 = digitalRead(MEM_IO6);
byte d4 = digitalRead(MEM_IO5);
byte d3 = digitalRead(MEM_IO4);
byte d2 = digitalRead(MEM_IO3);
byte d1 = digitalRead(MEM_IO2);
byte d0 = digitalRead(MEM_IO1);
byte data_bus = d7<<7 | d6<<6 | d5<<5 | d4<<4 | d3<<3 | d2<<2 | d1<<1 | d0;
return data_bus;
}
void writeData(byte b) {
digitalWrite(MEM_IO1, bitRead(b, 0));
digitalWrite(MEM_IO2, bitRead(b, 1));
digitalWrite(MEM_IO3, bitRead(b, 2));
digitalWrite(MEM_IO4, bitRead(b, 3));
digitalWrite(MEM_IO5, bitRead(b, 4));
digitalWrite(MEM_IO6, bitRead(b, 5));
digitalWrite(MEM_IO7, bitRead(b, 6));
digitalWrite(MEM_IO8, bitRead(b, 7));
}
void writeAddress(word w) {
digitalWrite(MEM_A0, bitRead(w, 0));
digitalWrite(MEM_A1, bitRead(w, 1));
digitalWrite(MEM_A2, bitRead(w, 2));
digitalWrite(MEM_A3, bitRead(w, 3));
digitalWrite(MEM_A4, bitRead(w, 4));
digitalWrite(MEM_A5, bitRead(w, 5));
digitalWrite(MEM_A6, bitRead(w, 6));
digitalWrite(MEM_A7, bitRead(w, 7));
digitalWrite(MEM_A8, bitRead(w, 8));
digitalWrite(MEM_A9, bitRead(w, 9));
digitalWrite(MEM_A10, bitRead(w, 10));
digitalWrite(MEM_A11, bitRead(w, 11));
digitalWrite(MEM_A12, bitRead(w, 12));
digitalWrite(MEM_A13, bitRead(w, 13));
digitalWrite(MEM_A14, bitRead(w, 14));
}
// Utilizziamo il Write Cycle 1
void writeCycle(byte data, word address) {
writeAddress(address);
delayMicroseconds(3);
digitalWrite(MEM_OE, 1);
delayMicroseconds(3);
digitalWrite(MEM_CS, 0);
delayMicroseconds(3);
setDatabusOut(true);
writeData(data);
delayMicroseconds(3);
digitalWrite(MEM_WE, 0);
delayMicroseconds(1);
digitalWrite(MEM_WE, 1);
delayMicroseconds(3);
setDatabusOut(false);
delayMicroseconds(3);
digitalWrite(MEM_CS, 1);
delay(6);
}
// Utilizziamo il Read Cycle 1
byte readCycle(word address) {
writeAddress(address);
digitalWrite(MEM_CS, 0);
delayMicroseconds(3);
digitalWrite(MEM_OE, 0);
delayMicroseconds(3);
byte data = readData();
digitalWrite(MEM_OE, 1);
delayMicroseconds(3);
digitalWrite(MEM_CS, 1);
delayMicroseconds(3);
return data;
}
// Utilizziamo il Read Cycle 2
void continuousReadCycle(word address, byte* data, unsigned int size) {
writeAddress(address);
delayMicroseconds(1);
digitalWrite(MEM_CS, 0);
digitalWrite(MEM_OE, 0);
for (int idx = 0; idx < size; ++idx) {
writeAddress(address + idx);
delayMicroseconds(1);
data[idx] = readData();
}
digitalWrite(MEM_OE, 1);
digitalWrite(MEM_CS, 1);
delayMicroseconds(1);
}
void setup() {
Serial.begin(19200);
setDatabusOut(false);
pinMode(MEM_A0, OUTPUT);
pinMode(MEM_A1, OUTPUT);
pinMode(MEM_A2, OUTPUT);
pinMode(MEM_A3, OUTPUT);
pinMode(MEM_A4, OUTPUT);
pinMode(MEM_A5, OUTPUT);
pinMode(MEM_A6, OUTPUT);
pinMode(MEM_A7, OUTPUT);
pinMode(MEM_A8, OUTPUT);
pinMode(MEM_A9, OUTPUT);
pinMode(MEM_A10, OUTPUT);
pinMode(MEM_A11, OUTPUT);
pinMode(MEM_A12, OUTPUT);
pinMode(MEM_A13, OUTPUT);
pinMode(MEM_A14, OUTPUT);
pinMode(MEM_WE, OUTPUT);
pinMode(MEM_OE, OUTPUT);
pinMode(MEM_CS, OUTPUT);
digitalWrite(MEM_WE, 1);
digitalWrite(MEM_OE, 1);
digitalWrite(MEM_CS, 1);
}
void read_mem(unsigned int start, unsigned int len) {
unsigned int cur_add = start;
do {
sprintf(buffer, "%04X: ", cur_add);
sprintf(buffer_ascii, "");
byte data[16];
continuousReadCycle(cur_add, data, 16);
for (int i=0; i<16; ++i) {
if (cur_add < start+len) {
data_read = data[i];
cur_add++;
sprintf(tmp_buf, " %02X", data_read);
} else {
sprintf(tmp_buf, " ");
}
strcat(buffer, tmp_buf);
if (i == 7) {
strcat(buffer, " ");
}
if(isprint(data_read)) {
sprintf(tmp_buf, "%c", data_read);
strcat(buffer_ascii, tmp_buf);
} else {
strcat(buffer_ascii, ".");
}
}
strcat(buffer, " ");
strcat(buffer, buffer_ascii);
Serial.println(buffer);
} while (cur_add < start+len);
}
// Legge il comando da seriale
void readCommand() {
// Cancella il buffer
for(int i=0; i< COMMANDSIZE;i++) cmdbuf[i] = 0;
char c = ' ';
int idx = 0;
// Legge dalla seriale fino a che si trova un fine riga oppure il buffer è pieno
do {
if(Serial.available()) {
c = Serial.read();
cmdbuf[idx++] = c;
}
}
while (c != '\n' && c != '\r' && idx < (COMMANDSIZE)); //save the last '\0' for string end
// Cambia l'ultimo newline in '\0' per ottenere una stringa terminata con NULL
cmdbuf[idx - 1] = 0;
Serial.println("");
}
void loop() {
if (isWriting) {
readCommand();
// Fine scrittura
if (strcmp(cmdbuf, "") == 0) {
isWriting = false;
Serial.println("OK");
} else {
byte data_wr = strtol(cmdbuf, NULL, 16);
Serial.println(data_wr, HEX);
writeCycle(data_wr, current_add++);
}
} else {
readCommand();
byte index = 0;
char *strings[6];
char *ptr = NULL;
ptr = strtok(cmdbuf, " ");
while (ptr != NULL) {
strings[index++] = ptr;
ptr = strtok(NULL, " ");
}
if (index > 0) {
if (strcmp(strings[0], CMD_READ) == 0) {
unsigned int startAdd = 0;
unsigned int len = 16;
if (index > 1) startAdd = strtol(strings[1], NULL, 16);
if (index > 2) len = strtol(strings[2], NULL, 10);
read_mem(startAdd, len);
} else if (strcmp(strings[0], CMD_WRITE) == 0) {
unsigned int startAdd = 0;
if (index > 1) startAdd = strtol(strings[1], NULL, 16);
current_add = startAdd;
isWriting = true;
} else {
Serial.println("WRONG COMMAND");
}
} else {
Serial.println("WRONG COMMAND");
}
}
}
Codice mem_24c08_read_32.ino
/************************************************************
Lettura di 32 byte di una memoria seriale 24C08
Codice mostrato nel video YouTube:
https://youtu.be/glFaGDb5_NE
Alexa Academy
Paolo Godino
@ 2025
*************************************************************/
#include "Wire.h"
#define EEPROM_I2C_ADDRESS 0x50
// Hex+ASCII dump
void printHexAscii(const uint8_t start_add, const uint8_t *b, int len) {
for (int off=0; off<len; off+=16) {
Serial.print(F("0x"));
uint8_t add = start_add + off;
if(add<0x1000) Serial.print('0');
if(add<0x100) Serial.print('0');
if(add<0x10) Serial.print('0');
Serial.print(add,HEX);
Serial.print(F(": "));
for (int i=0;i<16;i++){
if (off+i<len){
if (b[off+i]<16) Serial.print('0');
Serial.print(b[off+i],HEX);
Serial.print(' ');
} else {
Serial.print(F(" "));
}
}
Serial.print(F(" | "));
for(int i=0;i<16 && off+i<len;i++) {
char c = (b[off+i]>=32 && b[off+i]<127) ? char(b[off+i]) : '.';
Serial.print(c);
}
Serial.println();
}
}
void read_mem_block(uint8_t* data, uint8_t add, int block) {
Wire.beginTransmission(EEPROM_I2C_ADDRESS+block); // Viene iniziata una scrittura "finta" per impostare l'indirizzo
Wire.write(add);
Wire.endTransmission();
Wire.requestFrom(EEPROM_I2C_ADDRESS+block, 32);
int i=0;
while (Wire.available() && i < 256) {
uint8_t value = Wire.read();
data[i++] = value;
}
}
void setup() {
Serial.begin(9600);
while (!Serial) {
// wait for Serial to become active
}
Wire.begin();
delay(5);
uint8_t buffer[32];
read_mem_block(buffer, 0x0, 0);
printHexAscii(0x0, buffer, 32);
}
void loop() {
}
Codice mem_24c08_write_8.ino
/************************************************************
Scrittura multibyte di una memoria seriale 24C08
Codice mostrato nel video YouTube:
https://youtu.be/glFaGDb5_NE
Alexa Academy
Paolo Godino
@ 2025
*************************************************************/
#include "Wire.h"
#define EEPROM_I2C_ADDRESS 0x50
void write_8byte(uint8_t* data, uint8_t add, int block) {
Wire.beginTransmission(EEPROM_I2C_ADDRESS+block);
Wire.write(add); // indirizzo interno iniziale
for (byte i = 0; i < 8; i++) {
Wire.write(data[i]); // scrive 8 byte consecutivi
}
Wire.endTransmission();
delay(10); // attesa per tWR (tempo di scrittura interno)
}
void setup() {
Serial.begin(9600);
while (!Serial) {
// wait for Serial to become active
}
Wire.begin();
delay(5);
uint8_t buffer[8] = {0x2a, 0x02, 0xaa, 0xba, 0x07, 0xfe, 0x12, 0xaa};
write_8byte(buffer, 0x00, 0);
Serial.println("Scrittura terminata");
}
void loop() {
}
