Riferimenti

https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md

  1. Abilitare SPI con raspi-config

Utilizzare wiringPi

Come installare la libreria: http://wiringpi.com/download-and-install/

Schema dei segnali SPI sul connettore P1 del Raspberry Pi

L'API è molto limitata. Dobbiamo capire come:

  • Disabilitare CS in modalità master, così da controllarlo tramite le API GPIO.
  • Verificare la polarità di campionamento e trasmissione dei dati.
  • In modalità slave, CS deve essere gestito dal driver.
  • Possiamo trasferire esattamente 7 byte in una sola transazione? Potrebbe essere semplice in modalità master e più difficile in modalità slave.

Nei sorgenti, wiringPiSPISetup chiama internamente wiringPiSPISetupMode, con firma int wiringPiSPISetupMode(int channel, int speed, int mode).

La modalità viene quindi passata a un ioctl. Le macro sono documentate nei sorgenti Linux:

https://github.com/torvalds/linux/blob/master/include/linux/spi/spi.h

https://github.com/torvalds/linux/blob/master/drivers/spi/spidev.c

Le macro interessanti sono:

Costanti SPI per modalità, polarità e chip select nel kernel Linux

SPI_CS_HIGH permetterebbe di usare una polarità diversa per il chip select; SPI_NO_CS consentirebbe invece di gestire la sincronizzazione del trasferimento con un handshake esterno. SPI_CS_WORD dovrebbe riguardare i trasferimenti continui, nei quali CS non commuta dopo ogni frame. Verifichiamo queste ipotesi nell’implementazione SPI del Raspberry Pi, a partire da:

https://github.com/raspberrypi/linux/blob/rpi-3.6.y/drivers/spi/spi-bcm2708.c

Per prima cosa osserviamo come vengono configurati i pin:

bcm2708_init_pinmode...

define INP_GPIO(g) (gpio+((g)/10)) &= ~(7<<(((g)%10)3))
define SET_GPIO_ALT(g,a) (gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)3))

int pin;
u32 *gpio = ioremap(0x20200000, SZ_16K);

/* SPI is on GPIO 7..11 */
for (pin = 7; pin <= 11; pin++) {
    INP_GPIO(pin);      /* set mode to GPIO input first */
    SET_GPIO_ALT(pin, 0);   /* set mode to ALT 0 */
}

iounmap(gpio);
undef INP_GPIO
undef SET_GPIO_ALT

I pin 7, 8, 9, 10 e 11 sono quelli di SPI0:

Pinout Raspberry Pi con pin SPI0 evidenziati

Sembra che questa funzione bcm2708_init_pinmode abbia configurato solo una SPI. Ma wiringPi dovrebbe funzionare con entrambe. Come viene gestito questo problema?

Per capire se questo codice è davvero compilato nel kernel, eseguiamo lsmod e scopriamo che non lo è:

Output di lsmod con il modulo spi_bcm2835

Esaminiamo questa versione 2835. Da qui:

https://github.com/notro/spi-bcm2708/wiki

La versione 2835 appare più snella. Dove si trova nei sorgenti? E quali sorgenti dobbiamo consultare?

Output del comando uname sul Raspberry Pi

Restiamo ai sorgenti ufficiali:

https://github.com/raspberrypi/linux

Alla fine abbiamo capito:

https://github.com/raspberrypi/linux/blob/bd991fd87ccf2c0e1596cdd6713c1f46c6d79343/drivers/spi/spi-bcm2835.c

È interessante notare che non possiamo gestire più di un dispositivo slave:

Codice della funzione bcm2835_spi_setup

Peccato! In questo progetto, per fortuna, dobbiamo comunicare con un solo dispositivo master o slave. L’opzione per controllare CS dal codice utente sembra disponibile, ma dobbiamo verificare che l’overlay SPI del Raspberry Pi non la impedisca.

Un primo test potrebbe essere quello di ricompilare wiringPi per passare la modalità NO_CS. Queste sono le principali modifiche al codice:

#define CUSTOM_MODE  (SPI_CS_HIGH | SPI_NO_CS | SPI_CPOL| SPI_CPHA)
...
if(( myFd = wiringPiSPISetupMode( SPI_CHAN, speed, CUSTOM_MODE)) < 0)
...
wiringPiSPIDataRW (SPI_CHAN, myData, 8);
int idx = 0;
for(idx = 0; idx < MAX_SIZE; idx++)
{
  myData[idx] = 0xaa;
}

L’ultima modifica serve perché wiringPiSPIDataRW scrive i dati ricevuti da MISO nello stesso buffer che contiene i dati da inviare su MOSI. Inoltre, SPI_CPHA e SPI_CPOL vengono impostati correttamente, mentre gli altri bit della modalità (SPI_CS_HIGH e SPI_NO_CS) no. Scorrendo i sorgenti vediamo infatti:

Codice della funzione wiringPiSPISetupMode

Rimuoviamo la limitazione e ricompiliamo: funziona! Sia SPI_CS_HIGH sia SPI_NO_CS hanno effetto. Resta da verificare se possiamo pilotare CS dal codice utente. Il CS da provare è il numero 10:

Output di gpio readall con pin SPI evidenziati

Funziona, ma emerge un problema di latenza. Quando CLK smette di commutare, passano circa 12 ms prima che venga eseguita l’istruzione successiva a wiringPiSPIDataRW. C’è un ritardo nascosto? Un secondo esperimento suggerisce che non dipenda dalla lunghezza del payload. Il wrapper di wiringPi non fa nulla di insolito. Proviamo a eliminare le scritture GPIO: non cambia nulla. Il ritardo sembra legato a SPI.

Esaminiamo il driver e la periferica stessa. Secondo il manuale della periferica Broadcom BCM2835,

https://www.raspberrypi.org/app/uploads/2012/02/BCM2835-ARM-Peripherals.pdf

sezione “SPI Address Map”, i registri sono quelli definiti nel driver:

Tabella dei registri SPI BCM2835
Offset dei registri SPI nel sorgente BCM2835

Nessun risultato. Torno al codice utente e riduco MAX_SIZE da 1048576 a 8: la latenza del Raspberry Pi scende a circa 2,4 ms. Non è poca, ma potrebbe essere accettabile.

SPI funziona anche con frame da 7 byte: ora possiamo provare un loopback con un dispositivo master SPI esterno.