Introduzione
I protocolli di comunicazione sono il fondamento dei sistemi embedded. Che tu stia leggendo un sensore, comunicando con una scheda SD o stampando messaggi di debug, stai utilizzando uno dei tre principali protocolli: Serial (UART), I2C (TWI) o SPI. In questo tutorial, eseguiremo tutti e tre simultaneamente su un Arduino Uno utilizzando il simulatore in-browser Velxio. Vedrai come scansionare il bus I2C, leggere/scrivere una EEPROM, eseguire trasferimenti SPI e registrare tutto tramite Serial.

Analisi del Circuito
Questo esempio utilizza solo la scheda Arduino Uno stessa — non sono necessari componenti esterni. Le periferiche integrate di Arduino forniscono tutto ciò di cui abbiamo bisogno:
- Serial (UART): Utilizza i pin 0 (RX) e 1 (TX) per la comunicazione con il Monitor Seriale.
- I2C (TWI): Utilizza i pin A4 (SDA) e A5 (SCL). Le resistenze di pull‑up interne sono abilitate dalla libreria Wire.
- SPI: Utilizza i pin 10 (SS), 11 (MOSI), 12 (MISO) e 13 (SCK). Il pin 10 è configurato come uscita slave select.
Non è necessario alcun cablaggio perché tutte le connessioni sono interne ad Arduino. Il canvas di Velxio mostra l’Uno con le etichette dei pin e puoi ispezionare i componenti virtuali aprendo il selettore dei componenti.

Analisi del Codice
Analizziamo lo sketch passo dopo passo.
Librerie e Definizioni
#include <Wire.h>
#include <SPI.h>
#define DS1307_ADDR 0x68
#define EEPROM_ADDR 0x50
#define SS_PIN 10
Includiamo la libreria Wire per I2C e la libreria SPI per SPI. Sono definiti due indirizzi I2C: 0x68 per un RTC DS1307 (anche se non fisicamente presente, lo scanneremo) e 0x50 per una EEPROM 24Cxx. Il pin slave select SPI è impostato sul pin digitale 10.
Funzioni di Supporto
byte bcdToDec(byte val) {
return ((val >> 4) * 10) + (val & 0x0F);
}
void readRTC(byte &hr, byte &min, byte &sec) {
Wire.beginTransmission(DS1307_ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(DS1307_ADDR, 3);
sec = bcdToDec(Wire.read() & 0x7F);
min = bcdToDec(Wire.read());
hr = bcdToDec(Wire.read() & 0x3F);
}
La funzione bcdToDec converte il codice binario decimale (BCD) in decimale. readRTC legge secondi, minuti e ore da un RTC DS1307. I mascheramenti & 0x7F e & 0x3F cancellano i bit inutilizzati.
void writeEEPROM(byte reg, byte value) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
delay(5);
}
byte readEEPROM(byte reg) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(EEPROM_ADDR, 1);
return Wire.available() ? Wire.read() : 0xFF;
}
Queste funzioni scrivono e leggono un singolo byte da una EEPROM I2C all’indirizzo 0x50. Il delay(5) garantisce che la EEPROM abbia il tempo di completare il ciclo di scrittura.
byte spiTransfer(byte data) {
digitalWrite(SS_PIN, LOW);
byte result = SPI.transfer(data);
digitalWrite(SS_PIN, HIGH);
return result;
}
Questa funzione esegue un trasferimento SPI full‑duplex: porta SS a LOW, invia un byte, riceve la risposta e riporta SS a HIGH.
Setup
void setup() {
Serial.begin(9600);
Wire.begin();
pinMode(SS_PIN, OUTPUT);
digitalWrite(SS_PIN, HIGH);
SPI.begin();
Serial.println("===================================");
Serial.println(" Multi-Protocol Demo");
Serial.println(" Serial (USART) + I2C (TWI) + SPI");
Serial.println("===================================");
Serial.println();
// ── I2C: Scan bus ──
Serial.println("[I2C] Scanning bus...");
int found = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print(" Found device at 0x");
if (addr < 16) Serial.print("0");
Serial.println(addr, HEX);
found++;
}
}
Serial.print(" ");
Serial.print(found);
Serial.println(" device(s) on I2C bus.");
Serial.println();
// ── I2C: Write/read EEPROM ──
Serial.println("[I2C] EEPROM write/read test:");
writeEEPROM(0, 42);
writeEEPROM(1, 99);
byte v0 = readEEPROM(0);
byte v1 = readEEPROM(1);
Serial.print(" Wrote 42, read ");
Serial.print(v0);
Serial.println(v0 == 42 ? " [OK]" : " [FAIL]");
Serial.print(" Wrote 99, read ");
Serial.print(v1);
Serial.println(v1 == 99 ? " [OK]" : " [FAIL]");
Serial.println();
// ── SPI: Transfer test ──
Serial.println("[SPI] Transfer test:");
byte spiData[] = {0xAA, 0x55, 0x42};
for (int i = 0; i < 3; i++) {
byte rx = spiTransfer(spiData[i]);
Serial.print(" TX=0x");
if (spiData[i] < 16) Serial.print("0");
Serial.print(spiData[i], HEX);
Serial.print(" RX=0x");
if (rx < 16) Serial.print("0");
Serial.println(rx, HEX);
}
Serial.println();
Serial.println("Setup complete. Reading RTC...");
Serial.println();
}
In setup(), inizializziamo tutti e tre i protocolli. Poi:
- Scansioniamo il bus I2C per dispositivi dagli indirizzi 1 a 126.
- Testiamo la EEPROM scrivendo i valori 42 e 99 agli indirizzi 0 e 1, poi leggendoli e verificandoli.
- Testiamo SPI inviando tre byte (
0xAA,0x55,0x42) e stampando i byte ricevuti.
Loop
void loop() {
// ── Serial: Print RTC time every 2 seconds ──
byte hr, min, sec;
readRTC(hr, min, sec);
Serial.print("[RTC] ");
if (hr < 10) Serial.print("0");
Serial.print(hr);
Serial.print(":");
if (min < 10) Serial.print("0");
Serial.print(min);
Serial.print(":");
if (sec < 10) Serial.print("0");
Serial.print(sec);
Serial.print(" | Uptime: ");
Serial.print(millis() / 1000);
Serial.println("s");
delay(2000);
}
Ogni due secondi, il loop legge l’ora dall’RTC e la stampa insieme al tempo di attività del sistema. Questo dimostra l’output Serial continuo mentre I2C e SPI sono inattivi.
Concetti Chiave
- Serial (UART): Asincrono, punto‑a‑punto. Utilizzato per output di debug e comunicazione con PC o moduli come GPS.
- I2C: Sincrono, multi‑master, utilizza due fili (SDA, SCL). Ogni dispositivo ha un indirizzo univoco. Ottimo per sensori e EEPROM.
- SPI: Sincrono, full‑duplex, utilizza quattro fili (MOSI, MISO, SCK, SS). Più veloce di I2C ma richiede più pin.
- Scansione del Bus: Iterare attraverso gli indirizzi I2C per scoprire i dispositivi collegati.
- Lettura/Scrittura EEPROM: Memoria non volatile che conserva i dati dopo la perdita di alimentazione.
- Trasferimento SPI: Invio e ricezione simultanei di dati.
Insidie Comuni e Suggerimenti per il Debug
- Resistenze di Pull‑up I2C: La libreria Wire abilita le pull‑up interne, ma per bus più lunghi si consigliano resistenze esterne (4,7kΩ).
- Pin SS SPI: Imposta sempre SS come output e mantienilo HIGH quando non in uso per evitare di entrare accidentalmente in modalità slave.
- Temporizzazione di Scrittura EEPROM: Dopo una scrittura, attendi almeno 5 ms prima dell’operazione successiva.
- Baud Rate del Monitor Seriale: Assicurati che il Monitor Seriale sia impostato a 9600 baud.
- Nessun Dispositivo Esterno: In questa simulazione, la scansione I2C non troverà dispositivi (tranne eventualmente il TWI interno di Arduino). Il test EEPROM utilizza una EEPROM virtuale integrata nel simulatore.
Estensioni Suggerite
- Aggiungi Sensori Reali: Collega un RTC DS1307 e una EEPROM 24Cxx al bus I2C, e un modulo scheda SD tramite SPI.
- Registra Dati sulla EEPROM: Memorizza letture dei sensori e recuperale dopo un reset.
- Usa Interrupt: Attiva un trasferimento SPI alla pressione di un pulsante.
- Implementa un Interfaccia a Comandi: Analizza comandi Serial per controllare le operazioni I2C e SPI.
Provalo Tu Stesso
Pronto a vedere tutti e tre i protocolli in azione? Apri l’esempio live in Velxio, compila ed esegui. Osserva l’output del Monitor Seriale mentre Arduino scansiona il bus I2C, testa la EEPROM, esegue trasferimenti SPI e poi stampa continuamente l’ora dell’RTC.
Avvia la Demo Multi-Protocollo su Velxio

Buon hacking!