Skip to content

i2c_bus

I2C bus abstraction.

Two types, because a controller and a device are not the same thing:

  • [I2cController] owns the physical driver P (by value) and speaks to the whole bus: it can scan the address space and hand out a device for one slave.
  • [I2cDevice] borrows the controller, bakes in one slave address, and is the [Bus] a logical driver talks through — every op it frames carries that address, unshifted, as tx[0].

Before this split a single I2cBus baked one address in, so a bus scan or a second device had to bypass Layer 2 and call exchange with the tx[0]-is-address convention by hand — exactly the raw-transfer reach-round the layering exists to stop. scan() and device(addr) keep it on the near side of the boundary.

The physical driver is held by value, not behind a &'static dyn, so one Once<I2cController<Esp32I2c>> holds the whole stack. A caller with a &'static driver can still pass it: &T is itself a [PhysicalTransfer] (the blanket impl in hal::bus).

Both the per-op transfer and each scan probe take an [api::mutex] lock around the physical exchange, and [I2cDevice] holds it for the whole op list so a compound transaction (write-then-read) is never split once a board hands the same &'static I2cController to more than one task. That lock is the kernel’s priority-inheritance mutex, a syscall the kernel refuses from interrupt context — so nothing here may be called from an ISR. An interrupt that must touch the peripheral should own the physical driver directly (exchange is &self) and coordinate with the task side through a CsCell.

struct I2cController<P> { /* private fields */ }

An I2C controller: owns the physical driver and addresses the whole bus.

struct I2cDevice<'a, P> { /* private fields */ }

One slave on an [I2cController], addressed by addr. This is the [Bus] a logical driver is handed.

Implements: Bus