Skip to content

Part 4 · Professional Firmware16 min read

What Comes Next

You have gone from your first printf to bootloaders, RTOS internals, safety mechanisms, and fleet diagnostics. This last lesson is about turning that into a career: what firmware interviews actually test, which projects prove you can do the job, how to keep learning from primary sources, and a 12-week plan for consolidating what you have learned.

Firmware interviews are less about algorithm puzzles than general software interviews, and more about whether you understand what the machine does. Expect a mix of these:

  • C fundamentals under pressure. Pointer puzzles, declarations, integer promotion, what sizeof returns, what a piece of code prints.
  • volatile, ISRs, and shared data. Almost every interview has one question here, because it separates people who have shipped firmware from people who have read about it.
  • Bit manipulation. Set, clear, and test bits; extract fields; count bits; swap bytes.
  • State machines. Design one on a whiteboard for a small device.
  • Debugging stories. “Tell me about the hardest bug you found.” They want a method, not luck.
  • Reading a datasheet excerpt. You are handed two pages of a register description and asked to write the init code.
  • A small driver design. Design the API for a UART, a sensor, or a ring buffer, and discuss trade-offs.

Twelve sample questions with answer outlines

Section titled “Twelve sample questions with answer outlines”

1. What does volatile do, and what does it not do? It tells the compiler that every access to the object must actually be performed, in program order relative to other volatile accesses, because the value can change or the access has side effects outside the compiler’s view. Use it for memory-mapped registers and variables shared with ISRs. It does not make access atomic, does not order non-volatile accesses, and is not a memory barrier.

2. What is wrong with this ISR?

#include <stdint.h>
#include <stdio.h>
uint32_t ticks;
void TIM2_IRQHandler(void)
{
ticks++;
printf("tick %lu\n", (unsigned long)ticks);
}

Three things: ticks is not volatile, so the main loop may never see it change; the timer’s update flag is never cleared, so the interrupt fires again immediately and forever; and printf is slow, non-reentrant, and may block, so it must not run in an ISR. Fix: clear the flag first, make ticks volatile, and move printing to the main loop.

3. How do you safely share a 64-bit counter between an ISR and main on a Cortex-M4? A 64-bit read is two 32-bit loads, so the ISR can update between them and you read a torn value. Either briefly disable interrupts around the read (save and restore PRIMASK), or read high, low, high again and retry if the high word changed.

4. Write a macro or function to set bits 4 to 6 of a register to a value v without touching other bits. reg = (reg & ~(0x7u << 4)) | ((v & 0x7u) << 4); Mention that this read-modify-write is not atomic if an ISR writes the same register, and that set/reset registers such as BSRR avoid the problem for GPIO.

5. What does int *const *p mean? p is a pointer to a constant pointer to int: you may change p and the int, but not the intermediate pointer. Explain with the right-left rule.

6. What does this print, and why? uint8_t a = 200, b = 100; printf("%d\n", a + b); It prints 300, because both operands are promoted to int before the addition. Assigning the result back to a uint8_t would give 44.

7. Your device resets randomly in the field every few days. How do you investigate? Record the reset cause register at boot and log it; add a HardFault handler that saves the stacked frame and fault status registers to .noinit RAM; check watchdog feeding strategy; look for stack overflow with painting; correlate with temperature, supply voltage, and uptime (49.7 days suggests a 32-bit millisecond wrap). Describe a method: reproduce, instrument, narrow, prove.

8. Design a state machine for a single button: short press toggles a light, a hold of 2 seconds enters setup mode, and setup mode times out after 30 seconds. Draw states (OFF, ON, PRESSED_FROM_OFF, PRESSED_FROM_ON, SETUP), events (press, release, timer expiry), and transitions; mention debouncing below the FSM, timeouts as events from a timer service, and testing by feeding event sequences on the host.

9. Here is a register description. Write the init for 115200 baud on a 42 MHz clock. Show the BRR calculation (42,000,000 / 115,200 = 364.58, so mantissa 22 and fraction 13 with 16x oversampling, BRR = 0x16D), enable the peripheral clock first, configure the pins’ alternate function, then set UE, TE, RE. State the resulting baud error (365 gives 115,068 baud, about 0.1% low).

10. What happens between reset and main() on a Cortex-M? The core loads the initial stack pointer from address 0 and the reset vector from address 4, then runs the reset handler, which copies .data from flash to RAM, zeroes .bss, optionally enables the FPU and sets up clocks, runs static constructors, and calls main.

11. When would you use an RTOS, and when would you not? Use one when you have several independent activities with different deadlines and blocking I/O, and the complexity of a superloop or cooperative scheduler is growing. Avoid one for simple, single-purpose devices, very tight RAM, or when certification cost outweighs the benefit. Mention the costs: RAM per task, context switch latency, priority inversion, and a new class of concurrency bugs.

12. Design the API for a ring buffer used between a UART ISR and the main loop. Fixed capacity (power of two), put from the ISR only and get from main only (single producer, single consumer), head written by one side and tail by the other, no locks needed with proper ordering, put returns failure when full and the caller counts drops. Discuss what changes if two producers exist.

Notice the pattern: every good answer includes what can go wrong. Interviewers listen for failure modes.

A public repository of real firmware is worth more than any certificate. Each project below builds on the previous one, and together they cover what employers look for. Each should have a README with a block diagram, build instructions, a short explanation of the interesting decisions, and photos or a Renode recording.

  1. Bare-metal blinky with your own startup code and linker script. No vendor code. A Makefile, startup.c, link.ld, and a map file you can explain line by line.
  2. A UART command shell. Interrupt-driven receive into a ring buffer, a command table, and a few useful commands. Add host unit tests for the parser.
  3. A sensor logger with flash storage. An I2C or SPI sensor behind a bus abstraction, a circular log in flash with CRC and sequence numbers, and a dump command. Show it surviving power cuts.
  4. An RTOS-based device with a bootloader and OTA. FreeRTOS or Zephyr, a bootloader that validates and swaps images, rollback on failed boot, and a host script that performs the update.
  5. A USB or BLE device. A CDC or HID device with TinyUSB, or a BLE peripheral with a custom GATT service and a phone or Python client.
  6. A contribution to an open-source firmware project. A bug fix, a new board port, a driver, or documentation for Zephyr, MCUboot, TinyUSB, Renode, or a similar project. Reviewed and merged code in a real project shows you can work in someone else’s codebase, which is most of the job.

How to read a reference manual and an errata sheet

Section titled “How to read a reference manual and an errata sheet”

The STM32F407 reference manual (RM0090) is over 1,700 pages. Nobody reads it front to back. Read it like this:

  • Start from the memory map and the RCC chapter, because every peripheral needs its clock and its address.
  • For a peripheral, read the functional description first (how it works, which modes exist), then the register map, then the register descriptions for the bits you will set. Keep the chapter’s “procedure” or “configuration sequence” paragraphs open while coding.
  • Note the access type of each bit (read-only, write-1-to-clear, read-clear) and any required order of operations.
  • Cross-check with the datasheet (a different document) for pin alternate functions, electrical limits, and timing.
  • Read the errata sheet for every peripheral you use, before you start debugging. Each entry describes a silicon bug, the conditions that trigger it, and a workaround. Search the errata for a peripheral’s name whenever it behaves strangely.
BookAuthorWhy read it
The C Programming LanguageKernighan and RitchieThe original, short and precise
C Programming: A Modern ApproachK. N. KingThe most thorough teaching text for C
Modern CJens GustedtC as it is defined today, with precise semantics
Expert C Programming: Deep C SecretsPeter van der LindenDeclarations, linking, and C’s dark corners, entertainingly
Effective CRobert C. SeacordSecure, correct C from a CERT C author
The Definitive Guide to ARM Cortex-M3 and Cortex-M4 ProcessorsJoseph YiuThe architecture book for this course’s core
Making Embedded SystemsElecia WhitePractical design and debugging for firmware engineers
Embedded Systems ArchitectureDaniele LacameraModern MCU firmware structure, bootloaders, networking
Real-Time Concepts for Embedded SystemsQing Li and Caroline YaoRTOS concepts explained carefully
Practical UML Statecharts in C/C++Miro SamekHierarchical state machines and active objects
Hacker’s DelightHenry S. Warren Jr.Bit manipulation and integer arithmetic tricks
Computer Systems: A Programmer’s PerspectiveBryant and O’HallaronHow programs really run, from bits to linking to memory

Primary sources answer questions that books and forums get wrong:

  • ARMv7-M Architecture Reference Manual: the exact behaviour of every instruction, exception, and system register on the Cortex-M3/M4.
  • AAPCS (Procedure Call Standard for the Arm Architecture): the calling convention, for reading disassembly and writing assembly.
  • C11 standard draft N1570: freely available and nearly identical to the published standard. Look things up in it, especially when someone on the internet asserts what C “allows”.
  • MISRA C: buy it if your work requires it; read it anyway for the rationale sections.
  • STM32F407 reference manual (RM0090), datasheet, and errata sheet: the source of truth for every register in this course.
  • FreeRTOS and Zephyr documentation: both are good and explain their design decisions.

