Loading header...

Toolchains, Builds, and Project Structure

A firmware build is a controlled transformation from source code into a binary image that can run on a target. Reproducible builds make debugging, releases, and field support possible.

Learning Objectives

By the end of this lesson, you should be able to identify toolchain stages, explain common build artifacts, organize a firmware repository, and create build settings that are visible, repeatable, and reviewable.

Toolchain Stages

flowchart LR C[.c and .s source] --> CC[Compiler and assembler] CC --> OBJ[Object files] OBJ --> LINK[Linker plus script] LINK --> ELF[ELF with symbols] ELF --> HEX[HEX or BIN image] ELF --> MAP[Map file]

The compiler translates each source file. The linker places code and data into memory regions. The image tool extracts the format needed by the programmer or bootloader.

Important Artifacts

Artifact Purpose
.o compiled object file
.elf linked image with symbols and debug information
.map memory placement and symbol map
.hex Intel HEX programming file
.bin raw binary image
.lst optional mixed source and assembly listing

Always archive the ELF and map file for releases. They are essential for crash analysis.

Project Structure

firmware/
  app/
  services/
  drivers/
  bsp/
  startup/
  include/
  tests/
  tools/
  build/

Keep generated build output out of source directories. Keep vendor code isolated so updates are reviewable.

Compiler and Linker Flags

Typical debug flags include symbols and low optimization:

-g3 -Og
-Wall -Wextra
-ffunction-sections
-fdata-sections

Typical release builds use stronger optimization and still keep warnings enabled:

-O2
-Wall -Wextra -Werror
-ffunction-sections
-fdata-sections

Linker garbage collection removes unused sections:

-Wl,--gc-sections
-Wl,-Map=firmware.map

Worked Example: Build Identity

Embed version information in every image:

char fw_ver[] = "1.4.2";
char fw_sha[] = GIT_SHA;
char fw_type[] = BUILD_TYPE;

The build system can pass definitions such as -DGIT_SHA=\"d7a9ff7\". Field logs can then identify the exact code running on a device.

Reproducibility Checks

  • Build from a clean checkout.
  • Pin compiler version in documentation or container image.
  • Record linker script and startup files in version control.
  • Make warnings visible in CI.
  • Generate and review code-size reports.

Common Mistakes

  • Manually changing IDE settings that are not committed.
  • Shipping only a BIN file and losing debug symbols.
  • Mixing vendor generated code with hand-written application code.
  • Ignoring warnings until release week.
  • Not checking flash and RAM usage after feature additions.

Summary

A firmware toolchain compiles, links, maps, and packages code. A maintainable project makes build settings explicit, preserves debug artifacts, separates code layers, and produces identifiable images.

Further Reading

  • GNU Arm Embedded Toolchain documentation.
  • CMake, Ninja, and Make documentation for embedded builds.
  • Memfault, "Firmware Build and Release Practices."

Mind Map

mindmap root((Firmware Build)) Core concept Source to image Linker places memory Artifacts aid debug Applications MCU firmware Bootloader image CI release Factory programming Calculations Flash usage RAM usage Stack reserve Build version Design rules Commit settings Archive ELF and map Isolate vendor code Pin toolchain Practical checks Clean build Warning policy Size report Git SHA embedded Common mistakes IDE-only config Lost symbols Untracked generated code Ignored warnings