machine.SPI(): add CS/NSS pin
It would be nice if CS/NSS pin for SPI could be driven by machine.SPI() itself. Manual toggling in user application slows it down.
ports/rp2: Optional spi_id and optional disabling of MISO pin
Summary
This PR includes two changes:
- Bring
machine.SPI()into parity withmachine.I2C(), selecting the default instance where none is specified. See: https://github.com/micropython/micropython/pull/16671 - Make
machine.SPI(mosi=None)explicitly disable MISO, for configurations which use this as a register-select or data/command pin (very common for write-only LCDs). See: https://github.com/micropython/micropython/issues/16912
I avoided making this change for all pins, since it rarely makes sense for MOSI or SCLK to be disabled.
Testing
Tested various invocations of machine.SPI() on a Pico 2 W.
Notably this change introduces a difference in behaviour that always resets MISO back to the default unless it's specified or set to None. Eg:
>>> machine.SPI()
SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, sck=18, mosi=19, miso=16)
>>> machine.SPI(miso=None)
SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, sck=18, mosi=19, miso=None)
>>> machine.SPI()
SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, sck=18, mosi=19, miso=16)
This is materially different from, for example, mosi where it's impossible to implicitly set a pin back to its default value:
>>> machine.SPI(mosi=23)
SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, sck=18, mosi=23, miso=16)
>>> machine.SPI()
SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, sck=18, mosi=23, miso=16)
>>> machine.SPI(mosi=None)
SPI(0, baudrate=1000000, polarity=0, phase=0, bits=8, sck=18, mosi=23, miso=16)
🤔 Maybe this changeset should also require that a pin is specified or that it is otherwise always set back to its default value.
Trade-offs and Alternatives
The latter change is more about explicit vs implicit. It's possibly just to set upt he MISO pin after setting up SPI and it will work as expected. A documentation change establishing this as the accepted pattern might be an alternative... though it might not be possible in all cases for the pin setup to happen before SPI.
Consider the following very crude example driver pattern where a user wishes to save a pin (normally eaten by SPI) and reuse it for register select:
class LCD:
def __init__(self, spi=None, rs=None):
self._spi = spi or machine.SPI()
my_led_screen = LCD(rs=machine.Pin(16))
However because the pin is constructed before it's passed into the driver class, its mux will be overwritten by machine.SPI()
With this change, a driver author could use:
class LCD:
def __init__(self, spi=None, rs=None):
self._spi = spi or machine.SPI(miso=None)
my_led_screen = LCD(rs=machine.Pin(16))
And SPI would no longer trample the "rs" pin config by setting it up as MISO.