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...

Read more »