Skip to main content

i2c-slave

Make this MCU answer on the bus as a register-mapped I²C target (slave), using the register-file helper from <alp/i2c_regfile.h> — a pure layer over the portable alp_i2c_target_* surface in <alp/peripheral.h>.

Both were added in v0.9 and are marked [ABI-EXPERIMENTAL].

Source: examples/peripheral-io/i2c-slave/.

What it does

  1. alp_init() — checked; the example bails out on failure.
  2. Primes an 8-byte g_regs[] array with 0xA0..0xA7.
  3. alp_i2c_regfile_open(BOARD_I2C_SENSORS, 0x42, g_regs, 8, &rf) — claims the bus as a target at 7-bit address 0x42 and exposes the caller-owned buffer as a classic register file.
  4. alp_i2c_regfile_set_write_window(rf, 2, 6) — carves out read-only registers. Registers 0–1 become device-ID style: writes to them are dropped.
  5. Five one-second ticks, each printing alp_i2c_regfile_stats() counters (writes_seen / reads_seen) plus the first four register values.
  6. alp_i2c_regfile_close(rf) — unregister and release the bus.

The helper ships the state machine every target application used to hand-roll: the first written byte latches the register pointer, subsequent bytes store with wraparound auto-increment, reads serve from the pointer, and STOP re-arms the pointer latch.

When the register-file idiom doesn't fit

Command/response protocols and FIFOs don't map onto a register file. Drop down to the raw byte-granular callbacks — alp_i2c_target_open() with on_write / on_read / on_stop in <alp/peripheral.h>. The example's own git history shows this same register file hand-rolled on those callbacks.

board.yaml

som:
sku: E1M-AEN801

preset: e1m-evk
supported_boards:
- e1m-evk
- e1m-x-evk

pins:
- { e1m: E1M_I2C0, macro: EVK_I2C_BUS_SENSORS, doc: "Shared sensor + IO-expander + INA236 bus" }

cores:
m55_hp:
app: ./src
peripherals:
- i2c

diagnostics:
log_level: info

prj.conf adds one line that board.yaml derivation does not cover:

CONFIG_I2C_TARGET=y

That's an app-specific knob: it compiles the controller driver's target-mode hooks so alp_i2c_target_open can register this MCU as a bus target.

Availability

Target mode needs controller-driver support — on Zephyr that means CONFIG_I2C_TARGET plus a driver implementing target_register. The helper degrades exactly as alp_i2c_target_open does: backends or drivers without target mode fail the open with ALP_ERR_NOSUPPORT, which the example handles by printing a diagnostic and exiting.

Expected output

On native_sim the emulated controller accepts the registration, but nothing ever drives the emulated bus, so the ticks show writes_seen=0:

[i2c-slave] listening @ 0x42 on BOARD_I2C_SENSORS
[i2c-slave] tick 0 writes_seen=0 regs={0xa0,0xa1,0xa2,0xa3,...}
...
[i2c-slave] done

Test setup (real hardware)

  1. Flash this example onto board A.
  2. Drive it from board B — adapt i2c-master to point at 0x42, or probe from a USB-I²C adapter. i2ctransfer -y 0 w1@0x42 0x00 r4 should return 0xa0 0xa1 0xa2 0xa3.
  3. Wire SDA–SDA, SCL–SCL, GND–GND. Add external 4.7 kΩ pull-ups if neither board provides them.
  4. Board A's console shows writes_seen incrementing. Remember registers 0–1 are read-only here, so writes to them are dropped.

Before flipping the target on, run i2c-scanner from board B to confirm 0x42 isn't already taken.

Status

Two twister rows, one per EVK route table, both on native_sim/native/64; the harness latches on [i2c-slave] done. The register-file behaviour itself is a bench signal — nothing drives the emulated bus in CI.

See also

Questions about this page? Discuss in Community Forum