L’I2C è il bus che io scelgo quando devo collegare più sensori, display o moduli a un Arduino senza riempire la scheda di cavi. Con due sole linee condivise, SDA e SCL, posso far dialogare dispositivi diversi, ma solo se cablaggio, indirizzi e pull-up sono corretti. Qui trovi una guida pratica: come funziona davvero, come si collega, come si scrive il codice con Wire e quali errori fanno perdere più tempo.
Le cose che contano davvero prima di accendere il bus
- SDA e SCL sono condivise, ma ogni periferica deve avere un indirizzo univoco.
- La libreria Wire è già disponibile nei pacchetti delle board Arduino.
- Su molti modelli classici, come UNO e Nano, le linee I2C corrispondono ad A4 e A5.
- Io parto quasi sempre da 100 kHz e salgo solo se il bus è pulito.
- Se qualcosa non funziona, controllo prima massa comune, tensioni e pull-up.
Perché I2C resta la scelta più pratica su Arduino
Quando progetto un circuito semplice ma già un po’ affollato, I2C mi fa risparmiare pin, spazio e confusione. Nella documentazione tecnica di NXP, il bus viene descritto come uno standard de facto usato in migliaia di circuiti integrati: è proprio questa diffusione a renderlo comodo nei progetti Arduino, perché sensori, EEPROM, RTC, controller per display e expander di I/O parlano spesso tutti lo stesso linguaggio.Il vantaggio principale è immediato: due fili per molti dispositivi. Non è solo una questione di cablaggio più ordinato, ma di architettura. Se devo leggere temperatura, tempo, stato dei pulsanti e pilotare un piccolo display, spesso preferisco un bus I2C ben fatto a una rete di collegamenti punto-punto più rumorosa e più difficile da mantenere.
Dal lato delle prestazioni, il protocollo standard lavora fino a 100 kbit/s in Standard-mode e 400 kbit/s in Fast-mode; lo standard prevede anche modalità più spinte, ma nel mondo Arduino io considero realistici soprattutto i primi due livelli. In pratica, per sensori e periferiche lente o medie I2C è perfetto; quando il flusso dati cresce davvero, conviene valutare altro. Il punto, però, è capire prima come funziona il bus, perché lì si nasconde quasi sempre la differenza tra un progetto che parte subito e uno che sembra stregato.
Come funziona il bus senza complicarsi la vita
Su I2C non parla “tutto con tutto” nello stesso momento. C’è un dispositivo che controlla la comunicazione e uno o più dispositivi periferici che rispondono. Io preferisco chiamarli controller e periferiche, perché la terminologia è più chiara e non crea confusione con vecchie abitudini da master/slave.
Ogni periferica ha un indirizzo, di solito a 7 bit, e il controller seleziona di volta in volta con chi comunicare. La sequenza tipica è semplice: start, indirizzo, eventuale registrazione del dato da leggere o scrivere, trasferimento dei byte e stop. Dopo ogni byte la controparte risponde con un ACK o un NACK, cioè conferma o rifiuto della ricezione. Questo dettaglio è importante: se l’indirizzo è sbagliato o il dispositivo non è pronto, il bus non “immagina” cosa intendi, semplicemente non risponde come dovrebbe.
Un altro aspetto che molti sottovalutano è il comportamento elettrico delle linee. SDA e SCL non vengono forzate alte attivamente: il livello alto arriva tramite i pull-up, mentre i dispositivi tirano la linea verso il basso quando devono segnalare uno zero. È una scelta elegante, ma rende il bus sensibile alla capacità complessiva della rete. Nella specifica NXP, il bus standard tiene conto di una capacità massima di bus fino a 400 pF; se i cavi si allungano, i moduli aumentano e il cablaggio diventa disordinato, il margine si riduce in fretta.
In sintesi, I2C è semplice a livello concettuale, ma chiede disciplina elettrica. Ed è proprio lì che entra la parte pratica del cablaggio, che vale spesso più di cento righe di codice.

Collegare i dispositivi nel modo giusto
Qui si gioca gran parte della riuscita. Io controllo sempre tre cose prima ancora di aprire l’IDE: pin corretti, massa comune e tensioni compatibili. Su molte board classiche Arduino, come UNO e Nano, I2C passa sui pin analogici A4 e A5; su schede più recenti spesso trovi serigrafie dedicate SDA e SCL, ma il principio resta lo stesso: il pinout non va mai dato per scontato, va letto sul modello esatto che hai in mano.
| Controllo | Cosa verifico | Perché conta |
|---|---|---|
| Pin SDA e SCL | Che corrispondano alla scheda specifica | Un pin sbagliato basta a bloccare tutto |
| Massa comune | Tutti i moduli condividono GND | Senza riferimento comune i livelli logici non sono affidabili |
| Tensione logica | 5 V o 3,3 V del modulo e della scheda | Evita letture errate e possibili danni |
| Pull-up | Che esistano davvero e non siano eccessivi | Il bus deve risalire in modo pulito |
| Lunghezza cavi | Tratti brevi, ordinati e senza inutili diramazioni | Riduce rumore e capacità del bus |
Il punto più delicato, nella pratica, è la compatibilità tra 5 V e 3,3 V. Molti sensori moderni lavorano a 3,3 V, mentre parecchie schede Arduino classiche ragionano a 5 V. Se il modulo non è dichiarato tollerante ai 5 V, io non improvviso: uso un level shifter o scelgo una board coerente con la logica del sensore. È una piccola precauzione che evita ore di diagnostica e, nei casi peggiori, componenti bruciati.
Un altro errore comune è dare per scontato che i pull-up siano già presenti. Molti moduli li integrano, ma non sempre in modo adatto a un bus con più dispositivi. Se il segnale sale troppo lentamente, il bus diventa instabile soprattutto quando alzo la frequenza. Da qui il passaggio naturale al codice: se il collegamento è corretto, Wire di solito fa il resto senza drammi.
Scrivere il primo scambio con Wire
La libreria Wire è il livello di accesso che Arduino usa per parlare in I2C, e nella documentazione Arduino è già inclusa nei pacchetti delle board, quindi non devo installarla a mano. Per me questo è il punto in cui il bus smette di essere teoria e diventa davvero operativo: pochi metodi, abbastanza chiari, ma da usare con un ordine preciso.
Su una scheda che fa da controller, il flusso base è questo: inizializzazione, apertura della trasmissione verso l’indirizzo della periferica, scrittura dei byte, chiusura della trasmissione e, se serve, richiesta dei dati in ritorno. Io parto quasi sempre da una frequenza prudente e lascio che il bus dimostri di essere stabile prima di spingerlo oltre.
#include
constexpr byte SENSOR_ADDR = 0x28;
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(100000);
}
void loop() {
Wire.beginTransmission(SENSOR_ADDR);
Wire.write(0x01); // registro da leggere o comando da inviare
byte err = Wire.endTransmission();
if (err == 0) {
Wire.requestFrom(SENSOR_ADDR, (byte)2);
if (Wire.available() == 2) {
int value = (Wire.read() << 8) | Wire.read();
Serial.println(value);
}
}
delay(500);
}
Nel dispositivo periferico il pattern cambia poco, ma cambia il ruolo: qui l’Arduino risponde a un indirizzo e può inviare dati quando il controller li richiede. I callback più utili sono Wire.onReceive() per ricevere comandi e Wire.onRequest() per restituire dati.
#include
constexpr byte SENSOR_ADDR = 0x28;
volatile uint16_t value = 1234;
void sendValue() {
Wire.write(highByte(value));
Wire.write(lowByte(value));
}
void setup() {
Wire.begin(SENSOR_ADDR);
Wire.onRequest(sendValue);
}
void loop() {
value++;
delay(100);
}
Qui c’è una regola che applico sempre: prima faccio funzionare il collegamento, poi ottimizzo la velocità. Se il bus non risponde a 100 kHz, non ha senso insistere con clock più alti. Quando la base è solida, puoi davvero usarlo come un’interfaccia pulita e molto prevedibile. Da lì in poi, i problemi più frequenti non sono software puro, ma errori di cablaggio o di indirizzamento.
Gli errori che vedo più spesso nei progetti reali
Se un bus I2C non parte, io parto quasi sempre da cinque sospetti: indirizzo sbagliato, assenza di pull-up, massa non condivisa, tensione incompatibile e cavi troppo lunghi. In molti casi il bug non è nel codice, ma in una combinazione di questi elementi. Il bello e il brutto di I2C è proprio questo: quando va bene è lineare, quando va male il problema è spesso “elettrico” prima ancora che logico.
- Controllo l’indirizzo con uno scanner I2C prima di toccare il resto.
- Abbasso il clock a 100 kHz per escludere instabilità di temporizzazione.
- Verifico che GND sia comune tra tutte le schede.
- Controllo se il modulo è a 3,3 V o a 5 V e se serve un adattamento di livello.
- Stacco tutti i dispositivi e ne lascio uno solo sul bus, poi li rimetto uno alla volta.
Un errore meno visibile, ma molto fastidioso, è il bus bloccato da una periferica che resta in uno stato anomalo. In questi casi il timeout della libreria può aiutare a non congelare il programma, ma io lo considero una rete di sicurezza, non una soluzione. Se devo essere pratico, un timeout ricorrente significa quasi sempre che il problema vero è ancora lì sul bus.
Un trucco semplice che funziona quasi sempre è questo: se il sistema è instabile, scollego tutto, lascio solo un dispositivo noto e collaudato, e ricostruisco il bus pezzo per pezzo. Sembra banale, ma è il modo più rapido per capire se il difetto nasce dal codice, dal cablaggio o dal singolo modulo. Da qui la domanda successiva è inevitabile: conviene davvero I2C in ogni scenario?
Quando I2C conviene davvero e quando no
Nel lavoro quotidiano io vedo I2C come il compromesso migliore tra semplicità e densità di periferiche. Se devo collegare sensori, piccoli display, RTC, expander di ingressi e memorie seriali su una scheda con pochi pin, è spesso la soluzione più pulita. Se invece il progetto chiede trasferimenti veloci e continui, oppure una linea più robusta su distanze maggiori, comincio a guardare altrove.
| Protocollo | Punti forti | Limiti | Quando lo scelgo io |
|---|---|---|---|
| I2C | Due fili, più dispositivi sullo stesso bus, cablaggio ordinato | Velocità media, attenzione ai pull-up e alla lunghezza dei cavi | Sensori, RTC, EEPROM, piccoli display, expander |
| SPI | Più rapido e molto diretto | Richiede più linee e selezione chip dedicata | Display veloci, SD card, periferiche con traffico intenso |
| UART | Semplice, diffuso, comodo per debug e moduli seriali | Di norma è punto-punto e meno adatto a più nodi | Console seriale, moduli GPS, Bluetooth, bridge seriali |
Se devo dirla in modo netto, I2C vince quando il problema principale è lo spazio sui pin e non la massima velocità. SPI vince quando il collo di bottiglia è il throughput. UART vince quando voglio una connessione semplice e lineare tra due estremi. Nella maggior parte dei progetti hobby e prototipali ben pensati, I2C resta comunque il punto d’equilibrio più interessante, perché permette di espandere la scheda senza trasformarla in un groviglio di fili.
La regola pratica che uso per farlo partire al primo colpo
Quando apro un progetto nuovo, io seguo sempre la stessa sequenza: prima leggo il pinout della scheda, poi verifico l’indirizzo della periferica, poi controllo tensioni e pull-up, e solo dopo aumento la velocità del bus. Questa disciplina mi evita il classico errore di voler “ottimizzare” una comunicazione che in realtà non è ancora stabile.
La mia regola minima è semplice: una periferica alla volta, 100 kHz, cavi corti, massa comune e livello logico coerente. Se tutto funziona così, allora posso aggiungere il resto con più sicurezza. Se una scheda supporta più di un bus, separare i dispositivi più rumorosi o più delicati può fare una differenza enorme; se non lo fa, allora il cablaggio ordinato e il controllo degli indirizzi diventano ancora più importanti.
In pratica, I2C non è difficile: è esigente sui dettagli. Quando quei dettagli sono sotto controllo, diventa uno dei modi più eleganti per far crescere un progetto Arduino senza spreco di pin e senza complicare inutilmente il codice. E, nella mia esperienza, è proprio questo equilibrio tra ordine e flessibilità che continua a renderlo così utile anche nei progetti più recenti.