Bare-Metal STM32: configurazione e utilizzo di SPI

L’interfaccia Serial Peripheral Interface (SPI) è stata inizialmente standardizzata da Motorola nel 1979 per le comunicazioni a breve distanza nei sistemi embedded. Nella sua configurazione a quattro fili più comune, il trasferimento dati full-duplex è possibile sulle due linee dati (MOSI, MISO) con velocità dati ben superiori a 10 Mb/s. Ciò rende SPI adatto per applicazioni full-duplex a larghezza di banda elevata come schede di memoria SD e display ad alta risoluzione e aggiornamento elevato.

I dispositivi STM32 sono dotati di un numero variabile di periferiche SPI, due nell’F042 a 18 Mb/s e cinque nell’F411. Nelle famiglie STM32, la periferica SPI è relativamente simile, con differenze piuttosto minime nella disposizione dei registri. In questo articolo esamineremo la configurazione di una periferica SPI in modalità master.

Definizione di SPI

Un fatto interessante e forse fastidioso con SPI è che, sebbene possa supportare più dispositivi, non ha un bus di indirizzamento, ma richiede invece che un pin designato sia abbassato sul dispositivo, solitamente chiamato slave select (SS) o chip select ( CS). Con SS alto, il dispositivo slave mette gli altri pin in modalità ad alta impedenza, disconnettendosi efficacemente dalle linee SPI. Le periferiche STM32 SPI dispongono di un pin SS (NSS) dedicato che può semplificare questo processo se è collegato un solo dispositivo. In genere si desidera utilizzare i pin GPIO per attivare questi pin SS, con un pin GPIO per dispositivo.

Per SPI a quattro fili i dispositivi master e slave sono quindi collegati con le seguenti linee, con la linea SS duplicata per ogni slave aggiuntivo:

  • SCLK (orologio seriale, da master)
  • MOSI (master out, slave in)
  • MISO (master in, slave out)
  • SS (selezione slave)
Diagramma temporale SPI, che mostra le diverse configurazioni CPHA e CPOL.

La configurazione della periferica SPI è relativamente semplice e richiede la configurazione dell’orologio e di parametri come i trasferimenti a 8 o 16 bit. Meno evidenti sono i parametri di polarità dell’orologio SPI (CPOL) e di fase (CPHA). Qui l’impostazione predefinita (Modalità 0) è solitamente CPOL 0 e CPHA 0, che si traduce in una linea di clock inattiva e nuovi dati vengono inseriti nella linea di dati sul bordo di uscita del ciclo di clock corrente. CPOL 1 e CPHA 1 determinano il comportamento opposto. Gli slave possono supportare modalità diverse dalla modalità 0, ma la scheda tecnica di ogni slave deve essere consultata caso per caso.

Tenendo presente tutto ciò, possiamo esaminare la configurazione di SPI su entrambi i microcontrollori F411 e F042. A causa della suddetta somiglianza tra le periferiche SPI delle famiglie STM32, è relativamente semplice adattare la routine di inizializzazione. Le stesse routine di trasferimento dei dati rimangono invariate.

Sistemare le cose

La configurazione di un master SPI inizia con la configurazione dei pin GPIO che utilizzeremo. Ciò comporta l’impostazione della modalità della funzione alternativa (AF) e dei parametri pin appropriati, ad esempio AF5 sui pin da 4 a 6 dell’MCU F411 sulla porta A. Ai pin SPI stessi vengono assegnate le seguenti proprietà:

  • SCLK: flottante, push-pull, alta velocità.
  • MOSI: flottante, push-pull, alta velocità.
  • MISO: pull-up, push-pull, alta velocità.
  • SS: pull-up, push-pull, alta velocità.

Poiché SPI si basa su una configurazione push-pull piuttosto che sull’open-drain di I2C, dobbiamo impostare tutti i pin in modo che corrispondano a questo, insieme all’opzione di velocità GPIO veloce per stare al passo con la segnalazione SPI. La decisione di lasciare un pin fluttuante invece di attivarne il pull-up è determinata principalmente dalla funzione di questi pin. Nel caso di un pin selezionato è indispensabile mantenerlo in uno stato alto per evitare l’attivazione accidentale di un dispositivo prima che il sistema abbia terminato l’inizializzazione.

L’attivazione del pull-up del pin MISO viene eseguita per mantenere questa linea in uno stato noto quando nessun dispositivo è selezionato e quindi nessuno di essi sta guidando la linea MISO. Anche se il master non sta leggendo il registro dati in ingresso, le tensioni intermedie possono potenzialmente causare problemi come un consumo eccessivo di energia.

Con i pin GPIO così configurati, la periferica SPI di destinazione viene abilitata nel relativo registro di abilitazione Reset and Clock Control (RCC). Ad esempio, la periferica SPI 1 è abilitata nel RCC_APB2ENR registro, mentre SPI 2 e SPI 3 si trovano generalmente sul bus APB1 e quindi abilitati nel corrispondente registro in RCC. Il prossimo passo è configurare la periferica SPI stessa.

Il primo elemento da configurare qui è il divisore di clock SPI (baud rate, BR) nel SPI_CR1 Registrati. Utilizza la frequenza APB (la frequenza del bus periferico, o fPCLK) come ingresso per l’orologio SPI, che può essere impostato tra fPCLK/2 e fPCLK/256 utilizzando tre bit di risoluzione. Il divisore dovrebbe essere scelto per ottenere un clock ragionevole e quindi una velocità di trasferimento per l’applicazione.

Mentre su entrambe le famiglie F0 e F4 la dimensione di trasferimento predefinita è 8 bit, la periferica di quest’ultima consente solo di impostare il formato del frame di dati su 8-16 bit nel SPI_CR1 DFF (Formato frame dati). Con la periferica SPI di F0, la gamma di opzioni è molto più ampia quando si configura il valore DS (Data Size) nel SPI_CR2 Registrati. Questo è un valore a 4 bit che consente di configurare la dimensione dei dati ovunque tra 4 e 16 bit, ad esempio 8 bit corrispondenti a b0111.

A meno che non ci siano requisiti speciali, la dimensione dei dati predefinita a 8 bit, la configurazione predefinita della modalità 0 e l’impostazione predefinita MSB-first sono buone scelte predefinite che dovrebbero funzionare con la maggior parte dei dispositivi SPI. Ciò significa quindi che in tutti i casi deve essere configurato solo il divisore di clock, dopodiché è possibile abilitare la modalità master SPI_CR1 (MSTR). Il pin SS può quindi essere abilitato e impostato come output tramite l’impostazione SSOE in SPI_CR2.

Infine, la periferica SPI può essere abilitata da settaggio SPE (Abilitazione periferica SPI) in SPI_CR1.

Trasferimenti di data

Tipico bus SPI: master e tre slave indipendenti. (Credito: Cburnett)

Come accennato in precedenza, SPI consente trasferimenti full-duplex. La complicazione che questo aggiunge deriva dalla natura completamente sincrona di SPI: per ogni byte messo sulla linea MOSI dal master, lo slave metterà un byte sulla linea MISO e viceversa. Poiché la linea di clock è pilotata dai byte inviati dal master, il risultato è che per ricevere dati da uno slave, il master deve inserire dati (es. byte nulli) su MOSI per ogni byte su MISO.

Un modo per aggirare questo problema è cambiare il bus SPI da una configurazione a quattro fili a una a tre fili (half-duplex) utilizzando BIDIMODE in SPI_CR1, che richiede una macchinosa riconfigurazione della periferica tra un trasferimento e l’altro. Generalmente vorresti semplicemente inserire byte nulli su MOSI per risparmiarti questo problema.

Per inviare byte a uno slave seguiamo quindi questa sequenza dopo aver abbassato la linea SS del target:

  1. Aspettare SPI_SR_TXE (registro di stato: registro di trasmissione vuoto) per diventare vero.
  2. Scrivi i dati (8-16 bit) in SPI_DR. Ripetere da (1) se devono essere scritti più dati.
  3. Aspettare SPI_SR_TXE per diventare di nuovo vero.
  4. Aspettare SPI_SR_BSY (registro di stato: bus occupato) per diventare falso.

La sequenza viene terminata portando nuovamente SS in alto, sebbene si noti che alcuni slave SPI supportano più scritture in una singola sequenza. Un trucco in questa sequenza è quando scriviamo i dati su SPI_DR cioè <16 bit: anche se scriviamo un 8-bit uint8_t variabile o simile a questo registro, finirà sempre per scrivere 16 bit nel registro, con i nostri dati più questo riempimento inserito su MOSI e rovinando il trasferimento dei dati. Per aggirare questo problema, dobbiamo eseguire il cast del registro SPI_DR sulla dimensione prevista, ad esempio per un array di dati a 8 bit:

*((volatile uint8_t*) &(SPI1->DR)) = data[i];

Per ricevere da uno slave, abbassiamo SS o lo lasciamo basso dopo una precedente sequenza di trasmissione e seguiamo questa sequenza:

  1. Aspettare SPI_SR_BSY diventare falso.
  2. Scrivi dati fittizi (ad es. 0x00). SPI_DR per generare un segnale di clock.
  3. Aspettare SPI_SR_RXNE (registro di stato: registro dati di ricezione non vuoto) per diventare vero.
  4. Leggi i dati da SPI_DR nel buffer locale. Torna a (1) per ricevere dati aggiuntivi.
  5. Aspettare SPI_SR_BSY diventare falso

Anche qui la sequenza si conclude tirando di nuovo in alto la SS. Nota che scrivere i dati fittizi fa la stessa cosa che con l’invio dei dati. Assicurarsi che il SPI_DR register viene eseguito correttamente prima di scrivere i dati. Per quanto riguarda il motivo per cui stiamo entrambi leggendo e scrivendo SPI_DR è perché è un registro condiviso, collegato alle FIFO TX e RX della periferica SPI.

Infine, per eseguire un’operazione di ricetrasmissione full duplex, possiamo combinare queste due sequenze, inviando dati anziché byte fittizi e ricevendo contemporaneamente dati da uno slave. Questa è ovviamente un’operazione che deve essere supportata dal dispositivo slave in questione. Per molti dispositivi e sensori SPI comuni, la maggior parte delle operazioni verrà probabilmente eseguita in modalità semiduplex.

Incartare

C’è ancora molto di più in SPI come accennato in precedenza, sebbene molte delle opzioni di configurazione siano piuttosto oscure e utilizzate raramente, come il trasferimento LSB-first e a 16 bit, la modalità TI e le varie impostazioni di fase e polarità dell’orologio. Un aspetto più comunemente usato delle periferiche SPI di cui parleremo in un prossimo articolo è la modalità I2S che si trova sulla maggior parte degli MCU STM32. Questa è un’interfaccia di connessione per codec audio esterni, spesso presente come modalità secondaria sulle periferiche SPI.

La stessa SPI vede un uso significativo con display a risoluzione maggiore e archiviazione dei dati, ma molti sensori come il BME280 di Bosch e i relativi sensori MEMS implementano anche un’interfaccia SPI oltre a quella I2C. A seconda del sistema, mettere alcuni di questi dispositivi su SPI anziché su I2C può avere molto senso a causa del routing o di altri vincoli.

Leave a Comment

Your email address will not be published. Required fields are marked *