Firmware engineering is a smaller field than web development, and its communities are friendly to people who have done their homework. Look for: the Embedded.fm podcast, Memfault’s Interrupt blog, the Embedded Artistry blog and newsletter, Jack Ganssle’s The Embedded Muse newsletter, the r/embedded community, the Zephyr project’s chat and mailing lists, and the forums of the vendors whose parts you use. Ask questions with code, a description of what you expected, what happened, and what you have tried. Answer other people’s questions once you can; explaining is the fastest way to find the gaps in your own understanding.

The best firmware engineers are good at things around firmware:

  • Python for tooling. Test stations, image signing scripts, log decoders, protocol test harnesses, data analysis. You will write Python every week.
  • Electronics basics. Ohm’s law, RC time constants, pull-ups, decoupling, transistors as switches, op-amp basics. Enough to have a useful conversation with an EE and to not destroy pins.
  • Reading PCBs and schematics. Follow a signal from the MCU pin through the layout to the connector; find test points; spot a missing pull-up.
  • Rust for embedded, at awareness level. Rust’s ownership model removes whole classes of memory and concurrency bugs at compile time, and its embedded ecosystem (HALs, the Embassy async framework, RTIC) is maturing quickly. You do not need to switch, but you should be able to read it and judge when it fits.
  • Git mastery. Interactive history editing, bisecting a regression with git bisect, clean commit messages, reviewing others’ changes. Firmware teams live in their history when hunting regressions.

This plan revisits the course with a project focus. Assume about 8 to 10 hours per week.

WeekFocusDeliverable
1Part 1: pointers, memory, UB lessonsRe-solve the pointer and UB exercises without notes; write a ring buffer with Unity tests
2Part 1: structs, bits, preprocessor, modular CA protocol encoder/decoder module with an X-macro message table and tests
3Part 2: reading assembly, registers, blinky, startup, linker scriptsPortfolio project 1, with an annotated map file in the README
4Part 2: clocks, GPIO, interrupts, timersButton and LED controller with debouncing and PWM dimming
5Part 2: UART, SPI, I2C, DMAPortfolio project 2: UART shell; read the on-board accelerometer over SPI
6Part 2: ADC, watchdogs, faults, flashFault handler with crash record; config storage with A/B records
7Part 3: drivers, state machines, testingPortfolio project 3: sensor logger with host tests and a fake bus
8Part 3: RTOS fundamentals and synchronizationPort the logger to FreeRTOS with a sensor task, a storage task, and a queue
9Part 3: bootloaders and securityPortfolio project 4: bootloader with CRC validation, slots, and rollback
10Part 3: debugging, power, performance, CIMeasure and halve the logger’s sleep current; add CI with a size budget
11Part 4: communication stacksPortfolio project 5: USB CDC or BLE interface to the logger
12Part 4: architecture, production, reviewWrite the architecture doc, budget table, and self-test commands; review your own code with the 25-point checklist

At the end of week 12 you will have five portfolio projects, a coherent architecture document, and a codebase you can talk about in detail in any interview. Then start project 6: find an open-source firmware project you use, read its contribution guide, and fix something.

  1. Answer interview questions 2, 3, and 9 in writing as if in an interview, including code. Then check each answer against the relevant lesson and note anything you missed.
  2. Choose the portfolio project from the ladder that you are least confident about. Write its README first: block diagram, requirements, the three hardest design decisions and your choice for each, and how you will test it.
  3. Pick one peripheral used in this course, open the STM32F407 errata sheet, and find every entry that applies to it. For each, write one sentence on whether code from this course would be affected and what the workaround is.

Question 1:An interviewer asks why a flag set in an ISR is never seen by the main loop at -O2, although it works at -O0. What is the best first answer?

Question 2:Which portfolio project best demonstrates the ability to work in an existing, unfamiliar codebase?

Question 3:A peripheral on your STM32 behaves in a way that contradicts the reference manual. What should you check first?

Question 4:In interview question 6, uint8_t a = 200, b = 100; and the code prints a + b with %d. What is printed?

  • Firmware interviews test machine-level understanding: volatile, ISRs and shared data, bits, state machines, datasheets, and debugging method.
  • Good answers always include the failure modes and how to detect them.
  • A public portfolio that climbs from bare-metal blinky to an open-source contribution proves more than any certificate.
  • Read reference manuals by peripheral (functional description, register map, procedures) and read the errata sheet before debugging.
  • Primary sources (the architecture manual, AAPCS, N1570, RM0090) settle questions that secondary sources get wrong.
  • Python, basic electronics, schematic reading, Git, and Rust awareness make a firmware engineer far more effective.

Next: Capstone I: Requirements, Architecture, and Memory Map, where you build a complete sensor node from everything in this course.