Why this page matters
This page explains how Build Configuration and Flags fits into the wider ZeroKernel execution model, what problem it is meant to solve, and what trade-off you are actually accepting when you use it in production firmware. The goal is not to treat Build Configuration and Flags as an isolated API call, but to understand where it sits inside bounded scheduling, queue discipline, fault visibility, and profile selection.
Read this topic as an operational contract. Start from the smallest working path, wire it into a lean profile first, and only expand into richer routing, diagnostics, or transport state after you can prove that the timing outcome is still worth the extra flash and RAM. That mindset is what keeps ZeroKernel useful on small boards instead of turning it into another bloated abstraction.
The safest pattern is always the same: define the runtime boundary, keep the hot path short, measure the effect with compare scripts, and only then scale complexity. The examples below are not filler; they show the smallest repeatable patterns you can lift into real firmware when you need clean integration instead of ad-hoc loops.
Three practical patterns
Use one bounded task for the hot path, then let the scheduler keep the phase aligned over time.
ZeroKernel.begin(boardMillis);
ZeroKernel.addTask("Fast", fastTask, 10, 0, true);
ZeroKernel.tick();
Move non-critical routing and transport out of the immediate task body so fast paths stay predictable.
const auto key = ZeroKernel.makeTopicKey("telemetry.sample");
ZeroKernel.publishDeferredFast(key, sampleValue);
ZeroKernel.flushEvents();
Read the timing report and stats together so you can prove the cost of each abstraction layer.
const auto stats = ZeroKernel.getStats();
const auto timing = ZeroKernel.getTimingReport();
Serial.println(timing.maxTickMs);
What to verify while you use it
- Validate timing before you validate aesthetics. A cleaner API is not a win if fast misses rise.
- Prefer the smallest profile that still matches the workload, then add optional modules only when the measured payoff is obvious.
- Keep callbacks and transport steps bounded so watchdog, panic flow, and queue limits remain meaningful.
Common mistakes that make results misleading
- Do not copy a demo pattern into production firmware without measuring it on the real board and real build profile you plan to ship.
- Do not read success counters without reading queue depth, timing, and workload label next to them.
- Do not enable heavier diagnostics and compatibility flags in a lean target just because the defaults looked convenient.
Recommended working sequence
Boot the runtime, register the minimum useful task set, and prove that the baseline timing is clean before adding optional layers.
Introduce routing, diagnostics, or transport one layer at a time so the cost and payoff remain obvious.
Update docs, charts, or public claims only after the same workload survives the same validation path more than once.
Configuration philosophy
ZeroKernel is shaped heavily at compile time. Queue capacities, metrics depth, compatibility surfaces, and optional modules all change the binary before the firmware ever reaches a board. That means configuration is not a side note. It is part of the runtime contract. If you change flags carelessly, you can change timing costs, memory usage, and visible behavior even when the application code appears almost unchanged.
The safest workflow is to start with a profile and only move to individual flags when you know exactly which tradeoff you are chasing. Profiles exist because most real firmware falls into a few common shapes: lean nodes, network-heavy nodes, diagnostic builds, or small boards that need strict budgets. Profiles let you begin from a tested intent instead of assembling one-off flag combinations from scratch.
Fine-grained flags still matter, but they should be treated as tuning tools. Use them when you can explain the reason: reduce size, disable a compatibility layer, or lock a build into a strict key-first path. If you cannot explain why a flag is changing, it probably should not be changing yet.
Common profile flags
-DZEROKERNEL_PROFILE_POWER_SAVE
-DZEROKERNEL_PROFILE_LEAN_NET
-DZEROKERNEL_PROFILE_NETWORK_NODE
-DZEROKERNEL_PROFILE_DIAGNOSTIC
Pick a profile based on the shape of the firmware, not based on marketing language. POWER_SAVE
is for small boards and tighter hot paths. LEAN_NET is for transport-heavy nodes that still need
a disciplined footprint. NETWORK_NODE is for richer networked firmware where some extra runtime
surface is acceptable. DIAGNOSTIC is for proving behavior, field service, and heavy visibility.
Useful low-level feature flags
-DZEROKERNEL_ENABLE_CAPABILITIES=0
-DZEROKERNEL_ENABLE_EXTENDED_TASK_METRICS=0
-DZEROKERNEL_ENABLE_LEGACY_LABEL_API=0
-DZEROKERNEL_ENABLE_TOPIC_KEY_ONLY=1
These flags matter most when you are already tuning a known budget. For example, if you want to remove legacy label overhead from a lean build, disable the legacy label API and enable strict topic-key mode. If you want to reduce per-task bookkeeping in a small target, disable extended task metrics. Each one has a cost and a purpose; the safest use is to change the smallest set necessary to hit a specific goal.
; PlatformIO example
build_flags =
-DZEROKERNEL_PROFILE_LEAN_NET
-DZEROKERNEL_ENABLE_LEGACY_LABEL_API=0
-DZEROKERNEL_ENABLE_TOPIC_KEY_ONLY=1
Rules the config now enforces
- Capacity limits are guarded with
static_assertso silentuint8_toverflow does not slip through. TOPIC_KEY_ONLYandLEGACY_LABEL_APIcannot be enabled in a contradictory way.- Lean profiles disable heavier features by default so new builds do not accidentally start in the most expensive mode.
These checks exist because configuration mistakes in embedded systems tend to fail quietly. A queue size that overflows a small integer or a contradictory routing mode can compile just fine and only surface later as odd runtime behavior. It is better to fail at compile time with a clear message than to discover the mistake after flashing a board and trying to debug symptoms.
Three practical configuration patterns
Pattern 1: small board
Profile: POWER_SAVE
Goal: keep the hot path lean and avoid paying for heavy diagnostics by default
Pattern 2: network-oriented node
Profile: LEAN_NET
Goal: keep queue discipline and transport helpers while limiting footprint growth
Pattern 3: lab validation build
Profile: DIAGNOSTIC
Goal: maximize visibility and explicit reporting while proving runtime behavior
These three patterns cover the majority of real use cases. The common failure mode is starting in a heavy profile because it "sounds safer," then trying to claw back size later. In most cases, you get a better result by starting lean and deliberately adding only the visibility or compatibility you actually need.
Configuration FAQ
Should I hand-tune flags before I try a profile?
No. Start with the closest profile, measure it, then tune the smallest number of feature flags needed.
Why is configuration treated as part of the docs, not just code?
Because most embedded tradeoffs are decided at compile time. If the flags are unclear, the benchmark claims are harder to trust.
Should I enable both legacy labels and topic-key-only mode during migration?
No. Migrate in stages, but keep the final build explicit. If the goal is a key-first build, treat legacy labels as a temporary bridge only.
Which profile should a new project try first?
Start with POWER_SAVE for small firmware, LEAN_NET for network-heavy nodes, and only move to heavier profiles when you can justify the extra cost.