Il bus SPI

 

1️⃣ Cos’è SPI

SPI (Serial Peripheral Interface) è un protocollo di comunicazione seriale sincrono usato per far comunicare un microcontrollore con periferiche (sensori, display, EEPROM, SD card, ecc.).

  • È sincrono, cioè usa un segnale di clock (SCK) per sincronizzare la trasmissione dei dati.
  • Lavora in full-duplex, quindi il microcontrollore può inviare e ricevere dati contemporaneamente.
  • È master/slave: l’Arduino è di solito il master, la periferica è lo slave.

1.1 Linee principali del bus SPI

Linea Funzione
MOSI (Master Out, Slave In) Dati che il master invia allo slave
MISO (Master In, Slave Out) Dati che lo slave invia al master
SCK (Serial Clock) Segnale di clock generato dal master
CS/SS (Chip Select / Slave Select) Seleziona quale slave comunica con il master
  • Il CS è fondamentale se ci sono più periferiche SPI: solo quella con CS basso (attivo) comunica.
  • Le linee MOSI, MISO e SCK possono essere condivise tra più dispositivi SPI.

2️⃣ SPI sull’Arduino Nano ESP32

L’ESP32 ha più bus SPI hardware, denominati VSPI, HSPI e SPI predefinito. Sull’Arduino Nano ESP32:

  • SPI predefinito viene mappato su certi pin, ma puoi anche usare SPI custom su altri pin.
  • La libreria Arduino SPI.h gestisce la configurazione hardware e software del bus SPI.

2.2 Come funziona la comunicazione

  • Il master abbassa il CS dello slave con cui vuole comunicare.
  • Il master genera il clock (SCK).
  • Ogni impulso di clock trasferisce un bit: il master manda 1 bit su MOSI, lo slave risponde 1 bit su MISO.
  • Dopo N bit (di solito 8, cioè 1 byte), la comunicazione continua con i successivi byte.
  • Al termine, il master rialza il CS e la periferica si “disconnette” dal bus.
#include <SPI.h>

void setup() {
  Serial.begin(115200);
  SPI.begin(); // inizializza SCK, MOSI, MISO
  pinMode(5, OUTPUT); // CS
  digitalWrite(5, HIGH); // disattivo periferica
}

void loop() {
  digitalWrite(5, LOW); // seleziona periferica
  byte response = SPI.transfer(0x42); // invia 0x42 e riceve byte
  digitalWrite(5, HIGH); // disattiva periferica

  Serial.println(response, HEX);
  delay(1000);
}
  • SPI.begin() inizializza il bus.
  • SPI.transfer(byte) invia un byte e contemporaneamente riceve un byte.
  • La gestione del CS è manuale, puoi anche avere più slave usando pin diversi.

2.4 Note pratiche

  • Velocità: puoi cambiare la frequenza SPI con SPI.beginTransaction(SPISettings(speed, MSBFIRST, SPI_MODE0)).
  • Modalità SPI: ci sono 4 modalità (0-3) che definiscono polarità e fase del clock.
  • Più periferiche: basta un CS diverso per ognuna, le linee MOSI/MISO/SCK sono condivise.
  • Full-duplex: puoi inviare e ricevere dati contemporaneamente, utile per sensori o display avanzati.

Su Arduino Nano ESP32, lo schema ufficiale indica:

Segnale SPI Pin ESP32 Nano
COPI (Chip Out, Peripheral In) = MOSI GPIO38 D11
CIPO (Chip In, Peripheral Out) = MISO GPIO47 D12
SCK (Clock) GPIO48 D13

Nota: CIPO e COPI sono solo un altro modo di dire MISO e MOSI:

  • COPI = quello che va dal master allo slave (MOSI)
  • CIPO = quello che va dallo slave al master (MISO)

Come leggere lo schema

  1. SCK (GPIO48): genera il clock. Tutti i dispositivi SPI si sincronizzano su questo segnale.
  2. COPI/MOSI (GPIO38): l’Arduino invia dati alla periferica.
  3. CIPO/MISO (GPIO47): l’Arduino riceve dati dalla periferica.
  4. CS (chip select): di solito lo scegli tu su un qualsiasi GPIO libero, ad esempio GPIO5 o GPIO21, e serve per selezionare quale periferica SPI vuoi attivare.

