← Back to the learning library
Work through the project with the source and evidence.
Deliberately flawed C firmware, an incomplete requirements baseline, two nominal host checks, four pending targeted tests and an optional HIL extension. Python 3 and a C99 compiler are needed for the host runner.
Download the starter projectBefore you begin: Complete TFA-401 or review its state machine, acquisition timestamp and host-test evidence. Keep TFA-102, TFA-103, TFA-104 and TFA-109 available as references.
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:
- Turn four vague statements into measurable firmware requirements.
- Use state, timing and execution-context models to locate defects before editing code.
- Write focused tests for a timeout boundary, shared-data race, restart behaviour and watchdog effectiveness.
- Correct the implementation without weakening the tests that exposed it.
- Separate host-test evidence from target integration and hardware-in-the-loop evidence.
Lesson 1
1. Understand the exercise boundary
The fictional controller begins in IDLE, enters STARTING after a start command and should reach RUNNING only after a valid sensor observation. An output is enabled in RUNNING. A 100 ms starting deadline, sensor updates from an interrupt, watchdog supervision and restart behaviour make the example small enough to inspect while preserving the kinds of interactions that create real firmware defects.
The downloadable project is intentionally incomplete and incorrect. Two nominal tests pass. Four targeted checks are marked pending, so the initial runner exits with a failure status. That is the expected starting condition: the work is to define the missing claims, implement tests that expose the defects and then repair the design. The numbers are teaching inputs and do not establish suitable limits for a real product.
This exercise stays inside the controller boundary. It asks what the firmware does with time, shared state, reset and progress. The complementary Embedded Academy exercise asks whether sensors, links, host software and recovery behaviour work together across the complete system.
Lesson 2
2. Complete the requirements before repairing code
The starter includes four incomplete statements. Rewrite each with a trigger, state, time origin, boundary, required response and observable acceptance criterion. “Starting shall time out after 100 ms” does not say whether the fault occurs at 100 ms or after it, which clock is authoritative, what output is required or how rollover is handled.
| ID | Minimum clarification |
|---|---|
| DBG-REQ-01 | From entry into STARTING, at elapsed time greater than or equal to 100 ms, enter FAULT and command output off even if no sensor event occurs. |
| DBG-REQ-02 | Main processing shall act only on a coherent sensor snapshot whose value, validity and timestamp come from one completed acquisition. |
| DBG-REQ-03 | Following power-on or watchdog reset, initialise in IDLE with output off; retained active state shall not authorise automatic restart. |
| DBG-REQ-04 | Refresh the watchdog only after defined main-loop progress; timer-interrupt activity alone shall not prevent expiry. |
These are suitable exercise requirements, not product requirements. A real design would justify the deadline, define physical output timing, name the actual synchronisation mechanism and specify the watchdog interval and reset sequence. Preserve that distinction in your test report.
Lesson 3
3. Draw behaviour and execution contexts
Draw the state transitions separately from the execution-context diagram. The state model contains IDLE, STARTING, RUNNING and FAULT. The context model contains the timer interrupt, sensor interrupt, main loop, watchdog peripheral and output driver. Combining them into one crowded drawing often hides the fact that an interrupt can run while the main loop is stalled.
Mark every shared object. The sensor interrupt writes temperature, timestamp and validity. Main processing reads the same fields. The timer interrupt changes the clock and services the watchdog in the starter. Ask which operations are atomic on the assumed target, which ordering rules apply and what observation proves useful progress.
Write a short fault hypothesis for each planted defect before opening the code. This prevents line-by-line debugging from becoming the only method and gives each test a specific claim to challenge.
Lesson 4
4. Inspect the deliberately flawed implementation
The complete flawed implementation is shown below and included in the download. Read it once for intent and again for execution order. Annotate the four places where a requirement can be defeated.
#include "firmware.h"
#define START_TIMEOUT_MS UINT32_C(100)
volatile uint32_t g_tick_ms;
volatile uint32_t g_watchdog_kicks;
volatile struct sensor_sample g_sensor_sample;
static void watchdog_kick(void)
{
++g_watchdog_kicks;
}
void timer_interrupt(void)
{
++g_tick_ms;
/* Deliberate defect: an interrupt proves only that the interrupt ran. */
watchdog_kick();
}
void sensor_interrupt(int16_t centi_c, bool valid)
{
/* Deliberate defect: main can observe fields from different acquisitions. */
g_sensor_sample.centi_c = centi_c;
g_sensor_sample.captured_ms = g_tick_ms;
g_sensor_sample.valid = valid;
}
void controller_boot(struct controller *c, enum reset_reason reason,
bool retained_running)
{
c->mode = MODE_IDLE;
c->output_on = false;
c->state_entered_ms = g_tick_ms;
c->last_sample_ms = 0;
c->progress_count = 0;
/* Deliberate defect: a watchdog reset can resume an active output. */
if (reason == RESET_WATCHDOG && retained_running) {
c->mode = MODE_RUNNING;
c->output_on = true;
}
}
void controller_start(struct controller *c)
{
if (c->mode == MODE_IDLE) {
c->mode = MODE_STARTING;
c->state_entered_ms = g_tick_ms;
}
}
void controller_step(struct controller *c)
{
/* Deliberate defect: this is not a coherent interrupt-to-main snapshot. */
const struct sensor_sample sample = g_sensor_sample;
const bool new_sample = sample.captured_ms != c->last_sample_ms;
++c->progress_count;
if (c->mode == MODE_STARTING && new_sample) {
c->last_sample_ms = sample.captured_ms;
if (sample.valid && sample.centi_c >= 3000) {
c->mode = MODE_RUNNING;
c->output_on = true;
return;
}
/* Deliberate defect: no new sample means the timeout is never checked. */
if ((uint32_t)(g_tick_ms - c->state_entered_ms) > START_TIMEOUT_MS) {
c->mode = MODE_FAULT;
c->output_on = false;
}
}
}
Do not correct everything at once. A large rewrite can make the symptoms disappear without proving which change addressed which defect. Establish one failing test per claim, retain the original observation, make the smallest coherent design correction and rerun the relevant regression set.
Lesson 5
5. Design tests that force the critical condition
EX-003 should enter STARTING, provide no new sample, execute at 99 ms and then exactly 100 ms, and require FAULT with output off at the boundary. EX-004 should force or emulate an update in progress so the reader cannot accept a mixture of fields. EX-005 should boot after a watchdog reset with retained active information and require IDLE with output off. EX-006 should advance timer interrupts without calling main processing and require that watchdog service does not continue.
Each test needs a precondition, stimulus, observation and verdict. Avoid a long scenario that fails for several possible reasons. A focused failure message should tell you which boundary or invariant was violated. After the repair, add nearby negative cases: 99 ms must not time out, a completed sensor update must be accepted, a normal power-on must remain inhibited and one completed main iteration may refresh supervision.
Lesson 6
6. Correct design ownership, not only symptoms
The timeout belongs to state supervision and must be evaluated independently of sensor arrival. The sensor handoff needs a supported coherent-snapshot mechanism, not merely volatile. Restart policy belongs to controlled initialisation, not a convenience rule based on retained state. Watchdog authority belongs to evidence of required progress, not the highest-frequency interrupt.
Your target may use a bounded critical section, interrupt masking, double buffering, a sequence counter or a platform-supported queue. Select one using the target's atomicity, latency and memory rules. The starter does not prescribe a universal mechanism. Record the chosen contract: which context publishes, how completion is indicated, what the reader does during an update and the maximum time for which access can be deferred.
Lesson 7
7. Plan the verification ladder and closure record
Host tests can demonstrate state transitions, arithmetic boundaries and logic around a simulated handoff. They cannot prove atomic access on the target, interrupt latency, watchdog configuration, reset-pin behaviour or physical output de-energisation. Allocate those claims to target integration and, where relevant, HIL.
| Level | Evidence to obtain | Important limitation |
|---|---|---|
| Host unit | Exact 99 / 100 ms boundary, restart decision, supervision ownership and snapshot algorithm. | Simulated execution cannot reproduce every compiler, CPU or interrupt behaviour. |
| Target integration | Atomicity mechanism, interrupt masking time, watchdog configuration, reset cause and startup output. | May observe registers without demonstrating the final physical output. |
| HIL | Loss of sensor events, stalled main loop, watchdog expiry, reboot state and measured harmless output response. | Rig and timing evidence apply only to the tested board, build and configuration. |
Close each defect with the original failing observation, requirement correction, design change, review, passing regression and impact on other tests. Record remaining limitations rather than turning “all host tests pass” into a claim about the product.
Decision guide
Where should each defect be exposed?
| Defect | First useful test level | Why |
|---|---|---|
| Missed timeout | Host unit | A controllable clock exposes the exact boundary cheaply. |
| Interrupt / main-loop race | Host plus target integration | The contract can be unit-tested; target atomicity and ordering still need confirmation. |
| Incorrect restart state | Host and target reset test | Logic is testable on host; startup pins and retained memory are target-specific. |
| Ineffective watchdog | Target integration / HIL | Peripheral configuration, independent clock and real stall response are platform behaviours. |
Worked example
Worked example — turn a hang into a bounded test
The controller remains in STARTING forever when the sensor interrupt stops, because timeout evaluation is nested inside the new-sample branch.
A host test enters STARTING, advances the controlled clock without publishing a sample, checks 99 ms and then the exact 100 ms boundary. The timeout check is moved to unconditional state supervision and uses rollover-safe elapsed arithmetic with an inclusive boundary.
The defect is reproduced deterministically, the correction is linked to DBG-REQ-01 and the same test becomes a permanent regression.
Engineering evidence
What should the work produce?
Completed requirements
Four measurable statements with triggers, boundaries and observable responses.
Fault hypotheses
A short causal explanation for each suspected defect.
Targeted host tests
One focused failing-then-passing regression for each claim.
Design correction record
State, concurrency, restart and supervision decisions with rationale.
Verification limitations
Explicit allocation of unresolved target and HIL evidence.
Common failure patterns
What goes wrong?
- Editing the code before establishing a failing observation.
- Refreshing the watchdog from an interrupt because it executes reliably.
- Using volatile as proof of a coherent multi-field transfer.
- Resuming an actuator after reset from retained state alone.
- Moving a timeout boundary in the test to match the implementation.
Applied exercise · approximately 90 minutes
Complete the four-defect investigation
Use the starter project to complete the requirements, implement EX-003 to EX-006, diagnose each failure and correct the design. Keep the tests independent and preserve the initial failing results.
- Rewrite DBG-REQ-01 to DBG-REQ-04 with exact triggers, boundaries and responses.
- Implement the four targeted host tests and record the first observed failures.
- Correct the design, rerun the regression set and explain why each correction addresses the causal defect.
- Define one target integration test and one optional HIL test for evidence the host cannot provide.
- Write a closure summary that names remaining limitations.
Review a suggested approach
The separate TFA-403 worked solution provides the full diagnosis, reference corrections, host-test evidence and limitations. Compare your requirement wording and causal reasoning before comparing code.
Completion criteria
- All four requirements are measurable and use explicit time or state origins.
- Each defect has a focused failing-then-passing regression.
- Corrections address ownership and architecture rather than suppressing symptoms.
- Host, target and HIL claims remain clearly separated.
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
- Does timeout supervision run even when the awaited event never arrives?
- Can the reader prove all sample fields belong to one acquisition?
- Is every reset reason mapped to a safe, deliberate initial state?
- Does watchdog service represent the progress that matters?
- Does the evidence state which target behaviours remain unverified?
Debugging becomes verification when each defect is connected to a precise requirement, a causal design correction and repeatable evidence at the right level.
References and further reading
- TFA-102 — State Machines & Scheduling
- TFA-103 — Interrupts, Timing & Concurrency
- TFA-104 — Defensive Programming
- TFA-109 — Unit, Integration & HIL Testing
- TFA-401 — Worked Project: Temperature Controller
- TFA-403 — Worked solution
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.
