This is the fast path from nothing to a passing hardware-in-the-loop test: install the library, wire one board to a BenchPod, and write a test that flashes firmware and checks it boots. If you've written a pytest test before, this will feel familiar — the hardware is just behind a fixture.
1. Install
pip install pytest embeddedciFlashing shells out to OpenOCD, which needs to be a recent build (the stock 0.12.0 package is too old). The quickest route is xPack OpenOCD: npm install -g @xpack-dev-tools/openocd. The pytest framework docs explain why, and the troubleshooting page covers the error you'll see if it's the wrong version.
2. Wire the target
The pod has no dedicated SWD/UART pins — it exposes 12 identical logic-analyzer channels (pins.pin_1 … pins.pin_12), and any DUT signal can be wired to any of them. So you map your bench's wiring once, at the top of the test, instead of relying on role-named pins:
- SWCLK / SWDIO — the two SWD lines for flashing.
- UART — the pod samples the DUT's TX on the rx channel and drives the DUT's RX on the tx channel.
- Target power — the eFuse rail that feeds the board (
--benchpod-efuse; 1 = internal 5V).
Pull-ups are available on LA1-8 only (LA1/2 = 4.7k, LA3/4 = 2.2k, LA5-8 = 10k); LA9-12 have none — so put an open-drain bus (like I2C) on a pull-up-capable channel.
3. Write the test
import pytest
from types import SimpleNamespace
@pytest.fixture
def wiring(pins):
# This bench's wiring: DUT signal -> BenchPod LA channel. Edit for your board.
return SimpleNamespace(
swclk=pins.pin_11, swdio=pins.pin_12,
uart_rx=pins.pin_5, uart_tx=pins.pin_4, # pod samples DUT TX / drives DUT RX
efuse=pins.efuse,
)
@pytest.mark.hardware
def test_firmware_boots(benchpod, wiring, firmware):
# Flash the firmware over SWD and power the target from the eFuse.
assert benchpod.flash(
file=firmware, target="target/stm32f4x.cfg",
swclk=wiring.swclk, swdio=wiring.swdio,
target_power=wiring.efuse,
).ok
# Power-cycle and assert the boot banner shows up.
benchpod.power_off(wiring.efuse)
benchpod.power_on(wiring.efuse, delay=1.5)
with benchpod.open_uart(rx=wiring.uart_rx, tx=wiring.uart_tx) as uart:
assert uart.read_until(r"APP_OK", timeout=12), uart.textbenchpod, pins, and firmware are fixtures the library registers for you — a connected pod, the pod's LA channels + eFuse, and the firmware path. The wiring fixture is your own: it names which channel each signal is on.
4. Run it
pytest test_firmware.py \
--benchpod-connection=192.168.1.213 \
--benchpod-firmware=build/app.elfPoint --benchpod-connection at the pod's IP, a serial port like /dev/ttyACM0, or embeddedci:<device-name> for a pod in the cloud. That's a passing HIL test — firmware flashed to real silicon and verified by its own boot output.
It stays green without hardware
Leave the connection off and the benchpod fixture skips instead of failing:
pytest test_firmware.py # no --benchpod-connection → test skips, suite stays greenSo the same test sits happily in a suite that runs on laptops and plain CI runners, and only does real work when a pod is actually wired up.
Where to go next
- Make the pod emulate a sensor and assert on the I2C bus.
- Run this exact test on real hardware from GitHub Actions.
- Drive the bench from an AI agent with the MCP server.