← index #11082PR #16922
Likely Duplicate · medium · value 1.873
QUERY · ISSUE

RP2 SPI: Instantiating with spi=None does not behave sensibly.

openby peterhinchopened 2023-03-20updated 2024-09-02
bugport-rp2

The constructor accepts None as an arg to miso, but still instantiates the default pin.

The clear intent is to create an output-only interface. Either it should reject the arg or (preferably) it should not instantiate a pin. Current behaviour can lead to unexpected pin conflicts.

This should also apply to mosi.

CANDIDATE · PULL REQUEST

ports/rp2: Optional spi_id and optional disabling of MISO pin

closedby Gadgetoidopened 2025-03-13updated 2025-03-26
port-rp2

Summary

This PR includes two changes:

  1. Bring machine.SPI() into parity with machine.I2C(), selecting the default instance where none is specified. See: https://github.com/micropython/micropython/pull/16671
  2. 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.

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied