3.22. SPI in code

machine.SPI omhult een hardware-SPI-controller; CS-lijnen zijn gewone Pin-uitgangen die door het script worden beheerd. Maak een SPI-instantie aan met de bus-id, de gewenste kloksnelheid en (indien nodig) de modus:

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

De id selecteert welk hardware-SPI-blok wordt gebruikt; de beschikbare nummers en de SCK/MOSI/MISO-pinnen waarop ze worden afgebeeld, hangen af van de print (zie de OpenMV-boards). baudrate is de SCK-frequentie in hertz – de werkelijke snelheid die de hardware bereikt, kan iets lager zijn vanwege klokdeling, wat de afgedrukte waarde van het SPI-object zal tonen.

De CS-pin wordt aangemaakt met value=1 zodat hij niet-geactiveerd inactief blijft. Elke transactie activeert CS (stuurt hem laag), verplaatst de bytes en deactiveert CS weer (stuurt hem hoog).

3.22.1. Lezen, schrijven, uitwisselen

Drie methoden dekken de gangbare gevallen:

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() is het alleen-schrijven-snelpad; de controller duwt de bytes eruit en negeert wat het randapparaat op MISO terugstuurde. read() is het spiegelbeeld – het klokt N SCK-pulsen terwijl het een vaste dummy-byte (standaard 0) op MOSI verstuurt en de MISO-bytes opslaat. write_readinto() is de full-duplexvorm: het verstuurt de bytes uit één buffer en slaat de gelijktijdige MISO-bytes op in een andere. Veel randapparaten gebruiken dit patroon – “verstuur een commandobyte en lees vervolgens het antwoord in de volgende overdracht” – zodat de twee bewerkingen op natuurlijke wijze in één write_readinto-aanroep passen.

De meeste randapparaten verwachten dat de CS-lijn voor de gehele transactie geactiveerd blijft (van commandobytes tot antwoordbytes), dus houd de cs.value(0) / cs.value(1)-haken om de gehele reeks heen, niet om elke methodeaanroep afzonderlijk.

3.22.2. Een typische sensoruitlezing

Veel SPI-sensors organiseren hun toestand als een verzameling interne registers en volgen dezelfde uitwisselingsvorm: verstuur het registeradres (met een lees-/schrijfvlag in de bovenste bit) en lees of schrijf vervolgens de bytes van het register. Een uitlezing van register 0x0F op zo’n apparaat:

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]

De eerste MISO-byte is rommel (het apparaat was op dat moment nog het commando aan het ontvangen); de tweede MISO-byte bevat de registerinhoud. Het exacte formaat van de commandobyte – welke bit de lees-/schrijfvlag is, of het adres automatisch oploopt bij meervoudige-byte-uitlezingen – staat in de datasheet van het apparaat.

3.22.3. Bit-banging

De bovenstaande SPI-instantie gebruikt een hardware-SPI-blok: een speciaal randapparaat binnen de MCU met zijn eigen schuifregister en klokgenerator dat de SCK/MOSI/MISO-golfvormen in silicium produceert. Software overhandigt het simpelweg een byte; de bits bewegen over de draad zonder verdere hulp van de CPU, waardoor de CPU vrij blijft om parallel ander werk te doen.

Het alternatief is bit-banging: software doorloopt elke bit in een lus en schakelt rechtstreeks GPIO-pinnen om dezelfde golfvorm te produceren. Er is geen hardware-randapparaat bij betrokken – de CPU stuurt SCK laag, zet MOSI, stuurt SCK hoog, bemonstert MISO, enzovoort voor elke bit van elke byte. Dat houdt de CPU bezig gedurende de gehele transactie en verloopt langzamer dan het hardwareblok aankan, maar het werkt op elke pin en heeft geen vrij hardwareblok nodig.

machine.SoftSPI is de bit-bang-implementatie van dezelfde SPI-API:

from machine import SoftSPI, Pin

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

Gebruik het wanneer het apparaat op pinnen moet zitten die niet bedraad zijn naar een hardware-SPI-blok, of wanneer de hardwareblokken allemaal in gebruik zijn. 500 kHz is een comfortabel maximum op de meeste cams; de CPU blijft gedurende de gehele transactie bezig.