← Back to the learning library
Before you begin: Basic C or C++ and familiarity with sensors, tasks and interrupts. Revisit TFA-103 when concurrency terminology is unfamiliar.
Reading time and practice time are estimates. Examples use fictional products and illustrative engineering values, not approved product limits.
Learning objectives
By the end of this module, you should be able to:
- Explain the purpose of firmware architecture beyond drawing a block diagram.
- Separate hardware access, services, control logic and product behaviour.
- Define ownership and contracts at component interfaces.
- Select architecture views that support implementation, review and verification.
Lesson 1
1. Architecture is a set of decisions
An architecture identifies the important elements of the firmware, what each element is responsible for and the rules governing their interaction. It should make difficult decisions visible: which component owns a peripheral, where state is held, which code may block and how faults cross a boundary.
A diagram without these decisions is only a map. A useful architecture constrains implementation and gives reviewers a basis for asking whether dependencies, timing and failure behaviour are controlled.
Lesson 2
2. Separate policy from mechanism
A practical layered structure often separates hardware drivers, platform services, domain control and product application logic. Drivers know registers; control logic knows product behaviour. This permits pure logic to be tested without target hardware and reduces the effect of a processor or peripheral change.
- Hardware abstraction: register access, pins, buses and peripheral-specific behaviour.
- Platform services: time, storage, diagnostics, communications and scheduling.
- Domain control: state, algorithms and safety-related decisions.
- Application coordination: use cases and sequencing across components.
Lesson 3
3. Ownership prevents accidental coupling
Every peripheral, buffer, state variable and asynchronous event should have a clear owner. Other components use an explicit interface rather than reaching into another component's data. Ownership is especially important when interrupts, DMA and tasks can access the same resource.
Interfaces should state valid inputs, units, ranges, error responses, timing expectations and concurrency rules. These contracts become design information and natural verification targets.
Lesson 4
4. Use several small views
No single diagram answers every engineering question. Use a context view for external interfaces, a component view for responsibilities and dependencies, a run-time view for tasks and interrupts, and a deployment view for memory regions, processors and boot stages. Keep each view focused and link it to requirements, risks and tests.
Lesson 5
5. Define a contract that survives implementation
Consider an actuator interface called request_output(). Returning success could mean that a request was queued, that a register was written, or that the physical output was confirmed. These are different contracts. Define the acknowledgement point and expose completion or failure separately when action is asynchronous.
A useful contract names the owner, permitted callers, input range and units, maximum execution time, blocking rules, output validity and failure response. State who owns a buffer after submission and when it may be reused. A pointer without a lifetime agreement is a concurrency defect waiting to happen.
| Interface element | Illustrative contract |
|---|---|
| Request | Target in °C; permitted only in Ready or Active state. |
| Acceptance | Queue entry accepted; physical response is not yet confirmed. |
| Data ownership | Service copies the request before returning. |
| Completion | Event contains request ID, result and monotonic timestamp. |
| Failure | Queue-full rejects the whole request without changing target. |
Lesson 6
6. Account for resources and separation
Logical separation does not automatically provide fault containment. Two well-named components can still corrupt each other when they share unrestricted memory. Identify which boundaries are conventions enforced by review and which are enforced by an MPU, process isolation, peripheral ownership or independent hardware.
Budget stacks, heap or pools, queues, DMA buffers and retained storage. Stack high-water measurements are useful observations but may miss rare call paths, nested interrupts or library recursion. Combine measurement with design restrictions and analysis. Consider peak simultaneous use: an update may require extra RAM while communications and diagnostics remain active.
For each dependency, ask whether failure can propagate backwards. A logger that blocks while flash is busy can turn a diagnostic call in a safety monitor into a missed deadline. Architectural rules should therefore include latency and failure coupling as well as dependency direction.
Lesson 7
7. Review architecture through change scenarios
Review a candidate design by walking real scenarios through it: replace a sensor, add a second channel, lose the host, fill storage, or restart one service. Name each affected interface and state owner. If a small change touches most components, the design may have distributed policy or duplicated ownership.
Record an architectural decision with its context, constraints, alternatives, choice and consequences. For a fixed-size queue, explain both the memory saving and the overflow policy. A later reviewer needs to know which assumption would make the decision wrong. Update the decision when measured load or a product change violates that assumption.
Decision guide
Where should a new sensor plausibility check live?
| Option | Use when | Main risk |
|---|---|---|
| Driver | The check concerns electrical validity or peripheral status. | Driver becomes product-specific. |
| Domain component | The check compares values with product or physical expectations. | Needs a clean data and error interface. |
| Application coordinator | The decision depends on the current use-case state. | Central coordinator becomes overloaded. |
Worked example
Worked example — temperature-controlled actuator
A device reads a thermistor, controls a heater and must stop heating if measurements become implausible.
The ADC driver returns raw samples and hardware errors. A temperature service converts samples to °C and reports validity. A control component owns the set-point algorithm. The application state machine decides whether heating is permitted. A safety monitor independently enforces an upper limit and commands the actuator interface off.
Each decision has one owner, the control algorithm can be unit tested, and the independent shutdown path can be verified by fault injection.
Engineering evidence
What should the work produce?
Firmware architecture description
Defines components, responsibilities and design rules.
Interface specification
Records contracts, units, ranges, errors and timing.
Run-time view
Shows tasks, interrupts, queues and resource ownership.
Architecture review record
Demonstrates that requirements and risks were considered.
Common failure patterns
What goes wrong?
- A “layered” diagram while components still access global state directly.
- One manager component that owns every decision and becomes impossible to test.
- Hardware details leaking into product logic.
- Architecture updated after implementation but not used to guide it.
Applied exercise · approximately 25 minutes
Design a two-channel measurement service
A controller acquires two sensors every 5 ms, publishes a combined result every 20 ms and accepts host commands. A sensor driver may take 8 ms to recover from a bus error. Produce a one-page architectural contract; these timings are exercise assumptions.
- Allocate acquisition, recovery, combination, command validation and diagnostics to named owners.
- Specify the result structure, timestamp and validity rules when one sensor is unavailable.
- Identify which operations must not block acquisition and give two verification scenarios.
Review a suggested approach
Let the acquisition context capture bounded samples and let a separate driver-recovery operation handle the 8 ms path. The combination service owns pairing and rejects samples outside an agreed age or synchronisation window. A result should carry channel validity, measurement time and sequence rather than a numeric value alone.
Do not hide an unavailable channel by retaining its previous reading indefinitely. Define whether a partial result is allowed by the system requirement. Test both normal pairing and a bus fault during a command burst, measuring the acquisition gap and checking result validity. The architecture is incomplete until the allowed gap and stale-data policy are agreed.
Completion criteria
- Every mutable resource has one owner and a defined caller contract.
- Recovery cannot silently extend the acquisition deadline.
- Two tests observe timing and data validity, not merely successful function returns.
This is a self-directed engineering exercise, not an accredited examination. Compare the rationale as well as the final answer.
Practical check
Questions for your next design review
- Can every peripheral and mutable state item be assigned to one owner?
- Are dependency directions intentional and free from cycles?
- Can important control logic execute in a host-based test?
- Are blocking and interrupt-callable interfaces identified?
- Can reviewers trace risk controls to architectural elements?
A good firmware architecture makes change local, behaviour explainable and verification possible.
References and further reading
- SEI — Software Architecture
- C++ Core Guidelines — Interfaces and resource management
- TFA-103 — Interrupts, Timing & Concurrency
- TFA-111 — Design & Verification Evidence
Educational guidance only. Apply the authorised standards editions, regulations and approved procedures relevant to your product and market. Linked publications support further study; the fictional exercises do not demonstrate compliance.
