Skip to main content

<alp/i2c_regfile.h> — Register-File I²C Target

A helper that exposes a caller-owned RAM buffer as a register-mapped I²C target (slave).

The byte-granular target callbacks in <alp/peripheral.h> mirror the wire protocol faithfully — which means almost every target application layers the same "register-mapped peripheral" state machine on top of them. This header ships that state machine once, so applications stop re-pasting it.

#include <alp/i2c_regfile.h>

The state machine

From the external controller's point of view, the device behaves like classic register-mapped silicon:

Wire eventEffect
Controller write, byte 0 (after a (re)START)Latches the register pointer, taken modulo the file length (EEPROM-style wraparound)
Controller write, byte 1…Stores into the backing buffer at the pointer; auto-increment with wraparound
Controller readStreams the backing buffer from the pointer; auto-increment with wraparound
STOPRe-arms "next written byte is the pointer" for the following transaction

Because the pointer wraps modulo len on every access, the controller can never index outside the buffer.

Quick example

static volatile uint8_t regs[32] = { [0] = 0xA5 }; // reg 0 = ID
alp_i2c_regfile_t *rf;

alp_status_t rc = alp_i2c_regfile_open(ALP_E1M_I2C0, /* own_addr_7bit */ 0x42,
regs, sizeof regs, &rf);
if (rc != ALP_OK) { /* degrade -- see availability below */ }

// Registers 0..1 stay read-only (ID / status); 2..31 are controller-writable.
alp_i2c_regfile_set_write_window(rf, /* first */ 2, /* count */ 30);

alp_i2c_regfile_stats_t st;
alp_i2c_regfile_stats(rf, &st); // st.writes_seen, st.reads_seen

alp_i2c_regfile_close(rf); // idempotent; buffer stays caller-owned

The buffer is the API

The backing buffer is caller-owned, which is the point of the design: firmware publishes state by plain (volatile) stores into it and observes controller writes by reading it back. There is no extra API between the ISR-context callbacks and the application thread.

:::caution Two rules the buffer imposes

  • Prime regs before calling open. The callbacks start firing as soon as it returns — set your ID register and defaults first.
  • Declare it volatile when the application thread polls it. The buffer is written from ISR context. :::

Functions

CallReturns
alp_i2c_regfile_open(bus_id, own_addr_7bit, regs, len, &out)ALP_OK; ALP_ERR_INVAL on NULL regs/out, len == 0, or an out-of-range address; ALP_ERR_NOMEM when the helper (or target) handle pool is exhausted; otherwise the alp_i2c_target_open failure code. bus_id is a studio-resolved instance id in the same space as alp_i2c_open; own_addr_7bit is 0x08..0x77; regs must stay valid until close.
alp_i2c_regfile_set_write_window(rf, first, count)ALP_OK; ALP_ERR_INVAL on NULL rf or a window that does not fit the register file.
alp_i2c_regfile_stats(rf, &out)ALP_OK; ALP_ERR_INVAL on NULL rf / out.
alp_i2c_regfile_close(rf)void. Idempotent on NULL. Unregisters the target; no callback touches the backing buffer after it returns, and the buffer stays caller-owned throughout.

Write windows

The whole file is controller-writable by default. alp_i2c_regfile_set_write_window() restricts writes to count registers starting at first; everything outside becomes read-only. count == 0 makes the whole file read-only. Reads are never restricted.

Out-of-window writes are dropped silently, but the register pointer still auto-increments — mirroring real silicon with read-only ID / status registers.

Call it right after open, before controller traffic: the window is read from ISR context and is not updated atomically against an in-flight transaction.

Stats

alp_i2c_regfile_stats_t answers "is the controller talking to us at all":

FieldMeaning
writes_seenuint32_t — payload bytes received (register-pointer bytes excluded)
reads_seenuint32_t — bytes streamed out to the controller

Both start at zero at open and saturate only by uint32_t wraparound. They are updated from ISR context, so a snapshot taken mid-transaction may be one byte stale — fine for bench observability, not a transaction log.

Availability

Availability tracks alp_i2c_target_open exactly — this helper is a pure layer over the portable target API and degrades with the same status codes:

  • ALP_ERR_NOSUPPORT — the backend or controller driver lacks target mode. On Zephyr that means CONFIG_I2C_TARGET plus a driver implementing target_register.
  • ALP_ERR_NOT_READY — the bus alias is unset.

Applications must degrade cleanly on both.

:::note On native_sim, nothing drives the bus The emulated controller accepts the registration, but nothing external ever talks to it — so the callbacks never fire and writes_seen / reads_seen stay at zero. That exercises the open/close path, not the state machine. :::

ABI status

[ABI-EXPERIMENTAL] — new in v0.9. Tracks the [ABI-EXPERIMENTAL] alp_i2c_target_* surface it wraps. Pin your SDK to a specific commit if you depend on it.

See also

Questions about this page? Discuss in Community Forum