Close

Firmware Variants as a Scaling Tool, Not a Maintenance Burden

pat-flemingPat Fleming wrote 02/08/2026 at 17:07 • 4 min read • Like

Early in this project, firmware variants were not a design goal. They were a necessity forced by two constraints that appeared almost immediately: limited memory on small MCUs and the need for a system that could be administered by non-programmers.

The techniques described here are not new. Compile-time guards, firmware variants, and simplified installation flows all exist independently in other projects. What is less common is treating them as a coherent system pattern, shaped deliberately by constraints like memory limits, administrative usability, and long-term growth. The value here is not invention, but integration: documenting how these decisions came together in a real, evolving system, and why they proved sustainable over time.

Trying to solve both problems with a single, feature-rich firmware image quickly proved unrealistic. Not everything fit, and not everything should.

What began as a pragmatic response to constraint later became a key mechanism for scaling the system without fragmenting it.

The Initial Problem: Memory, Administration, and Growth

Each device in the system performs a narrow role. Attempting to support every possible capability in every firmware image led to predictable issues:

At the same time, the system could not assume that the person installing or maintaining devices would be comfortable building firmware. Requiring end users to manage sketches, libraries, and toolchains was not acceptable outside of tightly supervised environments.

The problem was not flexibility — it was control, both technical and administrative.

Why the “Example Sketch” Model Didn’t Scale

Many open-source embedded projects handle variation by providing separate example sketches:

This model is effective for learning and experimentation. It scales poorly as a system.

As example collections grow, they tend to:

Over time, it becomes unclear which example represents the “real” system. Fixes must be applied repeatedly, and knowledge fragments along with the code.

For a system expected to evolve over years, this approach created more problems than it solved.

The Shift: One Codebase, Many Variants

Instead of multiplying sketches, the system moved toward a single common source with guarded firmware variants.

// Capability guard: the AP exists only in variants that include it.
#if defined(USE_WIFI_AP)
  wifiAp_.begin();
  webUi_.begin();        // captive portal / setup pages live here
#else
  // No AP, no web UI, no associated dependencies.
#endif

In this model:

This approach directly leveraged C++ characteristics:

Development effort was concentrated in the core, not spread across example implementations.

Variants as an Enabler, Not a Constraint

Initially, variants existed to solve immediate problems:

Over time, they enabled something more important.

As understanding of electronics improved and new devices were introduced—new sensors, new interfaces, new capabilities—the system did not need to be restructured. Support was added to the core, and a variant exposed that capability where appropriate.

Expansion followed a predictable pattern:

The system could grow without forcing every device to grow with it.

Firmware Delivery Became Predictable

The documentation site does more than describe firmware—it delivers it. Each supported device/variant has a documentation entry that presents an install action, so firmware is loaded over USB directly from the documentation page. This removed the Arduino-IDE workflow from the end-user path and made deployment repeatable across devices and updates.

Once variants were deliberate artifacts produced from a single source, firmware delivery could be simplified.

Firmware Upload From User Documentation
Documentation page provides firmware install for each variant; the browser talks directly to the MCU over USB, so deployment is a single repeatable step.

The Arduino IDE and CLI remain in use, but only as developer tools to produce known firmware images. End users never interact with source code, libraries, or build systems.

Instead:

This eliminated many common error classes in example-driven workflows and shifted responsibility to where it could be managed once.

Design Pattern Summary (Reusable)

Problem:
Scaling firmware across constrained devices while supporting non-programmer users.

Anti-Pattern:
Multiple example sketches that diverge over time.

Pattern:
One shared codebase with capability-based, guarded firmware variants.

Key Properties:

What This Enables Next

This foundation made several later system decisions possible, including:

Those topics are explored in future logs.

Transferable Takeaway

Firmware variants are often viewed as a maintenance burden. In constrained, long-lived systems, they can instead act as a scaling boundary — separating capability from behavior, and development from deployment.

The most important shift was not technical, but conceptual: treating growth as expected, and designing the firmware architecture accordingly.

Like

Discussions