SPI.

Scopo e principio di funzionamento.

Il nome interfaccia SPI è l'abbreviazione di "Serial Peripheral Bus", che può essere tradotto come "bus per il collegamento di periferiche". Da qui ne consegue il suo scopo principale - collegare un dispositivo Master - con uno o più Slave. Il Master in questa interfaccia è sempre solo, solo lui gestisce l'intero processo e solo lui può generare impulsi di clock. Se nel nostro caso il microcontrollore è sempre il Master (anche un computer può svolgere questo ruolo, ma questa è un'altra storia), allora nella stragrande maggioranza dei casi sono le periferiche a fare gli Slave. Sensori, display, chip DAC e ADC, lettori RFID, moduli di comunicazione wireless, inclusi ricetrasmettitori WiFi e Bluetooth, adattatori GPRS e così via. Di solito non viene utilizzato per collegare controller e/o computer, ma in generale è possibile e considereremo un esempio del genere di seguito. Questa interfaccia è particolarmente richiesta dove sono richieste velocità di trasferimento dati elevate e affidabilità non meno elevata. SPI è esattamente questo, è il più veloce che abbiamo a nostra disposizione e il più "leggero" in termini di risorse consumate. Il prezzo da pagare per questo è utilizzare più cavi rispetto ad altre interfacce. Qui sono necessari fino a 3 cavi solo direttamente per il trasferimento dei dati, non per niente il suo secondo nome è 3-wire. Si riportano i nomi dei segnali che possono variare a seconda del costruttore. Consultare il datasheet del componente che si intende utilizzare in caso di dubbi:
MOSI - Master Output Slave Input (Il Master invia, lo Slave riceve. *Nome alternativo: SDO, SIMO, DO, SI),
MISO - Master Input Slave Output (Il Master riceve, lo Slave trasmette. *Nome alternativo: SDI, SOMI, DI, SO).
SCLK - Serial Clock (segnale di clock. *Nome alternativo: SCK).
Come possiamo vedere, questo protocollo ha segni di entrambe le interfacce a noi note, UART ha due bus indipendenti per input e output, i2c ha un segnale di clock per una sincronizzazione affidabile, quindi puoi già immaginare approssimativamente il principio generale del funzionamento SPI, ma ha anche alcune soluzioni specifiche.
Tre fili è già un record, ma questo non ci basta. Ne saranno necessari altri per il corretto funzionamento della connessione. Poiché più dispositivi possono essere collegati al bus contemporaneamente, ma allo stesso tempo non hanno identificatori univoci, come, ad esempio, in i2c, è necessario in qualche modo distinguere l'uno dall'altro. Il master deve sapere esattamente a chi invia i dati e da chi li riceve. Per questo è stato aggiunto al protocollo il filo SS - Slave Select (*Nome alternativo: CS, nCS, nSS, STE). Ogni Slave ha un pin separato per questo, lo stato di cui monitora da vicino, una caduta a un livello basso significa che il Master si riferisce specificamente a lui. 
Nota: *si riportano i nomi dei segnali che possono variare a seconda del costruttore. Consultare il datasheet del componente che si intende utilizzare in caso di dubbi.

Ci sono tre modi per collegare Master e Slave.

Connessioni tra un Master e un singolo Slave.

Nei dispositivi Slave controllati singolarmente.

Vantaggi: comunicazione più rapida tra master e singoli Slave.
Svantaggi: necessità di avere un pin SS per ogni dispositivo Slave.

Nei dispositivi Slave connessi a catena (daisy chain).

Vantaggi: uso di un unico pin per selezionare i dispositivi.
Svantaggi: minore velocità di aggiornamento dei singoli Slave, il guasto di un elemento può causare un'interruzione del segnale negli altri dispositivi. 

La comunicazione.

La trasmissione dei dati sul bus SPI si basa sul principio di funzionamento dei registri a scorrimento in cascata! Diciamo di più, i registri di uscita 74HC595 funzionano facilmente secondo questo protocollo, che può essere utilizzato con successo. Di seguito considereremo un tale esempio.
Il protocollo di trasferimento dei dati SPI è molto semplice e quindi ha un minimo di opzioni: le cosiddette modalità. Sono descritti da due soli parametri:
CPOL - regola la polarità del clock ovvero discrimina lo stato normale di riposo cui si porta la linea di clock quando non è attiva. 
CPHA - regola la fase del clock, ovvero il fronte di clock in cui il ricevente campiona il segnale in ingresso. 

Diagramma temporale dei segnali che illustra le possibili polarità di clock e fase dei dati seriali. La comunicazione illustrata è a 8 bit. 

Leggi di più qui.

Nota: è importante che tutti i dispositivi sulla rete funzionino nelle stesse modalità SPI.

Come breve risultato intermedio, notiamo i vantaggi e gli svantaggi dell'interfaccia SPI rispetto a i2c.
Vantaggi: SPI è il più semplice possibile e quindi il più veloce possibile. La velocità può raggiungere decine di megahertz, il che consente di trasferire grandi quantità di dati in modalità streaming. Tutti gli bus sono unidirezionali, il che semplifica il compito di conversione del livello. Anche l'implementazione del software è molto semplice.
Svantaggi: richiede più fili e pin, che dipende direttamente dal numero di dispositivi, i2c ha solo due fili per qualsiasi numero di abbonati, i2c è più standardizzato, c'è meno rischio di incontrare diversi tipi di dispositivi che funzionano in modalità diverse.


Implementazione in Arduino.

Come già accennato, l'interfaccia SPI è destinata principalmente alla comunicazione tra il dispositivo principale e le periferiche. Ne derivano due caratteristiche. Primo: la stragrande maggioranza dei dispositivi progettati per funzionare in ambiente Arduino ha acquisito le proprie librerie e tutta la comunicazione con esse nel programma avviene tramite funzioni già pronte, il che significa che il programmatore quasi non utilizza direttamente SPI. In secondo luogo, una piccola e semplice libreria integrata per SPI è destinata principalmente al dispositivo principale. Si ritiene che il controller Slave sia estremamente raro. È più facile per lo Slave utilizzare direttamente le porte SPI, cosa che faremo nell'esempio un po' più avanti. La libreria stessa include una dozzina di funzioni, consideriamo le principali:
.begin(); - avvia il bus hardware SPI sul controller, imposta le modalità pin e i loro livelli, inoltre non è necessario farlo con pinMode().

.end(); - disabilita il bus SPI, mentre permane l'inizializzazione dei pin.

.setBitOrdine(order); - assegna l'ordine di output e input dei bit in un byte, dove order: LSBFIRST - prima bit meno significativo, MSBFIRST - prima bit più significativo.

.setClockDivider(); - divisore di frequenza del controller per la frequenza di clock del bus. Argomento SPI_CLOCK_DIVx, dove x può assumere i valori: 2, 4, 8, 16, 32, 64 e 128. Ad esempio, il comando SPI.setClockDivider(SPI_CLOCK_DIV4); forzerà il bus a funzionare a ¼ della frequenza del controller.

.setDataMode(mode); - imposta la modalità bus. Argomento: SPI_MODEx, dove x è il numero della modalità da 0 a 3, in base al numero di tutte le possibili combinazioni:
Mode             CPOL      CPHA
SPI_MODE0     0              0 
SPI_MODE1     0              1
SPI_MODE2     1              0
SPI_MODE3     1              1

.transfer(); - l'effettiva trasmissione e ricezione di un byte di dati. La ricezione e la trasmissione avvengono simultaneamente. in = SPI.transfer(out); dove in è il byte ricevuto, out è quello trasmesso.

I pin SPI hardware sulle schede Arduino principali sono i seguenti:
Linea     Arduino Uno (Nano, Mini)     Arduino Mega
MOSI                      11                                          51
MISO                      12                                          50
SCK                         13                                          52
SS                            10                                          53
Per più dispositivi collegati in parallelo secondo lo schema classico, sarà necessaria una linea SS in più per ciascuno, qualsiasi altro pin può svolgere il suo ruolo.

Inoltre, quasi tutte le schede Arduino hanno un connettore ICSP a sei pin ed è lo stesso ovunque, indipendentemente dalla scheda. Il suo scopo principale è la capacità di eseguire il flashing del microcontrollore con un programmatore, ad esempio per aggiornare il bootloader. Può essere utilizzato anche per collegare dispositivi, oltre che per collegare due schede Arduino, il che è conveniente, soprattutto se sono di tipo diverso.

Esempi.

La maggior parte dei dispositivi ha le proprie librerie e i propri esempi al loro interno. Un programmatore alle prime armi non capisce nemmeno sempre che la sua creazione funziona utilizzando il protocollo SPI. Pertanto, considereremo due casi di utilizzo di SPI "puri" espliciti dove è ancora possibile.

Primo esempio. Invio dati al registro a scorrimento (shift register) 74HC595.

Per una soluzione semplice e affidabile al problema della carenza di porte I/O, i cosiddetti registri a scorrimento (shift register) - microcircuiti. Non ci soffermeremo su cos'è un registro a scorrimento e su come funziona, c'è un articolo eccellente su questo, dai un'occhiata qui.

Assembliamo il circuito.

Dobbiamo anche collegare la libreria <SPI.h>, leggi di più qui.

Carichiamo il programma.

#include <SPI.h> 
#define ss 8;                                     // SS pin di registro. 
byte b;
void setup() 
     SPI.begin();                                 // inizializzazione dell'interfaccia SPI.
     pinMode(SS, OUTPUT); 
     digitalWrite(SS, HIGH); 
}
void loop() 
     b = 0x00000001; 
     for (int i = 0; i < 8; i++) 
     { 
          digitalWrite(SS, LOW);    // registro selezionato. 
          SPI.transfer(b);                   // trasferire byte in registro. 
          digitalWrite(SS, HIGH);   // trasferimento terminato. 
          delay(100);                           // ritardo. 
          b <<= 1;                                  // spostato uno. 
     } 


