6.22. SPI in code¶
machine.SPI wraps a hardware SPI controller; CS lines
are ordinary Pin outputs managed by the
script. Construct an SPI instance with the bus id, the desired
clock rate, and (if needed) the mode:
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
The id selects which hardware SPI block to use; the
available numbers and the SCK/MOSI/MISO pins they map to
depend on the board (see the OpenMV Boards).
baudrate is the SCK frequency in hertz – the actual rate
the hardware achieves may be slightly lower due to clock
division, which the printed value of the SPI object will show.
The CS pin is constructed with value=1 so it idles
deasserted. Every transaction asserts CS (drives it low),
moves the bytes, and deasserts CS (drives it high) again.
6.22.1. Reading, writing, exchanging¶
Three methods cover the common cases:
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 the write-only fast path; the
controller pushes the bytes and discards whatever the
peripheral sent back on MISO.
read() is the mirror image – it clocks N
SCK pulses while sending a fixed dummy byte (0 by default)
on MOSI and stores the MISO bytes.
write_readinto() is the full-duplex form:
it sends the bytes from one buffer and stores the simultaneous
MISO bytes into another. Many peripherals use this pattern –
“send a command byte, then read the response in the next
transfer” – so the two operations naturally fit into one
write_readinto call.
Most peripherals expect the CS line to stay asserted for the
entire transaction (command bytes through response bytes), so
keep the cs.value(0) / cs.value(1) brackets around the
whole sequence, not around each method call.
6.22.2. A typical sensor read¶
Many SPI sensors organise their state as a set of internal
registers and follow the same exchange shape: send the
register address (with a read/write flag in the top bit),
then read or write the register’s bytes. A read of register
0x0F on such a device:
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]
The first MISO byte is junk (the device was still receiving the command at that point); the second MISO byte holds the register contents. The exact command byte format – which bit is the read/write flag, whether the address auto-increments on multi-byte reads – is in the device’s data sheet.
6.22.3. Bit-banging¶
The SPI instance above uses a hardware SPI block:
a dedicated peripheral inside the MCU with its own shift
register and clock generator that produces the SCK / MOSI /
MISO waveforms in silicon. Software just hands it a byte; the
bits move on the wire without further CPU help, leaving the
CPU free to do other work in parallel.
The alternative is bit-banging: software loops over each bit and toggles GPIO pins directly to produce the same waveform. There is no hardware peripheral involved – the CPU drives SCK low, sets MOSI, drives SCK high, samples MISO, and so on for every bit of every byte. That ties the CPU up for the whole transaction and runs slower than the hardware block can, but it works on any pin and does not need a hardware block to be free.
machine.SoftSPI is the bit-bang implementation of
the same 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"))
Use it when the device needs to be on pins that are not
wired to a hardware SPI block, or when the hardware blocks
are all in use. 500 kHz is a comfortable ceiling on most
cams; the CPU stays busy for the whole transaction.