Skip to content

Replace the IO expander pull enable API with explicit modes - #320

Merged
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:ioe_pull_mode
Aug 17, 2026
Merged

Replace the IO expander pull enable API with explicit modes#320
lovyan03 merged 1 commit into
m5stack:developfrom
ainyan03:ioe_pull_mode

Conversation

@ainyan03

Copy link
Copy Markdown
Contributor

Problem

The pull resistor state of an IO expander pin was split across two virtuals:
setPullMode picked up or down, and enablePull turned 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 setPullMode alone already
established the state. enablePull(pin, true) only set the pull-up bit without
clearing 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

setPullMode now takes gpio_pull_t and establishes the whole state in one
call, 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_Base still return void; making them consistent is left for a
separate change.

Breaking change

enablePull is gone and setPullMode takes an enum instead of a bool. Old
calls do not compile, because neither a 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)

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) and setExtOutput(false) still select up and down as
    before, on both of the expanders they touch
  • all three modes reach the registers, including the enable bit dropping for
    pull_none and being set explicitly for the other two, which is what removes
    the dependency on the reset default
  • pins other than the one addressed are left alone
  • the setter returns true for all three modes

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) |

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) with setPullMode(uint8_t, gpio_pull_t) returning bool for success/failure.
  • Updates M5IOE1 and PI4IOE5V6408 drivers to implement the new explicit pull modes.
  • Updates existing call sites to use pull_none, pull_up, and pull_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.

Comment thread src/utility/PI4IOE5V6408_Class.cpp Outdated
Comment on lines +35 to +52
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;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/utility/M5IOE1_Class.cpp Outdated
Comment on lines +57 to +61
bool first;
bool second;
switch (mode) {
case pull_none:
first = bitOff(pu_reg, bit);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@lovyan03
lovyan03 merged commit 41154f6 into m5stack:develop Aug 17, 2026
27 checks passed
@ainyan03 ainyan03 mentioned this pull request Aug 17, 2026
@ainyan03
ainyan03 deleted the ioe_pull_mode branch August 17, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants