Common in Raspberry Pi projects, SPI is a synchronous (uses a dedicated clock signal), high-speed, full duplex (information passes both ways) serial communication protocol arranged as a controller and peripherals. A simple, high speed (especially over asynchronous) protocol with complete control over bits transmitted. Its limitations are just one controller, each peripheral requires a chip select line (CS) (although you could daisy chain in some applications) and has no error checking.
It uses four lines: Controller In Peripheral Out (COSI); Controller Out Peripheral In (CISO); Serial Clock (SCLK); and the fourth, Chip Select (CS), if there are multiple peripheral. Clock signal is generated by the controller, on each clock cycle the controller writes a bit to the peripheral and shifts its register, the controller reads a bit from the controller and it then shifts its register. After 8 clock cycles (for example) the contents of each device register has transposed.
Multiple peripherals can be connected to the same controller, which takes the chip select line low to select the relevant peripheral (so there will be a CS for each peripheral on the controller).
Two other bits are used to define how the clock signal is understood – Clock Polarity (CKP) and Clock Edge (CKE). CKP sets idle as low (0) or high (1) and CKE sets transfer on rising1Low to high. (1) or falling2High to low. (0) edge, giving modes. Mode zero is the most common (CKP=0 and CKE=0), data is sampled when clock signal goes high.
CircuitPython supports multiple interfaces including SPI with
busio.SPI Class. It doesn’t control CS, which is a simple digital line, so use
.DigitalInOut Class. The
board Class gives board specific pin names. The examples here are using a Raspberry Pi Pico however which doesn’t label the SPI pins:
import board import busio import digitalio
Lets deal with CS first, most devices expect to be taken low to select but remember to confirm on their data sheet (note this this is the same technique used to turn on the LED if you used
cs.digitalio.DigitalInOut(board.22) # Which pin CS is on cs.direction = digitalio.Direction.OUTPUT # Set it as an output cs.value = True # Set the line high (True) spi = busio.SPI(clock=board.GP2, MOSI=board.GP3, MISO=board.GP4)
The process is lock, configure, send/receive and unlock. A common construct is a
while loop with a
pass statement3Python’s “do-nothing” or null operation. until the lock is available (
acquire() methods are implemented in
busio so you can’t use
with, more on that later):
while not spi.try_lock(): pass spi.configure(baudrate=5000000, phase=0, polarity=0) in_data = bytearray(8) out_data = bytearray(b'\x01\x02\x03\x04\x05\x06\x07\x08') cs.value = False # Pull CS low spi.readinto(in_data) # Read spi.write(out_data) # Write cs.value = True # Release CS spi.unlock() # Release SPI lock
Note the paradigm communication is always duplex, CircuitPython
writeout are sending null data in the opposing direction.
Managing CS and locks directly invites errors. Adafruit has a library,
adafruit_bus_device, which has the
SPIdev class that does implement
acquire() (so you can use a with statement) and manages CS.
- 1Low to high.
- 2High to low.
- 3Python’s “do-nothing” or null operation.