Description
(This issue is an offshoot of #1270.)
A number of open issues (#1270, #6009, #7206, #8315, #8941, #8945, #9373, #9821, #9823) have asked for the ability to set pin drive mode, drive strength, or a pull for various *io
and other objects. Currently these classes provide limited access to some of these settings, and sometimes don't provide them at all. For instance, drive strength is not available at all, and specific pull settings are not available in classes that could use them.
In more recent posts in #1270, there has been some discussion of how to to do this in general way, maybe by introducing a Pad
class or making Pin
objects mutable.
I have an idea for an upward-compatible addition which takes a different approach. In most or all API's which currently take a Pin
, I propose the ability to additionally take a Pin.Configuration
object, which specifies both the Pin
and the desired Pin.DriveMode
, Pin.Pull
, and drive strength settings. A Pin.Configuration
object would not change the pin: it's instead just some settings that are desired when the pin is set up. Some of the settings could be left unspecified for the particular class to choose.
DriveMode
and Pull
would be moved from digitalio
. For drive strength, I propose passing an integer, which must be one of the integers in Pin.drive_strengths
, as described #1270 (comment). So there is no Pin.DriveStrength
enum.
Here is a rough pseudo-definition in Python:
class Pin:
...
class Configuration:
def __init__(self, pin: Pin, *, drive_mode: Optional[Pin.DriveMode] = None, drive_strength: Optional[int[ = None, pull: Optional[Pin.Pull] = None):
# Args should be validated
self._pin = pin
self._drive_mode = drive_mode
self._drive_strength = drive_strength
self._pull = pull
@property
def pin(self):
return self._pin
# ... similar read-only properties for the other values
# An existing pin can create a Configuration for itself:
def configuration(self, pin: Pin, *, drive_mode: Optional[Pin.DriveMode] = None, drive_strength: Optional[int[ = None, pull: Optional[Pin.Pull] = None):
return Configuration(pin, drive_mode=drive_mode, drive_strengh=drive_strength, pull=pull)
Right now, PWMOut can take a pin:
pwm = pwmio.PWMOut(board.D5)
But it could also take a Pin.Configuration
, say to set the drive strength:
pwm = pwmio.PWMOut(board.D5.configuration(drive_strength=4))
Internally, in the shared-bindings
constructors that take Pin
s as arguments, any bare Pin
would be converted to a Pin.Configuration
, and the Configuration
objects would be what are passed to all the common-hal/
constructors. The shared-binding/
and common-hal/
constructors could reject invalid configurations if they made no sense for that kind of object or that particular port (e.g., the chip doesn't support the configuration on the peripheral). The constructors would call some shared setup routines to do the GPIO configuration as needed on pins or pass the appropriate arguments to the port-specific HAL layer
I am not wedded to Configuration
as the name of the class. I also thought of:
Pin.Details
Pin.Config
Pin.Setup
Pin.Settings
It is important to emphasize that a Pin.Configuration
does not make any changes itself to the pin settings. It's just a carrier of requested settings. This is because for some HAL layers, the pin is not alway set up by GPIO operations. For instance, in ESP-IDF, pulls are sometimes specified at the driver level for a particular peripheral, in a driver-specific configuration structure.
Pin.Configuration
does not replace existing dynamic mode setting needs. For instance, DigitalInOut
would still provide dynamic setting of Pull
and DriveMode
, since that is often necessary for matrix, scanning, bidirectional buses, etc.
The generalized mechanism provided by Pin.Configuration
can make some current pull=
and similar arguments unneeded. For instance, keypad.Keys
takes a pull
argument. Instead, the pull would be specified in the pins argument:
# current
keys = keypad.Keys((board.D1, board.D2), value_when_pressed=False, pull=True)
# new
keys = keypad.Keys([pin.configuration(pull=Pull.UP) for pin in (board.D1, board.D2), value_when_pressed=False)
This proposal is the result of some 3am thinking, so maybe I am missing something obvious. But I like the idea.