Skip to content

Board contract: a peripheral hook should hand a driver the bus, not the pins #22

Description

@bdbarnett

Found via a failure that presented as something else entirely: opening a camera killed the touchscreen, which made an input-polling loop block on I2C timeouts, which was measured and reported as a 9x slowdown caused by appdev (see #21, closed). appdev was faithfully reporting a broken bus.

The mechanism

On the P4 panel board, board_config.py:77 builds the shared bus and exposes it:

i2c = I2C(1, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=400_000)

board_peripherals.py then hands the camera pin numbers rather than that object:

_CAM_SDA = 7
_CAM_SCL = 8

The driver, given pins and an i2c port argument defaulting to 0, creates a second ESP-IDF i2c_master_bus on port 0 with those same pins. Two I2C peripherals are then driven onto one pair of wires through the pin matrix. The touch controller stops answering with OSError: [Errno 116] ETIMEDOUT, while the camera works, so the symptom appears in an unrelated subsystem.

The comment directly above those constants says the camera shares the panel's bus. It is accurate and it did not prevent the bug, because sharing a bus and being handed two pin numbers look identical from the driver's side.

The rule this argues for

A peripheral hook hands a driver a bus, not the pins that make one. Pins describe wiring; a bus object carries ownership. A driver given a bus can only join it. A driver given pins has no way to discover who already owns them, and the failure is silent, remote and blamed elsewhere.

Concretely for this board: camera() should pass board_config.i2c (or, where a native driver needs the port rather than the object, the port number that bus is on), not _CAM_SDA / _CAM_SCL.

This is not specific to cameras or to this board. Any hook that hands out pins for a bus something else already owns has the same latent bug, so this is worth stating in the board contract rather than fixed once.

Open questions

  • Whether the contract should say bus object or port number. The object is safer but a native C driver may need the port, and MicroPython's machine.I2C port numbering and ESP-IDF's i2c_master_bus port numbering are the same space, which is exactly what made this collision possible.
  • Whether drivers that accept pins at all should be required to fail loudly when those pins are already claimed by a different port, as a backstop for boards that get this wrong.
  • Whether boarddev should expose bus ownership at all, so a driver can ask rather than guess.

The camera-side half is being fixed in cameraif independently: attach to the existing bus for that port rather than creating a second, and fail loudly rather than silently when the pins are already claimed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions