3.22. SPI nel codice

machine.SPI incapsula un controller SPI hardware; le linee CS sono normali uscite Pin gestite dallo script. Costruisci un’istanza SPI con l’id del bus, la frequenza di clock desiderata e (se necessario) la modalità:

from machine import SPI, Pin

spi = SPI(0, baudrate=1_000_000, polarity=0, phase=0)
cs = Pin("P3", Pin.OUT, value=1)         # CS idle high

L”id seleziona quale blocco SPI hardware utilizzare; i numeri disponibili e i pin SCK/MOSI/MISO a cui corrispondono dipendono dalla scheda (vedi Schede OpenMV). baudrate è la frequenza di SCK in hertz: la frequenza effettivamente raggiunta dall’hardware può essere leggermente inferiore a causa della divisione del clock, come mostrerà il valore stampato dell’oggetto SPI.

Il pin CS viene costruito con value=1 in modo che riposi disasserito. Ogni transazione asserisce CS (lo porta a livello basso), sposta i byte e disasserisce di nuovo CS (lo porta a livello alto).

3.22.1. Lettura, scrittura, scambio

Tre metodi coprono i casi più comuni:

cs.value(0)
spi.write(b"\x10\x20\x30")              # send 3 bytes, ignore what comes back
cs.value(1)

cs.value(0)
data = spi.read(4)                      # read 4 bytes; sends 0x00 while reading
cs.value(1)

rx = bytearray(2)
cs.value(0)
spi.write_readinto(b"\x9F\x00", rx)     # send 0x9F, 0x00; receive 2 bytes
cs.value(1)

write() è il percorso veloce di sola scrittura; il controller spinge i byte e scarta tutto ciò che la periferica ha rispedito su MISO. read() è l’immagine speculare: genera N impulsi di SCK inviando un byte fittizio fisso (0 per impostazione predefinita) su MOSI e memorizza i byte di MISO. write_readinto() è la forma full-duplex: invia i byte di un buffer e memorizza i byte MISO simultanei in un altro. Molte periferiche usano questo schema – «invia un byte di comando, poi leggi la risposta nel trasferimento successivo» – quindi le due operazioni si adattano naturalmente in un’unica chiamata write_readinto.

La maggior parte delle periferiche si aspetta che la linea CS resti asserita per l’intera transazione (dai byte di comando ai byte di risposta), quindi mantieni le parentesi cs.value(0) / cs.value(1) attorno all”intera sequenza, non attorno a ogni singola chiamata di metodo.

3.22.2. Una tipica lettura da sensore

Molti sensori SPI organizzano il loro stato come un insieme di registri interni e seguono la stessa forma di scambio: inviare l’indirizzo del registro (con un flag di lettura/scrittura nel bit più alto), poi leggere o scrivere i byte del registro. Una lettura del registro 0x0F su un dispositivo di questo tipo:

rx = bytearray(2)
cs.value(0)
spi.write_readinto(b"\x8F\x00", rx)     # 0x80 = "read" flag, then reg 0x0F
cs.value(1)
register_value = rx[1]

Il primo byte MISO è spazzatura (il dispositivo stava ancora ricevendo il comando in quel momento); il secondo byte MISO contiene il contenuto del registro. Il formato esatto del byte di comando – quale bit è il flag di lettura/scrittura, se l’indirizzo si auto-incrementa nelle letture multi-byte – è nella scheda tecnica del dispositivo.

3.22.3. Bit-banging

L’istanza SPI mostrata sopra usa un blocco SPI hardware: una periferica dedicata all’interno dell’MCU con il proprio shift register e generatore di clock che produce le forme d’onda SCK / MOSI / MISO nel silicio. Il software le passa semplicemente un byte; i bit si spostano sul filo senza ulteriore aiuto della CPU, lasciando la CPU libera di svolgere altro lavoro in parallelo.

L’alternativa è il bit-banging: il software cicla su ogni bit e commuta direttamente i pin GPIO per produrre la stessa forma d’onda. Non è coinvolta alcuna periferica hardware: la CPU porta SCK basso, imposta MOSI, porta SCK alto, campiona MISO e così via per ogni bit di ogni byte. Questo tiene la CPU occupata per l’intera transazione e funziona più lentamente di quanto possa fare il blocco hardware, ma funziona su qualsiasi pin e non richiede che un blocco hardware sia libero.

machine.SoftSPI è l’implementazione in bit-bang della stessa API SPI:

from machine import SoftSPI, Pin

spi = SoftSPI(baudrate=500_000, polarity=0, phase=0,
              sck=Pin("P2"), mosi=Pin("P0"), miso=Pin("P1"))

Usalo quando il dispositivo deve stare su pin non collegati a un blocco SPI hardware, oppure quando i blocchi hardware sono tutti in uso. 500 kHz è un limite confortevole sulla maggior parte delle cam; la CPU resta occupata per l’intera transazione.