Start Here22 min read
What Firmware Is (and Is Not)
Firmware is the code that runs closest to the hardware: the program inside your washing machine, the motor controller in a drone, the code in a USB keyboard that turns key presses into packets. This lesson defines firmware precisely, places it in the stack from transistors to applications, shows you the constraints that make firmware engineering its own discipline, and explains how this course is organised so you can study it effectively.
A working definition
Section titled “A working definition”Firmware is software that is written for one specific piece of hardware, ships inside that hardware, and is responsible for making that hardware do its job. It usually runs without a general-purpose operating system, it usually lives in on-chip flash memory, and it usually cannot be replaced by the end user without special tools.
That definition is fuzzy at the edges on purpose. The word dates from the 1960s, when it described microcode that sat “between” hardware and software: firmer than software, softer than hardware. Today the useful distinction is about how tightly the code is bound to the hardware:
| Layer | What it is | Who changes it | Example |
|---|---|---|---|
| Hardware | Transistors, wires, silicon, PCB | Chip designers, electrical engineers | The STM32F407 chip, the LED on pin PD12 |
| Firmware | Code that knows the exact hardware: register addresses, pin numbers, clock speeds, timing | Firmware engineers, shipped with the product | The code that sets bit 12 of GPIOD to turn the LED on |
| Software | Code that runs on top of an OS that hides the hardware | Application developers, updated freely | A phone app, a web server |
The boundary moves. A Linux kernel driver is “firmware-like” code living inside “software”. The BIOS or UEFI in a PC is called firmware even though it is millions of lines long. A smart thermostat may run firmware on one chip and Linux on another. What stays constant is the skill set: if your code writes to hardware registers, must meet timing deadlines, and has to fit in kilobytes, you are doing firmware.
What firmware is not
Section titled “What firmware is not”- It is not “just C”. C is the dominant language, but the hard part is the hardware model, the timing, and the debugging.
- It is not software with smaller numbers. There is often no operating system to catch your mistakes. A wild pointer does not produce “Segmentation fault”: it silently overwrites a variable, or a peripheral register, and the device misbehaves an hour later.
- It is not write-once. Modern devices receive firmware updates over the air for a decade. Updatability, security, and field diagnostics are part of the job.
Kinds of chips you will meet
Section titled “Kinds of chips you will meet”Four families of chips run code close to hardware. Knowing the differences tells you which kind of firmware you will be writing.
| Chip | What is inside | Memory | Typical code | Example |
|---|---|---|---|---|
| MCU (microcontroller) | CPU core, flash, SRAM, peripherals on one die | KB to a few MB of flash, KB of RAM, all on chip | Bare metal C or an RTOS | STM32F407, nRF52840, RP2040 |
| MPU (microprocessor) | Faster CPU core(s) with an MMU, no or little on-chip memory | External DRAM (hundreds of MB), external flash or eMMC | Embedded Linux | NXP i.MX 8, TI AM335x |
| SoC (system on chip) | Usually an MPU plus GPU, radios, DSPs, sometimes MCU cores, on one die | External DRAM | Linux or Android, plus firmware on the helper cores | Phone application processors, Raspberry Pi’s Broadcom chip |
| FPGA | Reconfigurable logic fabric, no fixed CPU (unless you add one) | Configuration memory plus block RAM | A hardware description (Verilog, VHDL), not a program | Xilinx/AMD Artix, Lattice iCE40 |
The term “SoC” is used loosely: vendors call many MCUs “SoCs” too, especially wireless ones such as the nRF52 or ESP32. The practical question is whether the chip has an MMU and runs Linux, or runs your code directly from on-chip flash. This course is about the second case.
Bare metal, RTOS, or embedded Linux
Section titled “Bare metal, RTOS, or embedded Linux”Once you know the chip, you pick how much system software sits under your application.
Bare metal means your code is the only code on the chip. After reset, your startup code sets up memory and calls main, and main runs forever. You talk to peripherals by writing their registers directly. There is no scheduler, no file system, no memory protection unless you set it up. Bare metal is right for small, single-purpose devices: a battery charger, a sensor, a motor controller, a bootloader. It gives you complete control over timing and the smallest footprint.
RTOS (real-time operating system) means a small kernel, often 5 to 20 KB of flash, that gives you tasks (threads), a priority-based scheduler, queues, mutexes, and timers. FreeRTOS and Zephyr are the two you will meet most. Your code is still compiled into one image with the kernel, and there is still no MMU. An RTOS is right when the device does several independent things with different deadlines: read a sensor every 10 ms, maintain a Bluetooth connection, update a display, log to flash.
Embedded Linux means a full Linux kernel on an MPU with an MMU and external RAM. You get processes, a file system, networking stacks, and every Linux library. You give up boot time (seconds instead of milliseconds), determinism (latency is harder to bound), power (hundreds of mW instead of microwatts in sleep), and cost. It is right for devices with displays, cameras, complex networking, or large software stacks: routers, smart displays, industrial gateways.
| Question | Bare metal | RTOS | Embedded Linux |
|---|---|---|---|
| Typical flash/RAM | 4 KB to 1 MB / 1 KB to 256 KB | 32 KB to 2 MB / 8 KB to 1 MB | 64 MB+ / 64 MB+ |
| Boot time | Microseconds to milliseconds | Milliseconds | Seconds |
| Worst-case interrupt latency | Tens of cycles, fully predictable | Tens of cycles plus kernel critical sections | Microseconds to milliseconds, needs care |
| Sleep current | Microamps or less | Microamps | Milliamps |
| Concurrency model | Superloop plus interrupts | Tasks plus interrupts | Processes, threads |
Many real products combine all three: a Linux MPU for the user interface, an MCU running an RTOS for the radio, and a tiny bare-metal MCU managing the battery.
What a firmware engineer actually does
Section titled “What a firmware engineer actually does”Job descriptions say “develop embedded software”. The day-to-day work looks like this:
- Board bring-up. A new prototype PCB arrives from the factory. You are the first person to run code on it. You check that the debug probe connects, the clock starts, the UART prints, then work through every peripheral. When something does not work you have to decide whether it is your code, the chip, or the board.
- Writing drivers. You read the chip’s reference manual (often 1,500+ pages) and a sensor’s datasheet, then write the code that configures the peripheral, moves data, and handles errors.
- Debugging with instruments. Your tools include a debugger connected over SWD, but also an oscilloscope to see whether a signal actually toggles, a logic analyzer to decode SPI or I2C traffic, and a multimeter or power profiler to see current draw. “The code says it sent the byte” and “the byte appeared on the wire” are different claims.
- Reading datasheets and errata. Chips have bugs. Vendors publish errata sheets listing them. Experienced engineers read the errata before trusting a peripheral.
- Working with electrical engineers. You review schematics (“this pin cannot do I2C”), agree on pin assignments, and explain firmware’s needs (a test point here, a pull-up there). When a board misbehaves you debug it together.
- Making it robust. Watchdogs, fault handlers, safe firmware updates, power-fail-safe storage. A crashed phone app restarts; a crashed pacemaker or car brake controller is a disaster.
- Fitting it. Squeezing a feature into the last 3 KB of flash, or cutting average current from 80 to 40 microamps so the battery lasts two years instead of one.
Most of this course teaches the knowledge behind those tasks. The later parts teach the professional practices (testing, CI, security, release management) that separate hobby projects from products.
The stack, from transistor to application
Section titled “The stack, from transistor to application”Every layer in a microcontroller builds on the one below it. You will work mainly in the top four, but bugs come from all of them.
+-----------------------------------------------+| Application: "sample sensor, send over BLE" | your logic+-----------------------------------------------+| Services: scheduler, logging, storage, FSMs | your code or an RTOS+-----------------------------------------------+| Drivers: uart_write(), spi_transfer() | your code+-----------------------------------------------+| C code: *(volatile uint32_t *)0x40020C14 = x | the language+-----------------------------------------------+| Memory map: flash, SRAM, peripheral regs | one address space+-----------------------------------------------+| Bus: AHB / APB matrix connecting everything | silicon+-----------------------------------------------+| Peripherals: GPIO, UART, timers, ADC, DMA | silicon+-----------------------------------------------+| CPU core: Cortex-M4 (registers, ALU, NVIC) | silicon (Arm design)+-----------------------------------------------+| Digital logic: flip-flops, gates, clocks | silicon+-----------------------------------------------+| Transistors, voltage levels, the PCB | physics+-----------------------------------------------+Here is one concrete trip down the stack. Your application decides to turn on the green LED. Your GPIO driver computes that this means “set pin 12 of port D”. In C that becomes a store of the value 1u << 12 to address 0x40020C18 (the BSRR register of GPIOD). The compiler turns that into a str instruction. The Cortex-M4 core executes it and puts a write transaction on its bus. The bus matrix routes the address to the AHB1 bus, where GPIOD lives. The GPIO peripheral’s logic latches the bit into its output register. That register drives a transistor pair connected to the physical pin, which pulls the pin to 3.3 V, and current flows through the LED.
If any layer is wrong, the LED stays dark. If GPIOD’s clock is not enabled, the peripheral ignores the write. If the pin is not configured as an output, the output register changes but the pin does not. If the LED is soldered backwards, all the firmware is correct and nothing happens. Firmware engineering is the skill of reasoning across these layers.
The constraints that shape everything
Section titled “The constraints that shape everything”These are the facts of life that make firmware different from application programming:
- Memory measured in kilobytes. The STM32F407 is a generous MCU with 1 MB of flash and 192 KB of RAM. Many products use chips with 32 KB of flash and 4 KB of RAM. You will count bytes, and you will care whether a table lives in flash or RAM.
- No operating system underneath. Nobody loads your program, cleans up after it, or catches its crashes. Your code sets up the stack, initialises memory, and configures the clocks.
- No MMU. Every pointer can reach every byte of the address space, including peripheral registers. Writing through a bad pointer can reconfigure hardware.
- Real-time deadlines. A motor control loop that runs 50 microseconds late can destroy the motor. Correctness includes timing: a right answer delivered late is wrong.
- Power budgets. A coin cell holds about 200 mAh. Running at 30 mA, it lasts under 7 hours. Sleeping at 5 microamps and waking briefly, it lasts years. Firmware decides which.
- It cannot crash. Or more precisely, when it crashes it must recover by itself, safely, without a human. Devices sit in walls, fields, and bodies for years.
- Hardware is shared with you. Peripherals run in parallel with your code. Interrupts arrive between any two instructions. Concurrency bugs exist even in a program with no threads.
Families you will meet
Section titled “Families you will meet”You will encounter many MCU families. This course uses one, but the concepts transfer to all of them.
| Family | Vendor | Core | Known for |
|---|---|---|---|
| STM32 | STMicroelectronics | Arm Cortex-M0 to M7, M33 | Huge range, cheap boards, the industry default |
| nRF52 / nRF53 / nRF54 | Nordic Semiconductor | Cortex-M4F, M33 | Bluetooth Low Energy, excellent low power |
| ESP32 | Espressif | Xtensa LX6/LX7, RISC-V on newer parts | Wi-Fi and Bluetooth at very low cost |
| RP2040 / RP2350 | Raspberry Pi | Dual Cortex-M0+ / dual M33 or RISC-V | Programmable I/O blocks, cheap, well documented |
| AVR | Microchip (originally Atmel) | 8-bit AVR | Classic Arduino boards, very simple |
| PIC | Microchip | 8, 16, 32-bit PIC cores | Long industrial history, tiny parts |
| MSP430 | Texas Instruments | 16-bit MSP430 | Ultra-low power, metering |
| RISC-V MCUs | Many (Espressif, WCH, GigaDevice, SiFive) | Open RISC-V ISA | Royalty-free ISA, growing fast |
What Cortex-M is
Section titled “What Cortex-M is”Arm does not make chips. It designs CPU cores and licenses them to chip vendors, who add memory and peripherals around the core and sell the result. Cortex-M is Arm’s family of cores for microcontrollers. They share a programmer’s model: the same Thumb instruction set, the same interrupt controller (the NVIC), the same system timer (SysTick), the same memory map layout, and the same debug interface.
That is why learning one Cortex-M chip teaches you most of the others. An STM32, an nRF52, and an NXP LPC all handle interrupts the same way because Arm designed that part. What differs between vendors is the peripherals: ST’s UART registers look nothing like Nordic’s.
The common members are the M0 and M0+ (smallest, cheapest), M3 (the original mainstream core), M4 (M3 plus DSP instructions and an optional single-precision FPU), M7 (fast, with caches), and the ARMv8-M cores M23 and M33 (which add TrustZone security). The STM32F407 uses an M4 with the FPU, written Cortex-M4F.
The reference board and simulators
Section titled “The reference board and simulators”Every worked example in this course targets the STM32F407VG, the chip on ST’s STM32F4 Discovery board. It has a Cortex-M4F core that runs up to 168 MHz, 1 MB of flash, 128 KB of SRAM plus 64 KB of core-coupled memory, and a built-in ST-Link debug probe, so a single USB cable powers, programs, and debugs it. The board has four user LEDs on PD12 (green), PD13 (orange), PD14 (red), and PD15 (blue), a user button on PA0, an accelerometer on SPI, and an audio DAC on I2C. You will use all of them.
No board? You can still do almost everything:
- Renode is an open-source simulator from Antmicro that models whole boards, including the STM32F4 Discovery. It runs your unmodified ELF file, simulates GPIO, UART, timers, and more, and supports scripted tests. It is the recommended option.
- QEMU can emulate an STM32F405-based board with
qemu-system-arm -M netduinoplus2. Its peripheral model is limited, but it is good for UART-only examples.
A simulator is never a perfect substitute: it will not show you a noisy signal, a marginal clock, or a solder bridge. But it runs the exact same machine code, so everything about C, memory, startup, and interrupts carries over.
How this course is structured
Section titled “How this course is structured”The course has five parts plus a capstone:
- Start Here (this part): what firmware is, setting up tools, the mental model of a running program.
- The C Language: all of C, taught with the machine in mind. This is the heart of the course and the longest part. You will compile and run most of it on your computer before touching hardware.
- C Meets the Metal: reading assembly, registers, startup code, linker scripts, the Cortex-M architecture, clocks, and every common peripheral, all written from scratch with no vendor library.
- Firmware Systems: driver design, state machines, RTOS internals, concurrency, bootloaders, security, debugging, power, and build systems.
- Professional Firmware: testing, communication stacks, storage, design patterns, architecture, safety standards, embedded Linux, and production.
- Capstone: a complete battery-powered sensor node with a bootloader and firmware updates.
How to study it
Section titled “How to study it”- Type everything. Do not copy and paste code. Typing forces you to read each line, and the typos you make teach you what compiler errors look like.
- Break things on purpose. When a lesson says “without
volatilethis loop is optimised away”, removevolatileand look at the disassembly. When it says “forgetting the clock enable does nothing”, forget it and watch nothing happen. You will meet these bugs in real work; meet them first where you know the cause. - Read what the tools tell you. Compiler warnings, map files, disassembly, and register dumps are how professionals see inside the machine. Every lesson asks you to read at least one of them.
- Do the exercises. The quiz checks understanding; the exercises build skill. Skipping them is the most common way to finish a course and still be unable to do the job.
- Go in order the first time. Later lessons assume earlier ones. After that, use the reference pages and jump around.
Common mistakes
Section titled “Common mistakes”- Treating firmware as “small software” and expecting an OS to catch errors: assume every bad pointer silently corrupts something, and design defensively.
- Starting with a vendor HAL and code generator before understanding registers: learn the register level first so you can debug the HAL when it fails.
- Trusting the code instead of the hardware: when a signal “should” be there, measure it with a scope or logic analyzer.
- Ignoring the errata sheet: read it before blaming your own driver for an unexplained peripheral bug.
- Choosing embedded Linux for a job an MCU can do: check boot time, power, and cost requirements first.
- Learning on a board with no debugger: pick hardware with an on-board probe (like the Discovery’s ST-Link) so you can single-step from day one.
Exercises
Section titled “Exercises”- Pick three devices in your home (for example a microwave, a TV remote, a router). For each, decide whether it most likely contains an MCU running bare metal, an MCU running an RTOS, or an MPU running Linux. Write one sentence of reasoning for each based on the constraints in this lesson (power source, user interface, network features).
- Find the label on any electronic board you own, or look up the main chip of a product you use. Identify the vendor and, if it is a microcontroller, which CPU core it uses. A correct answer names the core family (for example “Cortex-M0+” or “Xtensa LX7”).
- Using the numbers in this lesson, compute how long a 200 mAh coin cell lasts at a constant 30 mA, and at an average of 10 microamps. You should get about 6.7 hours and about 2.3 years.
- Redraw the transistor-to-application stack diagram from memory, then annotate where each of these bugs lives: the LED is soldered backwards; the GPIO clock is not enabled; the compiler removed a write because a pointer was not
volatile; the application logic never asks for the LED to turn on. Each bug belongs to a different layer. - Write a one-page “study contract” for yourself: how many hours per week, which hardware option (Discovery board or Renode), and where you will keep your exercise code (a Git repository is the right answer). Keep it; you will revisit it at the end of the course.
Check yourself
Section titled “Check yourself”Question 1:A product must sleep for months on a coin cell, wake every second to read a sensor, and send a radio packet. Which software base fits best?
Linux on an MPU draws milliamps even in its lightest sleep and needs external DRAM. A low-power MCU can sleep at microamps and wake in microseconds, which is what months on a coin cell requires.
Question 2:Your code writes the correct value to a GPIO output register, but the pin does not change. Which explanation is impossible on a microcontroller with no MMU?
A bare-metal MCU has no operating system and no MMU, so nothing checks permissions on a store. The other three are all real, common causes that live at different layers of the stack.
Question 3:Why does learning interrupts on an STM32F407 transfer well to an nRF52840?
Arm designs the core, including the NVIC and SysTick, and licenses it to vendors. Peripherals such as the UART are vendor-specific and differ between ST and Nordic.
Question 4:A pointer in your bare-metal program is uninitialised and your code writes through it. What is the most likely immediate result?
Without an MMU, almost any address is a valid target for a store. The write lands somewhere and the symptom shows up later, which is why such bugs are hard to find. Some addresses do cause a fault, but you cannot count on it.
Question 5:What distinguishes an MCU from an MPU most directly?
The STM32F407 is a 32-bit MCU, so bit width is not the difference. Integration of memory on chip versus external DRAM plus an MMU is the practical dividing line.
Key takeaways
Section titled “Key takeaways”- Firmware is code bound to specific hardware: it knows register addresses, pins, and timing, and it ships inside the product.
- MCUs run firmware directly from on-chip flash; MPUs with MMUs and external RAM run embedded Linux. Pick by power, boot time, determinism, and cost.
- Bare metal, RTOS, and embedded Linux are points on a spectrum of how much system software sits under your code.
- With no OS and no MMU, mistakes do not crash loudly. They corrupt memory or hardware state silently, so firmware must be defensive.
- Arm designs Cortex-M cores; vendors add peripherals. Core features (NVIC, SysTick, exceptions) transfer across vendors, peripherals do not.
- This course targets the STM32F407 on the STM32F4 Discovery, with Renode as the simulator when you have no board.
Next: In Setting Up Your Toolchain you will install the compilers, debugger, and simulators used for the rest of the course and verify each one works.