Osserviamo il corretto funzionamento dell'interfaccia SPI, dello shift register e del codice.
Un'osservazione interessante, se si aggiunge l'impostazione SPI.setBitOrder() a Setup; e gioca con i parametri, inserendo LSBFIRST o MSBFIRST, puoi vedere che la direzione dell'illuminazione cambierà solo da questa riga.

Secondo esempio. Scambio di dati tra due controller.

Ancora una volta, il compito di collegare i controller è l'ultimo che gli sviluppatori dell'interfaccia SPI si sono prefissati, ma a volte può sorgere la necessità, ad esempio, se si desidera realizzare la propria periferica SPI basata sul controller, o tutte le altre possibilità per buttare un paio di byte già esauriti. In ogni caso, sapere come si fa è interessante, utile e può tornare utile.
Dal lato del Master tutto è semplice, tradizionalmente utilizzeremo la libreria già pronta <SPI.h> e invieremo comandi allo Slave per accendere il LED premendo un pulsante. In risposta, riceveremo il numero di questi clic, che verrà conteggiato sul dispositivo Slave. 
Ma sullo Slave, faremo tutto il lavoro di ricezione e invio dei dati in modo completamente manuale, senza una libreria. Ci sono due ragioni per questo. In primo luogo, la biblioteca non supporta bene il lavoro dello Slave, poiché è su misura per il Master. In secondo luogo, dobbiamo sapere come possiamo fare a meno di una biblioteca e che è anche possibile in linea di principio. Sorprendentemente, il programma non diventa molto più ampio da questo. 