Nota pratica sull’ESP32 Nano

  • Questi pin sono predefiniti nel bus SPI hardware, quindi usando SPI.begin(COPI, CIPO, SCK, CS) puoi inizializzarlo anche con pin non standard.
  • Se hai più dispositivi SPI, puoi usare lo stesso COPI/CIPO/SCK ma pin CS differenti per ogni periferica.

1️⃣ SPI.begin() senza parametri

  • Se scrivi solo:

            SPI.begin();

  • Allora l’ESP32 userà i pin SPI hardware di default, che sulla tua scheda sono:

    Funzione Pin ESP32 Nano
    SCK GPIO48
    COPI/MOSI GPIO38
    CIPO/MISO GPIO47
    CS tu lo scegli, ad esempio GPIO5

Quindi sì, SPI.begin(); inizializza il bus SPI sui pin di default.

2️⃣ SPI.begin(SCK, MOSI, MISO, CS)

  • Se vuoi usare pin diversi, puoi fare per es:

    SPI.begin(7, 8, 9, 10);

  • In questo caso:

    Funzione Pin che usi
    SCK D7
    MOSI/COPI D8
    MISO/CIPO D9
    CS D10
  • Importante: il CS (D10) non viene gestito automaticamente da SPI; devi abbassarlo e rialzarlo manualmente come nel mio esempio precedente:

    digitalWrite(10, LOW); // seleziona periferica SPI.transfer(0x42);
    digitalWrite(10, HIGH); // deseleziona periferica

3️⃣ Piccolo dettaglio sull’ESP32

  • L’ESP32 ti permette di mappare SPI su quasi tutti i GPIO, quindi puoi scegliere i pin più comodi.
  • Se usi i pin hardware di default, la comunicazione è più veloce e stabile.
  • Se usi pin diversi, Arduino li configurerà via SPI software (“bit-banging”), leggermente più lento ma funziona.

Quanti dispositivi posso gestire sul bus SPI?

Il numero di dispositivi SPI che puoi gestire non è fisso, dipende più che altro da come gestisci i pin CS e dalle caratteristiche elettriche del bus. Vediamo i dettagli.

1️⃣ Limite teorico

  • SPI è un bus condiviso: MOSI, MISO e SCK possono essere condivisi tra tutti i dispositivi.
  • Ogni periferica SPI ha bisogno del suo pin CS (Chip Select).
  • Quindi, il numero massimo di periferiche è pari al numero di GPIO liberi che puoi usare come CS.

Esempio:

  • Hai 10 GPIO liberi → puoi collegare fino a 10 periferiche SPI.
  • Tutte condividono MOSI/MISO/SCK, solo CS cambia.

2️⃣ Limite pratico

  • Carico elettrico: ogni periferica aggiunge capacità sul bus. Se metti troppi dispositivi sullo stesso MOSI/MISO/SCK, il segnale può degradare.
  • Velocità: più periferiche ci sono, più è difficile mantenere alte velocità di comunicazione.
  • Gestione software: devi controllare manualmente il CS per ciascuna periferica.
  • Nella pratica con Arduino/ESP32, è comune collegare 5–8 dispositivi senza problemi, ma in teoria puoi arrivare anche a una dozzina se la lunghezza dei cavi è corta e i segnali sono ben schermati.

3️⃣ Strategie per molti dispositivi

  1. Decodificatori di CS: puoi usare un IC tipo 74HC138 per generare più segnali CS da pochi GPIO.
  2. Shift register: simile al decodificatore, puoi controllare più CS con un registro a scorrimento.
  3. Bus multiplexer SPI: alcuni IC permettono di selezionare dinamicamente la periferica SPI.

💡 Regola pratica: se hai pochi dispositivi (1–5) basta collegare direttamente CS a un GPIO libero. Se superi i 5–6 dispositivi, conviene usare un decodificatore o multiplexer per non consumare troppi pin.

 

Confronto tra I²C e SPI

I²C sembra più comodo a prima vista, perché usa solo due pin (SDA e SCL) e puoi collegare teoricamente molti dispositivi sullo stesso bus grazie agli indirizzi. Però SPI ha alcuni vantaggi specifici che lo rendono spesso preferibile, a seconda dell’applicazione. Vediamo un confronto chiaro:

