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