Carica il programma sul Master. Attiro la attenzione, funziona sulla libreria <SPI.h>.

#include <SPI.h> 
#define BUT 7                            // pulsante. 
byte but[2]; 
byte c;
void setup() 
     pinMode(BUT, INPUT); 
     SPI.begin();                            // inizializzazione dell'interfaccia.   
     pinMode(SS, OUTPUT); 
     digitalWrite(SS, HIGH); 
     Serial.begin(9600); 

void loop() 
     button(); 
     // qui posizioniamo qualsiasi programma.
}
void button() 
     static unsigned long timer; 
     if (timer + 100 > millis()) return;  // pulsanti di polling ogni 100 ms. 
     but[0] = but[1]; 
     but[1] = digitalRead(BUT); 
     if (but[0] && !but[1])                       // premuto.
     { 
          digitalWrite(SS, LOW); 
          c = SPI.transfer(1);                     // inviane uno, ottieni un byte in risposta. 
          digitalWrite(SS, HIGH); 
          Serial.println(c);                          // stampa il byte ricevuto (contatore) sul monitor.
     } 
     else if (!but[0] && but[1])             // spremere.
     { 
          digitalWrite(SS, LOW); 
          c = SPI.transfer(0);                    // invia uno zero, ottieni un byte in risposta.
          digitalWrite(SS, HIGH); 
          Serial.println(c);                         // stampa il byte ricevuto (contatore) sul monitor. 
     } 
     timer = millis(); 
}