1️⃣ Pin e semplicità

Protocollo Pin necessari Gestione periferiche
I²C 2 (SDA, SCL) Ogni dispositivo ha un indirizzo univoco
SPI 4+ (MISO, MOSI, SCK + 1 CS per periferica) Ogni periferica ha il proprio CS
  • Vero: SPI usa più pin, soprattutto se hai molte periferiche.
  • I²C può sembrare più “economico” sui pin.

2️⃣ Velocità

  • SPI: molto veloce, fino a decine di MHz (ESP32: tipicamente 80–100 MHz teorici, in pratica 20–40 MHz).
  • I²C: più lento, tipicamente 100 kHz (standard) o 400 kHz (fast mode), massimo 1–3,4 MHz nelle versioni “High Speed”.

Quindi se ti serve trasferire grandi quantità di dati rapidamente (display, memorie, sensori ad alta velocità), SPI vince nettamente.

3️⃣ Affidabilità e semplicità elettrica

  • SPI: più robusto su lunghe distanze, perché è full-duplex e non ha bisogno di pull-up sugli stessi segnali dati come I²C.
  • I²C: segnali open-drain con resistenze pull-up, quindi più sensibile a lunghezze di cavo lunghe o a disturbi elettrici.

4️⃣ Full-duplex vs Half-duplex

  • SPI: puoi inviare e ricevere dati contemporaneamente. Utile per sensori o display che rispondono subito.
  • I²C: sempre half-duplex, un dato alla volta, quindi più lento in applicazioni bidirezionali intensive.

5️⃣ Numero di dispositivi

  • I²C: teoricamente fino a 127 dispositivi sullo stesso bus (7 bit di indirizzo), ma nella pratica dipende dalle resistenze pull-up e dalla capacità totale del bus.
  • SPI: limitato dai pin CS disponibili, ma puoi usare decodificatori o shift register per aumentare il numero.

6️⃣ Quando conviene usare SPI invece di I²C

  1. Velocità alta richiesta → display, memorie, transfer di immagini, audio.
  2. Dati full-duplex → lettura e scrittura simultanea.
  3. Affidabilità su distanze maggiori o ambienti rumorosi.
  4. Periferiche che non hanno indirizzi I²C o hanno più pin di controllo (alcuni sensori complessi, DAC, ADC veloci).

💡 Sintesi pratica:

  • I²C → pochi pin, molte periferiche, velocità limitata, half-duplex.
  • SPI → più pin, velocità molto alta, full-duplex, robusto, ideale per grandi quantità di dati o periferiche “veloci”.

Esempio di comunicazione SPI:

 

#include <SPI.h>

const int CS_PIN = 5; // Chip Select

void setup() {
  Serial.begin(115200);

  // Inizializza il bus SPI sui pin ufficiali
  SPI.begin(48, 47, 38, CS_PIN); // SCK, MISO, MOSI, CS (opzionale)
  // Oppure :SPI.begin();  Imposta i pin di default
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);

  Serial.println("SPI ready");
}

void loop() {
  // 1️⃣ Seleziona lo slave
  digitalWrite(CS_PIN, LOW);

  // 2️⃣ Invia comando (primo transfer)
  SPI.transfer(0x42); // comando allo slave

  // 3️⃣ Leggi risposta (secondo transfer, invia un byte "fittizio")
  byte response = SPI.transfer(0x00);

  // 4️⃣ Disattiva periferica
  digitalWrite(CS_PIN, HIGH);

  // Mostra il dato ricevuto
  Serial.print("Slave response: 0x");
  Serial.println(response, HEX);

  delay(1000);
}

 

Note importanti

  1. SPI.begin(SCK, MISO, MOSI, CS) permette di usare i pin hardware ufficiali dell’Arduino Nano ESP32.
  2. Il primo SPI.transfer(0x42) invia il comando, ma non riceverai la risposta utile.
  3. Il secondo SPI.transfer(0x00) serve solo a generare il clock per ricevere la risposta dello slave.
  4. Il pin CS deve essere sempre LOW durante la comunicazione e rialzato a HIGH alla fine.