Skip to main content

Cross-EVK Portability — <alp/board.h> and BOARD_* Aliases

One example, two EVKs. The <alp/board.h> facade plus the portable BOARD_* aliases let the same firmware source build unchanged for both official EVKs — the E1M EVK (35 × 35) and the E1M-X EVK (45 × 65) — for every interface the e1m-spec declares as a common role (STANDARD.md §7.2).

How it works

Three pieces, all generated from board.yaml:

  1. ALP_BOARD_<SLUG> compile define — the build emits ALP_BOARD_E1M_EVK or ALP_BOARD_E1M_X_EVK from the board.yaml preset.

  2. <alp/board.h> facade — selects the active board's generated routes header from that define:

    #include <alp/board.h> /* resolves to alp_e1m_evk_routes.h or
    alp_e1m_x_evk_routes.h at compile time */

    Building without any ALP_BOARD_* define is a compile error with a pointed message — set the example's preset:, or pass -DALP_BOARD_E1M_EVK / -DALP_BOARD_E1M_X_EVK by hand.

  3. BOARD_* aliases — each board's routes header defines the same BOARD_* names, mapped to that board's own macros. The mapping comes from the board_alias: field on e1m_routes: entries in the board definition; parity across boards is CI-enforced, so an alias can never silently exist on one EVK only.

#include <alp/board.h>

alp_pwm_t *led = alp_pwm_open(BOARD_PWM_LED_RED, &cfg); /* builds on both EVKs */

Alias catalogue

AliasRoleE1M EVKE1M-X EVK
BOARD_UART_DEBUGConsole UARTEVK_UART_PORT_DEBUGXEVK_UART_PORT_DEBUG
BOARD_UART_ARDUINOArduino UNO UARTEVK_UART_PORT_ARDUINOXEVK_UART_PORT_ARDUINO
BOARD_I2C_SENSORSShared sensor + IO-expander busEVK_I2C_BUS_SENSORSXEVK_I2C_BUS_SENSORS
BOARD_SPI_ARDUINOArduino UNO SPIEVK_SPI_BUS_ARDUINOXEVK_SPI_BUS_ARDUINO
BOARD_CAN0CAN-FD busEVK_CAN_VEHICLE_BUSXEVK_CAN_BUS0
BOARD_DAC0Arduino DAC outputEVK_DAC_ARDUINO_DAC0XEVK_DAC0
BOARD_DAC1Audio line outEVK_DAC_AUDIO_LINE_OUTXEVK_DAC1
BOARD_I2S_AUDIOAudio codec / amp busEVK_I2S_AUDIO_CODECXEVK_I2S_AUDIO
BOARD_PWM_LED_RED / _GREEN / _BLUERGB status LED PWMEVK_PWM_LED_*XEVK_PWM_LED_*
BOARD_PIN_LED_RED / _GREEN / _BLUERGB status LED GPIOEVK_PIN_LED_*XEVK_PIN_LED_*
BOARD_PWM_ARD1..3Arduino PWM headersEVK_ARD_PWM1..3XEVK_ARD_PWM1..3
BOARD_ENC_ROTARYRotary encoder (PEC12R)EVK_ENC_ROTARYXEVK_ENC_ROTARY
BOARD_PIN_ENCODER_SWEncoder push switchEVK_PIN_ENCODER_SWXEVK_PIN_ENCODER_SW
BOARD_PIN_BMI323_INT1BMI323 data-readyEVK_PIN_BMI323_INT1XEVK_PIN_BMI323_INT1

Declaring portability: supported_boards:

A portable example declares the presets it is verified to build for at the top level of its board.yaml:

supported_boards: [e1m-evk, e1m-x-evk]

CI builds one scenario per entry, so portability stays proven rather than claimed. When omitted, the list defaults to the single preset:. This is for examples — per ADR-0011 application firmware still targets one SoM family.

The portability lint

Portability is enforced, not just encouraged. scripts/check_example_portability.py runs over every example that has a board.yaml and is a hard gate in the pr, full, and release quality profiles — so a portability regression fails CI rather than landing quietly.

Its headline rule: a customer-facing example must not #include <zephyr/drivers/...> directly. That's the vendor driver-class layer the portable <alp/*.h> surfaces exist to hide — reach for <alp/display.h>, <alp/pwm.h>, <alp/i2c.h> and friends instead. (Commented-out includes don't trip it; the checker strips comments before matching.)

The other rules it enforces:

RuleSeverityWhat it checks
Zephyr driver includeserrorNo direct #include <zephyr/drivers/*.h>
Chip family matcherrorEvery chips: entry has a manifest whose families: includes the example's own SoM family
supported_boards: parityerrorEach entry has a matching ALP_BOARD_<SLUG> Twister variant in testcase.yaml
Pinout namespaceerrorNo mixing <alp/e1m_pinout.h>/ALP_E1M_* with <alp/e1m_x_pinout.h>/ALP_E1M_X_*
Chip includes declarederrorAn <alp/chips/X.h> include has a matching chips: entry
Ring classificationinfoReports each example as ring1-cross-family / ring2-chip-bound / ring3-som-bound

Rings 2 and 3 are an accepted, intentional category — not migration debt. A chip-bound or SoM-bound example is a legitimate thing to write; the classification exists to make the distribution visible, not to shame it.

The allowlist is reasoned, not a mute button

A handful of examples genuinely have no portable surface to route through yet. Those are named individually with the reason — never silently exempted:

ExampleWhy it's exempt
multicore/rpmsg-v2nmbox.h — raw OpenAMP/MHU mailbox transport for AMP core-to-core messaging; no portable <alp/*.h> IPC surface exists yet
peripheral-io/alp-consolepwm.h (RGB status LED) + gpio.h/pinctrl.h (the on-module Wi-Fi/BLE bridge's own control-transport HAL); a pre-existing gap, with the LED path tracked as separate follow-up work
v2n/v2n-ethernet-dualmdio.h — raw PHY register access for a link-diagnostics demo; no portable MDIO surface exists
v2n/v2n-xspi-flash-readwriteflash.h — no portable <alp/flash.h> surface exists yet

Each entry states why it isn't a migration target. A new entry is only appropriate with a real "no portable surface exists" reason — not as a way to silence the gate.

Scope: only directories containing a board.yaml are checked, at one or two levels under examples/. Zephyr-specific register/bench tools that carry no board.yaml (e.g. examples/aen/*-regcheck/) are outside this check by construction.

When NOT to use the facade

Form-factor-specific firmware (anything touching pads that exist on only one EVK) should include the specific routes header directly — alp_e1m_evk_routes.h / alp_e1m_x_evk_routes.h — and use the EVK_* / XEVK_* macros. The facade exists for the common-role subset, not as a universal indirection.

See also

Questions about this page? Discuss in Community Forum