Replace the IO expander pull enable API with explicit modes - #320
Conversation
The pull resistor state was split across two virtuals: setPullMode picked up or down, and enablePull turned pulling on or off. What that pair means differs per expander. On the M5IOE1 the two share one register pair, so setPullMode alone already established the state and enablePull(pin, true) only set the pull-up bit without clearing the pull-down one, which could leave both enabled at once. On the PI4IOE5V6408 the registers are separate and the pair genuinely has to be used together, yet nothing in this repository ever enabled it: the pull-ups in use rely on the reset default of the enable register. setPullMode now takes gpio_pull_t and establishes the whole state in one call, so neither the ordering nor a reset default matters. The values match the standalone M5IOE1 driver constants. Writes are ordered so that two pulls are never enabled at the same time, and the enable register is now written explicitly on the PI4IOE5V6408 rather than assumed. The setter returns whether the requested state was fully established, since it takes more than one I2C write and a partial failure is what would leave a pin in the state this change is meant to rule out. The other virtuals in IOExpander_Base still return void. BREAKING: enablePull is gone and setPullMode takes an enum instead of a bool. Old calls do not compile, because neither bool nor an integer converts to the enum implicitly. | Old | New | | --- | --- | | enablePull(pin, false) | setPullMode(pin, pull_none) | | setPullMode(pin, true), with pulling enabled | setPullMode(pin, pull_up) | | setPullMode(pin, false), with pulling enabled | setPullMode(pin, pull_down) |
There was a problem hiding this comment.
Pull request overview
This PR updates the IO expander pull-resistor API to remove the ambiguous enablePull/setPullMode(bool) split and replace it with a single explicit setPullMode(pin, gpio_pull_t) call that establishes the complete pull state in one operation.
Changes:
- Replaces
enablePull+setPullMode(bool)withsetPullMode(uint8_t, gpio_pull_t)returningboolfor success/failure. - Updates M5IOE1 and PI4IOE5V6408 drivers to implement the new explicit pull modes.
- Updates existing call sites to use
pull_none,pull_up, andpull_down.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utility/Power_Class.cpp | Updates call sites to use the new explicit pull modes (notably pull_none, pull_up, pull_down). |
| src/utility/PI4IOE5V6408_Class.hpp | Removes enablePull and updates setPullMode signature to the new enum-based API. |
| src/utility/PI4IOE5V6408_Class.cpp | Implements new setPullMode logic using select/enable registers and returns success as bool. |
| src/utility/M5IOE1_Class.hpp | Removes enablePull and updates setPullMode signature to the new enum-based API. |
| src/utility/M5IOE1_Class.cpp | Implements new setPullMode logic intended to avoid dual-pull states and returns success as bool. |
| src/utility/IOExpander_Base.hpp | Introduces gpio_pull_t enum and updates the virtual interface to bool setPullMode(...). |
| src/M5Unified.cpp | Updates internal initialization to use the new explicit pull modes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const auto bit = 1 << pin; | ||
| bool select_ok; | ||
| bool enable_ok; | ||
| switch (mode) { | ||
| case pull_none: | ||
| return bitOff(0x0B, bit); | ||
| case pull_up: | ||
| select_ok = bitOn(0x0D, bit); | ||
| enable_ok = bitOn(0x0B, bit); | ||
| break; | ||
| case pull_down: | ||
| select_ok = bitOff(0x0D, bit); | ||
| enable_ok = bitOn(0x0B, bit); | ||
| break; | ||
| default: | ||
| return false; | ||
| } | ||
| return select_ok && enable_ok; |
There was a problem hiding this comment.
Both of these are right, and they were already fixed -- the branch had not been
pushed when the pull request was opened, so the review ran against the earlier
commit. Sorry for the noise.
The pushed version gates the second write on the first: for pull_up and
pull_down, clearing the opposite pull (M5IOE1) or writing the select register
(PI4IOE5V6408) now returns false immediately on failure, so the requested pull
is never enabled after a partial failure. Every partial failure therefore either
leaves the previous state alone or falls back to no pull, and the "true only when
the requested state was completely established" contract holds.
pull_none is left attempting both writes on the M5IOE1, since the goal there is
to clear both bits and stopping at the first failure would only leave more of the
old state behind. It cannot enable two pulls at once either way.
| bool first; | ||
| bool second; | ||
| switch (mode) { | ||
| case pull_none: | ||
| first = bitOff(pu_reg, bit); |
There was a problem hiding this comment.
Both of these are right, and they were already fixed -- the branch had not been
pushed when the pull request was opened, so the review ran against the earlier
commit. Sorry for the noise.
The pushed version gates the second write on the first: for pull_up and
pull_down, clearing the opposite pull (M5IOE1) or writing the select register
(PI4IOE5V6408) now returns false immediately on failure, so the requested pull
is never enabled after a partial failure. Every partial failure therefore either
leaves the previous state alone or falls back to no pull, and the "true only when
the requested state was completely established" contract holds.
pull_none is left attempting both writes on the M5IOE1, since the goal there is
to clear both bits and stopping at the first failure would only leave more of the
old state behind. It cannot enable two pulls at once either way.
5665147 to
b201d3c
Compare
Problem
The pull resistor state of an IO expander pin was split across two virtuals:
setPullModepicked up or down, andenablePullturned pulling on or off.What that pair means differs per expander, and neither implementation is well
served by it.
On the M5IOE1 the two share one register pair, so
setPullModealone alreadyestablished the state.
enablePull(pin, true)only set the pull-up bit withoutclearing the pull-down one, so calling it on a pin that was previously pulled
down left both enabled at once.
On the PI4IOE5V6408 the registers are separate and the pair genuinely has to be
used together, yet nothing in this repository ever enabled it. The pull-ups in
use work because the reset default of the enable register happens to be the
enabled side.
Fix
setPullModenow takesgpio_pull_tand establishes the whole state in onecall, so neither the call order nor a reset default matters. The values match
the constants of the standalone M5IOE1 driver.
Writes are ordered so that two pulls are never enabled at the same time, and
the first write is checked before the second is attempted: if clearing the
opposite pull fails, the requested one is not enabled. Every partial failure
therefore either leaves the previous state alone or falls back to no pull. On
the PI4IOE5V6408 the enable register is now written explicitly rather than
assumed.
The setter reports whether the requested state was fully established, since it
takes more than one I2C write and a partial failure is what would leave a pin
in the state this change is meant to rule out. The other virtuals in
IOExpander_Basestill returnvoid; making them consistent is left for aseparate change.
Breaking change
enablePullis gone andsetPullModetakes an enum instead of abool. Oldcalls do not compile, because neither a
boolnor an integer converts to theenum implicitly.
enablePull(pin, false)setPullMode(pin, pull_none)setPullMode(pin, true), with pulling enabledsetPullMode(pin, pull_up)setPullMode(pin, false), with pulling enabledsetPullMode(pin, pull_down)Intended for the next release, bumping the minor version to 0.3.0.
Verification
Built for ESP32-S3, ESP32-P4, ESP32-C5 and ESP32. The S3 and P4 builds are the
ones that matter: every call site of this API sits behind a target guard for one
of those two, and all eight are updated in this commit so the change stays
bisectable.
Checked on a Tab5, reading the pull enable and select registers back:
setExtOutput(true)andsetExtOutput(false)still select up and down asbefore, on both of the expanders they touch
pull_noneand being set explicitly for the other two, which is what removesthe dependency on the reset default