SPI software su ESP32: quando l'hardware SPI non basta

Published on 13/07/2026 ESP32

L'ESP32 mette a disposizione due controller SPI utilizzabili dall'utente (comunemente indicati come HSPI e VSPI, oltre a quello riservato alla flash), gestibili tramite la libreria SPI standard o l'oggetto SPIClass. Nella maggior parte dei progetti bastano e avanzano. Ma ci sono situazioni in cui conviene rinunciare all'accelerazione hardware e implementare SPI via software, cioè pilotando manualmente i pin con digitalWrite(). Vediamo quando ha senso e come farlo.

I limiti reali dell'SPI hardware

Grazie alla GPIO matrix, l'ESP32 può in teoria mappare i segnali SPI hardware su quasi qualsiasi pin GPIO, quindi il problema "non ho i pin giusti" è meno frequente di quanto sembri. Il limite vero è che hai solo due istanze hardware disponibili. Se nel tuo progetto convivono, ad esempio, un display TFT (che vuole un bus SPI dedicato, veloce, con transazioni frequenti) e uno o più sensori con requisiti di clock/mode diversi o con librerie che gestiscono il bus in modo "invasivo" (cambiano frequenza, modalità, o si aspettano controllo esclusivo), mettere tutto sullo stesso controller hardware porta a conflitti: transazioni interrotte, letture sporche, glitch sul display.

Quando conviene passare al software SPI

  • Hai già saturato i due bus hardware con periferiche "pesanti" (display, SD card) e serve collegare un ulteriore dispositivo lento (es. un termocoppia amplifier come MAX31855, un sensore di pressione, una EEPROM SPI).
  • La libreria del dispositivo aggiuntivo bit-banga già di suo il protocollo e non condivide bene lo stato di un controller hardware usato in parallelo da un'altra libreria (tipico con display TFT + sensori su bus fisicamente diverso).
  • Vuoi isolare completamente un dispositivo critico da eventuali interferenze di timing generate da un altro bus SPI hardware molto attivo (es. refresh continuo di un display), a costo di velocità.
  • Stai prototipando su pin che non passano comodamente per l'IO_MUX ad alta velocità e preferisci non stressare la GPIO matrix per un dispositivo che comunque lavora a poche decine di kHz.

Il prezzo da pagare è la velocità: il software SPI bit-banged arriva a poche centinaia di kHz nella migliore delle ipotesi, contro le decine di MHz dell'hardware. Va bene per sensori e chip di controllo, non per display o SD card ad alta velocità.

Implementazione: bit-banging manuale

Il principio è semplice: per ogni bit, imposti il livello su MOSI, generi un fronte di clock, ed eventualmente leggi MISO. Ecco una funzione minimale per trasferire un byte in modalità SPI Mode 0 (CPOL=0, CPHA=0):

#define PIN_SCK  18
#define PIN_MOSI 23
#define PIN_MISO 19
#define PIN_CS    5

void softSPIBegin() {
  pinMode(PIN_SCK, OUTPUT);
  pinMode(PIN_MOSI, OUTPUT);
  pinMode(PIN_MISO, INPUT);
  pinMode(PIN_CS, OUTPUT);
  digitalWrite(PIN_SCK, LOW);
  digitalWrite(PIN_CS, HIGH);
}

uint8_t softSPITransfer(uint8_t data) {
  uint8_t received = 0;
  for (int i = 7; i >= 0; i--) {
    digitalWrite(PIN_MOSI, (data >> i) & 0x01);
    digitalWrite(PIN_SCK, HIGH);
    received <<= 1;
    if (digitalRead(PIN_MISO)) received |= 0x01;
    digitalWrite(PIN_SCK, LOW);
  }
  return received;
}

uint8_t leggiRegistro(uint8_t indirizzo) {
  digitalWrite(PIN_CS, LOW);
  softSPITransfer(indirizzo);
  uint8_t valore = softSPITransfer(0x00);
  digitalWrite(PIN_CS, HIGH);
  return valore;
}

Usare una libreria che già lo fa per te

Molte librerie per sensori SPI espongono già una modalità "software SPI" nel costruttore, proprio per lasciarti libertà sui pin e per non toccare i controller hardware. Esempio tipico con una libreria per termocoppie tipo MAX31855:

#include <Adafruit_MAX31855.h>

#define MAXCS   5
#define MAXCLK  18
#define MAXDO   19

// Costruttore a 3 pin = software SPI dedicato,
// indipendente dal bus hardware usato da altre periferiche
Adafruit_MAX31855 termocoppia(MAXCLK, MAXCS, MAXDO);

void setup() {
  Serial.begin(115200);
  delay(500);
}

void loop() {
  double c = termocoppia.readCelsius();
  if (isnan(c)) {
    Serial.println("Errore lettura termocoppia");
  } else {
    Serial.println("Temperatura: " + String(c) + " C");
  }
  delay(1000);
}

Nota pratica

Se scegli il software SPI per isolare un sensore da un display che gira su bus hardware, usa pin fisicamente diversi e non condivisi in alcun modo (nemmeno lo stesso pin CS di un altro dispositivo), e non fare mai coesistere sullo stesso bus fisico un dispositivo hardware SPI e uno software: il senso stesso della scelta è la separazione completa. Tieni anche conto che il bit-banging blocca la CPU per tutta la trasmissione: se lo esegui spesso o con payload grandi, valuta di girarlo su un task FreeRTOS dedicato per non far accumulare latenza sul resto del firmware.