Back to resources
HIL
CI
Testing

A Pod Per Board: Cover Your Whole Test Matrix Without Rewiring

One debug probe means constant cable-swapping. BenchPods are cheap enough to leave one wired to every target — so CI fans out across your whole board matrix and nobody touches the bench.

Edward Viaene · June 11, 2026 · 3 min read

A real product rarely has one target. There's the STM32 main board and the nRF radio module; this hardware revision and the last one you still ship; the variant with the extra sensor. Testing all of them on hardware usually means a single debug probe and a lot of cable-swapping — plug in board A, flash, test, unplug, plug in board B. That's fine at a desk. It's hopeless in CI, where there's nobody to move the cable.

The BenchPod is designed to make that problem go away in two complementary ways.

One pod handles any SWD target

A single pod isn't tied to one chip. Flashing goes through a standard OpenOCD target config, so covering a different MCU is a one-line change:

bp.flash(file="app.elf", target="target/stm32f4x.cfg", ...)   # STM32F4
bp.flash(file="app.elf", target="target/stm32h7x.cfg", ...)   # STM32H7
bp.flash(file="app.elf", target="target/nrf52.cfg",    ...)   # nRF52

Same pod hardware, same wiring pattern, a heterogeneous set of targets. So your fleet can be identical pods even when your boards aren't.

A pod per board, wired once

The bigger idea is that BenchPods are built to be inexpensive — cheap enough that you don't share one across boards, you give every target its own. Wire each DUT to its own pod once, give it a stable name, and leave it connected. Nothing ever gets disconnected; CI just talks to whichever pod it needs.

To keep per-board coverage even cheaper, a smaller mini variant — the same flashing, power control, UART, and logic-analyzer features without the analog front-end — is on the roadmap, for the many targets that don't need a 16-bit ADC and DAC hanging off them.

The result is a standing rack of always-connected boards instead of one bench you keep re-cabling.

Naming and routing

Each pod connects out to embeddedci.com and gets a stable, URL-safe name on the BenchPod page (benchpod-stm32f4, benchpod-nrf52, …). A test selects one by name, and the same test runs against any pod by changing one string:

pytest --benchpod-connection=embeddedci:benchpod-stm32f4

Per repository, you choose which devices that repo's GitHub Actions are allowed to drive — Any device, or a specific list — so a fleet can be shared across teams without anyone being able to grab a board they shouldn't.

Fan out in CI

Once every board is named and always connected, a test matrix is just a GitHub Actions matrix — one job per device, all running in parallel, each flashing and testing its own target:

permissions:
  id-token: write
  contents: read

jobs:
  hil:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - device: benchpod-stm32f4
            target: target/stm32f4x.cfg
          - device: benchpod-nrf52
            target: target/nrf52.cfg
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-python@v6
        with: { python-version: "3.12" }
      - run: pip install pytest "embeddedci[cloud]"
      - run: |
          pytest hil/ -v \
            --benchpod-connection=embeddedci:${{ matrix.device }} \
            --benchpod-firmware=build/${{ matrix.device }}.elf

Every board in your matrix gets flashed and tested on every push, in parallel, with no human at the bench and nothing to reconnect. Add a new target? Wire up another pod, name it, and add a line to the matrix.

For the auth model behind embeddedci:<device-name> (GitHub OIDC, no stored secrets) see Run HIL in GitHub Actions; for the test API, the pytest framework docs.