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 »