adafruit / adafruit/Adafruit_Wippersnapper_Arduino

v2: refcounted shared power-rail/pin ownership + sleep-mode rail handling

Open
#952 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
54
Forks
56
Avg merge
2d 12h
Merged PRs (30d)
11

Description

### Context

adafruit/Adafruit_Wippersnapper_Arduino#936 added a panel **power-enable rail** to the display Add (`ws.display.Add.power`, a prerequisite `ws.digitalio.Add` — see adafruit/Wippersnapper_Protobuf#208). The PR implements deliberately **two-party** arbitration, per review:

- pin free at Add → the display driver *owns* the rail (enable level before init, released to disable level in its destructor, i.e. display Remove/replace);
- pin already registered with the digitalio controller → **pass through** only if it's an `OUTPUT` already at the enable level, else the Add fails ("Power-enable pin unavailable").

That's correct for the direct display↔digitalio conflict, but it does not generalize. This issue captures the gaps so the design discussion has a home.

### Failure scenarios (current behavior, verified against the code)

Assume one rail (e.g. GPIO15) powers the display **and** I²C sensors **and** NeoPixels, with components added/removed in arbitrary order at check-in or runtime:

| Order of events | What happens today |
|---|---|
| digitalio Add (OUTPUT@enable) → display Add | ✅ pass-through works |
| display Add → **digitalio Add, same pin** | ❌ digitalio never queries display ownership — it reconfigures the pin per its own Add (possibly INPUT or LOW) and kills the panel |
| display owns rail → **display Remove** | ❌ driver dtor drives the rail to disable — I²C sensors / NeoPixels on the same rail are power-cut silently |
| passed-through digitalio pin → **digitalio Remove** | ❌ `DigitalIOHardware` dtor does `LOW + pinMode(INPUT)` — the rail floats and the panel dies with no notification path |
| two displays sharing one rail | ❌ ownership is private to each driver instance; the second display sees the pin as "free", both own it, first Remove powers down the second |
| I²C bus init when `PIN_I2C_POWER` == the rail | ❌ `i2c/hardware.cpp` drives the macro pin directly — it even flips it through `INPUT` to sniff rest polarity (momentary float/brownout), invisible to any check |
| NeoPixel power pin | v2 `pixels` doesn't model a power pin at all |

Root cause: **ownership is pairwise, one-directional, and implicit** — there is no shared registry, no refcount, and time-of-Add-only checking.

### Deep sleep / light sleep (v2 sleep component)

The sleep controller configures wakeups and sleeps; it does **not** tear down component hardware and never calls `gpio_hold_en`:

- **Deep sleep**: the digital GPIO matrix powers down → all non-RTC pins float. An active-high rail sags off (accidentally the right power-saving outcome, by luck). An **`is_inverted` rail may float ON through sleep** (or oscillate, per board pulls) — burning the battery budget the sleep exists to save. Correct handling needs `gpio_hold_en` + `gpio_deep_sleep_hold_en` at the disable level, or an RTC GPIO.
- **Wake from deep sleep = reset** → check-in replay → componentAdds re-run → the add-order nondeterminism above repeats on *every wake cycle*.
- **Light sleep**: GPIO state is retained and execution resumes — the panel stays lit between sensor reads, so no display power saving unless something deliberately drops the rail (policy question).

### Proposed direction

A central **refcounted pin/rail registry** (in the digitalio controller, or a small `PinOwnershipRegistry`):

- `acquire(pin, level [, flags])` / `release(pin)`: same-level acquires bump a refcount (add-order independence falls out for free); a conflicting-level acquire fails the component Add deterministically; the rail is only driven to its disable level when the **last** holder releases.
- **Symmetric**: digitalio Add/Remove, display power/backlight, `PIN_I2C_POWER`, and a future pixels power pin all go through it — no component can stomp or power-cut another's rail.
- **Sleep hook**: a pre-sleep pass over held rails — `gpio_hold` the appropriate level for deep sleep (critical for inverted rails); a future proto policy flag (e.g. `keep_powered_during_sleep`) covers wake-on-display use cases; light sleep policy (blank/power-down the panel?) decided per component.

### Scope

Touches digitalio, display, i2c, pixels, and sleep — hence split out of #936 rather than grown inside a board-support PR.

Refs: adafruit/Adafruit_Wippersnapper_Arduino#936 (review discussion), adafruit/Wippersnapper_Protobuf#208 ("prerequisite components" thread).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Contributor guide

No contributing guide indexed for this repository

Research direction

Trace power ownership through the digitalio controller, ws.display.Add.power, DigitalIOHardware destruction, i2c/hardware.cpp, pixels, and the v2 sleep component. Start by mapping each direct pin operation and its add/remove path, then review the referenced issues for design constraints. Done means the agreed registry and sleep behavior cover every listed component without order-dependent rail changes.

Written by the indexing model from the issue text.

Assessment

Tech stack
arduino, cpp
Domain
embedded-iot
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.