Carichiamo il programma sul Salve. Notiamo che non utilizza la libreria, solo i registri dell'interfaccia hardware diretta.

#define LED 9                   // LED
// definire pin SPI hardware (solo per l'inizializzazione). 
#define MOSI_PIN 11
#define MISO_PIN 12 
#define SCK_PIN 13 
#define SS_PIN 10 
byte c;                              // contatore 0-254. 
byte rec;                             // definire una variabile per il byte ricevuto.
void setup() 
     SPCR = B00000000;  // reimpostare il registro di controllo SPI.
     SPCR = (1 << SPE);    // abilitare SPI come Slave (avvio).
     // inizializzare i pin per lavorare con SPI. 
     pinMode(MOSI_PIN, INPUT); 
     pinMode(MISO_PIN, OUTPUT); 
     pinMode(SCK_PIN, INPUT); 
     pinMode(SS_PIN, INPUT); 
     pinMode(LED, OUTPUT); 
     Serial.begin(9600); 

void loop() 
     while (digitalRead(SS_PIN) == LOW)   // mentre ci contattano, accettiamo byte.
     { 
          rec = spi_receive();                            // ricevere byte. 
          Serial.println(rec, HEX); 
          digitalWrite(LED, rec);                       // inviarlo al LED. 
          c++;                                                            // incrementare il contatore.
          SPDR = c;                                                 // invia il valore del contatore all Master. 
     } 

byte spi_receive()                                    // la funzione restituisce l'intero byte ricevuto.
     while (!(SPSR & (1 << SPIF))) {};  // fino che non viene impostato il flag di fine trasmissione, riceviamo bit. 
     return SPDR;                                      // restituire il contenuto del registro dati SPI. 

Quindi, assembliamo il circuito. Prendiamo due Arduino, ricordiamo dove hanno i pin SPI e colleghiamoli direttamente. Aggiungiamo un pulsante al Master e un LED allo Slave. 

Vedrai come si comporterà il LED e cosa apparirà nel monitor del Master assemblando e facendo funzionare il circuito. La cosa principale è che i dati vengono trasmessi e ricevuti correttamente da entrambi i dispositivi.
Sì, lo Slave non può dire nulla fino a quando il Master non lo chiama, questo deve essere tenuto in considerazione nella progettazione e prendere le misure appropriate. Ci sono solo due semplici opzioni: interrogare regolarmente lo Slave o estendere un altro filo dal Master allo Slave, una specie di filo per il campanello "mail, sir!", SS viceversa. È necessario decidere come procedere al meglio ogni volta individualmente, in base a molti fattori e alle proprie preferenze. 

Conclusione.

L'interfaccia SPI è veloce, affidabile e semplice anche senza librerie. Non c'è quasi nessun alle prime armi che non incontrerebbe i dispositivi SPI nel primo mese della sua comprensione del mondo dell'elettronica digitale. Non importa se l'interfaccia è visibile attraverso le librerie periferiche, o se è più facile utilizzarla direttamente, ma ogni master fai da te che si rispetti dovrebbe capire come funziona, come connettersi al meglio e cosa succede in generale.
Crea il tuo sito web gratis! Questo sito è stato creato con Webnode. Crea il tuo sito gratuito oggi stesso! Inizia