There is a pattern I have seen on almost every integrated hardware project that runs into serious trouble. It starts well: the team is competent, the requirements are clear, and the individual components all work in isolation. Then integration begins, and everything slows to a crawl. Changes that should take a day take two weeks. A firmware update requires a PCB revision. A sensor swap cascades into a firmware rewrite. The team is competent. The problem is architectural.
The root cause is almost always the same: nobody specified the interfaces.
The Problem With Unspecified Interfaces
When you build a monolithic embedded system, every component knows too much about every other component. The firmware knows the exact memory layout of the sensor driver. The sensor driver knows the specific I2C timing assumptions of the firmware. The mechanical enclosure was designed around the exact PCB form factor that existed when CAD was started.
Nothing is explicitly coupled. But everything is implicitly coupled. And implicit coupling is the most dangerous kind, because it is invisible until the moment it breaks.
When you change one thing, you do not know what you have broken. You find out at integration time. At that point, the team is doing archaeology, digging through undocumented assumptions to find which one is causing the failure.
An interface contract is not documentation added after the fact. It is a specification written before either side of the interface is built. It defines what one module promises to deliver to another, in what form, at what rate, and what happens when it cannot deliver. Once that contract is stable, each side can be built, tested and changed independently.
What a Contract Actually Specifies
For the servo arm I architected for PCB testing, the system had five distinct modules: control, power, sensing, communication and integration. Before any of them were designed in detail, I specified the interface between each adjacent pair. Every contract answered four questions:
- What data passes across this boundary? Format, units, precision, whether it is a stream or a request-response.
- At what rate? Max frequency, whether timing is hard or best-effort, what jitter is acceptable.
- What are the error conditions? What does the downstream module receive when the upstream module fails, times out, or is initialising.
- Who owns the contract? Which team is responsible for maintaining it when requirements change.
A short, specific, versioned agreement. The control module team does not need to know how the sensor reads data, what IC it uses, or how it handles calibration. They only need to know what arrives at their boundary and under what conditions.
What Happens When the Contract Is Stable
On the servo arm project, the vision system requirements changed midway through development. A new camera was selected with different resolution and a different interface protocol. In a monolithic architecture, that change would have cascaded: new drivers, firmware changes, motion planning updates, possibly a PCB respin to accommodate the new connector.
Because the sensing module's interface contract was stable, still delivering the same 32-byte struct at 200 Hz with the same error semantics, the change was entirely contained within the sensing module. The control module did not change. The power module did not change. The communication module did not change.
That is what a contract buys you. Development speed in the moment, and more importantly the ability to evolve one part of the system without touching the rest.
How I Apply This on a Real Build
Before any module is designed in detail, I run an interface specification session. I bring together whoever will build each module and we work through the boundary conditions together. What does the power module need to know about load characteristics? What does the communication module need to know about packet timing? What does the firmware need to know about the sensor's startup sequence?
These are not long meetings. An hour per interface pair is usually enough. The output is a short document per interface, version controlled, reviewed by both sides, and referenced by both teams throughout development. When someone wants to change an interface, they raise it as a contract change. The other team sees it. They review it. They agree to it.
The distinction matters. A code change is invisible to the team on the other side. A contract change is a formal signal that the boundary has moved, and the other team needs to know.
The Five Modules and What Each Contract Covers
For integrated embedded systems I consistently structure around five module types, each with well-defined contract surfaces:
- Control module. The decision-making core. Its contracts specify what inputs it requires from sensing and what commands it emits to actuation. The firmware team owns the control module contracts.
- Power module. Energy management. Its contracts specify what load profiles each module is permitted to draw, startup sequencing requirements, and what the downstream modules receive during brownout conditions.
- Sensing module. Data collection. Its contracts specify data formats, update rates, coordinate frames and error flag conventions. The sensing team cannot change these without a contract review.
- Communication module. External interfaces. Its contracts specify message schemas, transport protocols and latency guarantees. Anyone who depends on the communication module should be able to write tests against the contract before the module exists.
- Integration module. The baseplate that ties everything together. This is where shared timing references, power rails and physical connectors are defined. It is the most constrained contract of all, because changes here affect everything.
When This Approach Is Most Valuable
Interface contracts matter most when two or more of these conditions are true: the team has more than three people working in parallel, the development timeline is longer than three months, or requirements are expected to change after development has started.
For a single-person prototype over two weeks, the overhead probably outweighs the benefit. Two teams working simultaneously on different layers, electronics and firmware, or mechanical and integration, and the cost of an unspecified interface compounds fast.
If the electronics team changes their sensor IC tomorrow, which teams need to update their code? If the answer is more than one team, the interface is not specified clearly enough.
What This Has to Do With Architecture
Interface specification is an architecture decision. The choice of where to draw module boundaries, what each boundary promises to deliver, and how changes to those promises are governed determines how easily the system can be built, tested, debugged and evolved.
A system with well-specified interfaces is a system I can reason about completely. I know exactly what each module depends on and what it provides. I can identify where a failure could originate. I can tell you, before a single line of code is written, which modules are on the critical path and which can be developed in parallel.
That is what architecture produces. A system that is legible to the person who built it, to whoever maintains it, to whoever debugs it two years later at three in the morning.