TL;DR
- Firmware interview answers should connect software decisions to timing, memory, electrical behavior and recovery after failure.
- Explain what
volatiledoes without treating it as an atomic operation, a lock or a complete memory-ordering strategy. - Practise a small timer exercise across counter wraparound, and state the assumptions that make the result meaningful.
- For hardware troubleshooting, collect evidence from the power supply, signals and software state before changing several things at once.
What does a firmware interview actually test?
A firmware engineer works where software behavior meets a physical device. An answer that is reasonable for a web service may be incomplete for a battery-powered sensor, a motor controller or a small microcontroller with limited RAM. The interesting question is often what happens when a deadline is missed, a peripheral stops responding or power disappears during a write.
Use the eight original practice questions below to rehearse that connection. They are preparation exercises, not questions attributed to a particular employer. The C example was checked on a host compiler; the hardware scenarios are design exercises and have not been run on a physical board.
Before starting, clarify the target: bare metal or an RTOS, processor family, compiler, memory budget, safety requirements and whether interrupts or DMA can modify the same data. Those details change which synchronization and debugging techniques are appropriate.
1. Does volatile make an interrupt-shared variable safe?
Start by separating three concerns: whether an access occurs as required by the implementation, whether the access is indivisible, and whether other operations become visible in the necessary order. One keyword does not settle all three.
The GCC documentation on volatile explicitly explains that accesses to non-volatile data are not ordered by a volatile access. A ready flag therefore does not automatically establish that a preceding buffer write is visible to another execution context. A compound operation such as incrementing a counter also needs more reasoning than declaring the counter volatile.
A useful answer would be: “I will check the compiler and processor guarantees, identify which context owns the data, and choose a documented atomic operation, critical section or RTOS primitive appropriate to that target.” Avoid promising that disabling interrupts on one core controls another core or a DMA engine.
For a memory-mapped register, also inspect the device documentation. Some status bits clear on read; others require a particular write pattern. An ordinary read-modify-write expression may have unintended effects even when the address is volatile-qualified.
2. How would you check a timeout across timer wraparound?
Assume a device exposes a monotonically advancing 32-bit tick counter. Here is a small original helper for an elapsed-time check:
#include <stdbool.h>
#include <stdint.h>
bool has_elapsed(uint32_t now, uint32_t start, uint32_t duration)
{
return (uint32_t)(now - start) >= duration;
}The conversion produces the difference in the 32-bit unsigned range. The modular arithmetic rules are described in the C11 committee draft, section 6.2.5. This is different from relying on signed integer overflow.
Suppose start is UINT32_MAX - 49. After wrapping through zero, a current value of 50 represents 100 elapsed ticks. A duration of 100 has expired; at a current value of 25, only 75 ticks have elapsed.
| Case | Start | Now | Duration | Expected |
|---|---|---|---|---|
| No time passed | 100 | 100 | 10 | false |
| Just before expiry | 100 | 109 | 10 | false |
| Exact expiry | 100 | 110 | 10 | true |
| Wrapped, before expiry | UINT32_MAX - 49 | 25 | 100 | false |
| Wrapped, exact expiry | UINT32_MAX - 49 | 50 | 100 | true |
| Zero duration | 100 | 100 | 0 | true |
Now explain the limits. Both readings must use the same uninterrupted clock, and the actual elapsed interval must remain below one full counter cycle. If the device sleeps through a complete cycle, the stored values cannot tell you how many wraps occurred. Arrange checks and maximum durations accordingly, or use a wider time representation. A clock reset is not an ordinary wrap, and reading a multiword counter may itself require a consistent snapshot.
This function checks elapsed duration; it is not a general ordering relation for arbitrary timestamps. Being precise about that distinction is part of the exercise.
3. What work belongs in an interrupt handler?
Consider a sensor that sends bursts of bytes. A first design reads a byte, parses a message, writes a log and updates a display inside the interrupt. Ask what latency that creates for other time-sensitive work.
A more deliberate design captures the required peripheral state, acknowledges the interrupt according to the device specification, and hands bounded work to a queue or task. Parsing and display updates can then happen outside the handler when the system permits it.
The queue still needs a full-capacity policy. Dropping the oldest sample, rejecting the newest sample or applying backpressure are different behaviors. Choose one from the product requirements and expose an overrun counter. Concurrency interview questions on bounded queues and backpressure ask for the same choice when a thread pool's work queue fills up. “We use a ring buffer” is not a complete answer if producer and consumer can overwrite each other's state.
Finally, identify which APIs are interrupt-safe in the chosen RTOS. A blocking operation that is acceptable in a task may be invalid in an interrupt context.
4. How would you reason about DMA and buffer ownership?
Imagine an ADC fills one buffer while a task processes the previous sample batch. Draw the ownership transitions before writing code: available, owned by DMA, completed, owned by the consumer, then available again.
The consumer must not inspect a partially filled buffer, and the producer must not reuse storage while the consumer still needs it. Double buffering can help, but it does not remove the need for explicit handoff and an overrun policy if processing falls behind.
Cache coherency and memory ordering depend on the target. The Arm CMSIS intrinsic reference distinguishes ordering barriers from completion barriers. That distinction does not tell you, by itself, which cache maintenance operations a specific DMA transfer requires. Consult the processor and peripheral documentation, alignment requirements and memory region attributes.
A strong answer describes the ownership invariant first and then explains how the selected hardware and software primitives enforce it.
5. A peripheral stopped responding. Where do you start?
Use an original scenario: a temperature sensor works after reset but stops reporting during a long run. First record the last successful transaction, timeout count, reset cause, firmware version and whether other peripherals still operate.
Then examine one layer at a time. Is supply voltage stable? Are the expected clock and pin configuration present? Does the bus show requests, acknowledgements or a line stuck at one level? Does the driver wait for a state that the peripheral can no longer reach? Say what result you expect before each check. Our guide to debugging unfamiliar code in live interviews explains why that prediction step matters.
A logic analyzer can distinguish “the application never requested a transfer” from “a transfer was sent but not acknowledged.” A debugger can reveal state, but pausing execution may alter timing. Say how you would preserve evidence under realistic operation.
If a recovery reset is permitted, bound its frequency and report it. Repeatedly resetting the peripheral without recording the fault can make the symptom disappear while leaving the defect unexplained.
6. How do you investigate memory corruption?
Start from a concrete symptom such as a damaged queue length. Identify every writer and inspect array bounds, buffer lifetime, stack use and concurrent access. A pointer to a local variable cannot remain valid after that function returns merely because its old bytes still look correct.
For a small device, establish a memory budget for stacks, static storage, buffers and any heap. Include worst-case paths rather than only the idle footprint. If dynamic allocation is used, explain allocation failure and fragmentation behavior under the expected workload. For a comparable prompt on keeping memory use predictable in a long-running embedded process, see the embedded systems questions in the Apple interview guide.
On a host, sanitizers and boundary tests can find classes of defects in portable parsing logic. On the target, available memory protection, watchpoints and stack diagnostics may provide different evidence. State which tool demonstrated the problem; a clean host test is not proof that a timing-sensitive target interaction is safe.
7. When should a watchdog be fed?
Suppose a scheduler is running but the sensor task has deadlocked. Feeding the watchdog from an unconditional periodic interrupt can keep a broken application alive indefinitely.
Define meaningful progress for the tasks that matter. A supervisor might require each critical task to report completion within its allowed interval, with rules for legitimate idle periods and long operations. The exact mechanism depends on the system; the key is that a heartbeat represents useful progress, not merely execution of the feeding code.
After a watchdog reset, retain an appropriate reset reason or diagnostic record if the hardware allows it. Then make startup recovery safe. A device controlling an actuator should not assume that a reset restores the external physical state to a known condition.
8. How would you make a firmware update recoverable?
Describe what happens if power fails during download, verification, installation or the first boot. A robust design needs a known recovery path and a way to distinguish a verified candidate image from an incomplete one.
For an interview exercise, propose separate active and candidate storage only if the flash budget supports it. Explain image authenticity checks, compatibility checks, boot selection metadata and what marks the new application healthy. A failed health check should have a defined response; repeatedly trying the same broken image is not recovery.
Also discuss configuration compatibility. Rolling back executable code may not restore data that the new version already changed. The interviewer should hear both the normal update sequence and the failure sequence.
A practical rehearsal session
Spend ten minutes explaining the timeout helper and its assumptions, ten minutes drawing the sensor-buffer ownership diagram, and ten minutes walking through one peripheral failure. Ask a partner to introduce a reset, a full queue or an unexpectedly long processing interval.
Use PhantomCodeAI during permitted preparation to practise explaining the decisions aloud. Your final answer should remain grounded in what you have built or can demonstrate: the target, the evidence, the tradeoff and the next experiment.