When miso=None is set, the default miso pin changed state.
Port, board and/or hardware
Raspberry Pi Pico W
MicroPython version
MicroPython v1.24.1 on 2024-11-29; Raspberry Pi Pico W with RP2040
Reproduction
RS = Pin(16, Pin.OUT)
spi = SPI(0,
baudrate=400000,
polarity=0,
phase=0,
bits=8,
firstbit=SPI.MSB,
sck=Pin(18),
mosi=Pin(19),
miso=None)
RS.value(0)
time.sleep_ms(1)
RS.value(1)
Expected behaviour
Expected to set up SPI without MISO, set the RS pin value to 0, and after 1 ms, set the RS pin value to 1.
Observed behaviour
The SPI was set up correctly, but the RS pin's value never changed.
Additional Information
If I set up the pin after initializing SPI, it works as expected, the used pin 16 is default pin for miso. I think this bug is on other types of RPIs Pico.
Code of Conduct
Yes, I agree
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.