Skip to main content

<alp/peripheral.h> — Core Buses

The most-used header in the SDK. <alp/peripheral.h> provides handle-based wrappers for the four core bus classes: GPIO, I²C, SPI, and UART.

#include <alp/peripheral.h>
#include <alp/e1m_pinout.h> // for E1M_* instance IDs

GPIO

alp_gpio_t *led = alp_gpio_open(&(alp_gpio_config_t){
.pin_id = E1M_GPIO_IO1,
.direction = ALP_GPIO_DIR_OUTPUT,
});
if (led == NULL) {
int err = alp_last_error();
return err;
}

alp_gpio_write(led, 1); // drive high
alp_gpio_write(led, 0); // drive low

bool value;
alp_gpio_read(led, &value);

alp_gpio_close(led);

Input with pull-up + IRQ

alp_gpio_t *btn = alp_gpio_open(&(alp_gpio_config_t){
.pin_id = E1M_GPIO_IO0,
.direction = ALP_GPIO_DIR_INPUT,
.pull = ALP_GPIO_PULL_UP,
});

void on_press(alp_gpio_t *pin, void *user) { /* ... */ }

alp_gpio_set_irq(btn, ALP_GPIO_IRQ_FALLING_EDGE, on_press, NULL);
alp_gpio_enable_irq(btn);

Config struct

FieldTypeNotes
pin_idalp_pin_id_tUse E1M_GPIO_IO<N> from <alp/e1m_pinout.h>.
directionalp_gpio_dir_tINPUT / OUTPUT.
pullalp_gpio_pull_tNONE / UP / DOWN.
drivealp_gpio_drive_tPUSH_PULL / OPEN_DRAIN.

I²C

alp_i2c_t *bus = alp_i2c_open(&(alp_i2c_config_t){
.bus_id = E1M_I2C0,
.bitrate_hz = 400000u, // 100k / 400k / 1M / 3.4M (per-SoC)
});

uint8_t buf[2] = { 0x00, 0x42 };
alp_i2c_write(bus, /* addr */ 0x50, buf, sizeof(buf));

uint8_t reg = 0x00;
uint8_t rx[16];
alp_i2c_write_read(bus, 0x50, &reg, 1, rx, sizeof(rx));

alp_i2c_close(bus);

Config struct

FieldTypeNotes
bus_idalp_bus_id_tE1M_I2C0E1M_I2C<N>.
bitrate_hzuint32_tBus speed. Capped by <alp/soc_caps.h> at _open().
address_modealp_i2c_addr_t7BIT / 10BIT.

Common operations

FunctionDescription
alp_i2c_write(bus, a, buf, n)Write n bytes.
alp_i2c_read(bus, a, buf, n)Read n bytes.
alp_i2c_write_read(bus, a, tx, n, rx, m)Repeated-start: write n, read m.
alp_i2c_scan(bus, addrs, max, *count)ACK-probe and fill addrs[].

SPI

alp_spi_t *spi = alp_spi_open(&(alp_spi_config_t){
.bus_id = E1M_SPI1,
.freq_hz = 8000000u,
.mode = ALP_SPI_MODE_0,
.bits_per_word = 8,
.cs_pin_id = E1M_GPIO_IO5,
});

uint8_t tx[4] = { 0xAA, 0x55, 0xC3, 0x3C };
uint8_t rx[4];
alp_spi_transfer(spi, tx, rx, sizeof(tx));

alp_spi_close(spi);

Config struct

FieldTypeNotes
bus_idalp_bus_id_tE1M_SPI0E1M_SPI<N>.
freq_hzuint32_tSCLK frequency.
modealp_spi_mode_tMODE_0 / MODE_1 / MODE_2 / MODE_3.
bits_per_worduint8_tTypically 8; some SoCs support 16/32.
cs_pin_idalp_pin_id_tPer-transfer chip-select pin.

UART

alp_uart_t *uart = alp_uart_open(&(alp_uart_config_t){
.bus_id = E1M_UART0,
.baudrate = 115200u,
.data_bits = 8,
.stop_bits = ALP_UART_STOP_1,
.parity = ALP_UART_PARITY_NONE,
.flow_control= ALP_UART_FLOW_NONE,
});

const char *msg = "hello\n";
alp_uart_write(uart, (const uint8_t *)msg, strlen(msg));

uint8_t buf[64];
size_t got;
alp_uart_read(uart, buf, sizeof(buf), &got);

alp_uart_close(uart);

RX ring buffer (interrupt-driven)

When the consumer can't keep up, enable a caller-supplied ring buffer:

#include <alp/peripheral.h>

static uint8_t rx_ring_storage[256];
alp_uart_rx_ringbuf_t ring;
alp_uart_rx_ringbuf_init(&ring, rx_ring_storage, sizeof(rx_ring_storage));
alp_uart_rx_ringbuf_attach(uart, &ring);

// Later, pull what's there without polling:
size_t got = alp_uart_rx_ringbuf_pop(&ring, buf, sizeof(buf));

CONFIG_ALP_SDK_UART_RX_RINGBUF=y enables the ring path. See the uart-rx-ringbuf example.

Capability validation

Every _open() cross-checks the requested config against <alp/soc_caps.h>. Example failures:

ConfigResult on a 12-bit SoC
alp_adc_open(.resolution_bits = 16)NULL + alp_last_error() == ALP_ERR_OUT_OF_RANGE
alp_i2c_open(.bitrate_hz = 5_000_000)NULL + OUT_OF_RANGE (SoC max 3.4M)
alp_uart_open(.bus_id = E1M_UART9)NULL + NOSUPPORT (instance not routed)

E1M portability

Instance IDs come from <alp/e1m_pinout.h> and are portable across every conformant SoM. The companion E1M_<CLASS>_COUNT macros tell you how many instances the E1M standard guarantees:

for (size_t i = 0; i < E1M_I2C_COUNT; i++) {
alp_i2c_t *bus = alp_i2c_open(&(alp_i2c_config_t){
.bus_id = E1M_I2C0 + i,
.bitrate_hz = 100000u,
});
/* ... */
}

Apps that stay within the portable bound work on every E1M SoM, present and future.

See also

Questions about this page? Discuss in Community Forum