Full disclosure: the I/O HATs used here are boards I design and sell, and the DUT is one of my own products. The architecture works with any I/O boards.

The problem

Two pains pushed me into building this, and they turn out to be the same problem wearing different hats :).

End-of-line testing. Every manufactured board needs a functional test before shipping. Manual EOL testing is slow and, worse, inconsistent. The human checking board #100 of the day is not the human who checked board #2.

Firmware regression. Every firmware change should re-run the full functional suite against real hardware, and nobody does regression testing by hand more than twice.

Both reduce to: stimulate the DUT's inputs, observe its outputs, compare against expectations. So one bench does both jobs.

The core idea

A Raspberry Pi 4 sits in the middle. On top of it: whichever I2C I/O HATs match the DUT. Need to read the DUT's outputs? Stack a digital-input HAT. Need to drive its inputs? Stack a relay HAT or some other type of output HAT. I'm using I2C HATs, so each HAT has a configurable I2C address, they all share one bus, and everything goes through one Python library. Robot Framework sits on top and turns the Python calls into plain-English test cases and outputs nice, detailed HTML reports.

Contrast with a typical commercial functional test fixture: built for exactly one product, rebuilt from scratch when the product changes. Here, the Pi and HAT stack stay put. Need more I/O for the next product? Stack another HAT, give it an address. In practice, only the.robot file changes.

A lucky twist in my case: since my DUTs are themselves Pi HATs, they connect the same way the bench HATs do, so the stack plus wiring to terminal blocks *is* the fixture. No bed of nails. For any other product you'd put a jig (pogo pins, harness, clamp) between the terminal blocks and the DUT; the test logic doesn't care.

What's on the bench

Current DUT: a board with 6 isolated digital inputs and 6 relay outputs (DI6acDQ6rly I2C HAT), which conveniently exercises everything in one suite: input reading, relay switching, interrupts, and boot/address jumpers.

The bench stack:

Two details I'm fond of:

1. Jumpers emulated by relays. The DUT's boot and address jumpers are each replaced by a relay contact, so the suite can short pins programmatically: activate the boot "jumper, " reset the DUT over I2C, scan the bus, assert it enumerated at the bootloader address. The address-jumper test is a Robot Framework template, a four-row table of jumper index vs. expected address. A broken jumper solder-joint/pad/trace on a manufactured board fails instantly, by name.

2. A deliberately marginal supply. The DUT inputs are driven from a separate 3V supply chosen to sit right at the minimum high-level threshold. Every input test therefore validates the input circuit's *sensitivity*, not just its logic. The inputs also accept both wiring polarities, so a dedicated relay HAT(DQ5rly I2C-HAT) flips the supply and the whole input matrix runs again reversed.

Block diagram

+---------- TEST BENCH -----------+
|                                 | I2C bus + power
|              +------------------------------------+
|              |                  |                 |
|   +----------------------+      |      +--------------------+
|   | Test Bench HAT stack |      |      |        DUT         |
|   |                      |      |      |                    |
|   |  DQ10rly  @ 0x5E     |------------>| boot/addr jumpers  |
|   |  DI16ac   @ 0x40     |<------------| DUT relay outputs  |
|   |  DQ10rly  @ 0x5F     |---+-------->| DUT digital inputs |
|   |  DQ5rly   @ 0x5D     |---+  |      |                    |
|   +----------+-----------+      |      +--------------------+
|              | I2C bus + power  |
|   +----------v----------+       |
|   |   Raspberry Pi 4    |       |
|   +---------------------+       |
+---------------------------------+

Why Robot Framework

No deep selection process: the HAT libraries are Python, I had Robot Framework experience from a previous job, done. pytest would work just as well. The real value is in the Python layer underneath, not the runner. What Robot gives for free: readable keyword-style test cases, HTML report + log per run, and a clean split between test logic and hardware access. The glue layer exposing the HAT library as Robot keywords is thin and linked below.

The suite, briefly

One bench, two jobs

During firmware development the suite is a regression net: flash, run, know in minutes whether the change broke inputs, IRQ handling, or address selection. At end-of-line the *identical* suite is the production gate: jumpers, both polarities, interrupts, relays, LEDs, consistent verdicts across hundreds of boards, failing channel named in the report. One set of test logic, maintained once.

Hard-earned warnings!

Roadmap