Skip to content
BoKSA

Compilers and Toolchains

Compilers and Toolchains

Introduction

You write firmware in C (or C++, Rust, etc.), but the MCU executes machine instructions. A toolchain translates, links, and loads your program onto the target. On embedded systems you almost always cross-compile: the compiler runs on your PC (host) and produces code for the MCU (target).

Understanding compile vs interpret, native vs cross-compile, and what a linker does saves hours when build errors mention "undefined reference" or wrong flash addresses.


Compiler vs interpreter

Compiler Interpreter
Translation Whole program (or translation unit) → machine code Line by line at runtime
Output Binary / object files Immediate execution
Speed Fast repeated execution Slower; startup can be fast
On MCU Standard (GCC, Clang, vendor tools) Rare (MicroPython on some boards)

Embedded firmware is almost always compiled for efficiency and predictable resource use.


Cross-compiler

A cross-compiler runs on one architecture and generates code for another:

1
2
3
Host:  x86-64 PC (Windows/Linux/macOS)
Target: ARM Cortex-M4 MCU
Tool:   arm-none-eabi-gcc

You cannot run the .elf file on your laptop — it is ARM machine code. You flash it via JTAG, SWD, UART bootloader, or USB DFU.

Typical toolchain components

Tool Role
Preprocessor #include, #define
Compiler C → assembly / object code
Assembler Assembly → object code
Linker Combines objects + libraries → single executable
Locator / script Places sections in flash and RAM (linker script)
Flasher / debugger OpenOCD, pyOCD, vendor IDE

Build pipeline (simplified)

flowchart LR SRC[Source .c] --> CC[Compiler] CC --> OBJ[Object .o] OBJ --> LD[Linker] LIB[Libraries] --> LD LD --> ELF[Executable .elf] ELF --> BIN[Binary .bin / .hex] BIN --> FLASH[Flash to MCU]

The linker script (memory.ld) maps .text (code) to flash, .data / .bss to RAM — mismatches cause link errors or boot failures.


Debug vs release builds

Build Optimisation Debug symbols Use
Debug -O0 / low Yes Breakpoints, single-step
Release -Os / -O2 Often stripped Shipping firmware

Optimisation can reorder code — timing measurements and debugging differ between builds.


Higher-level virtual machines

Some systems run bytecode on a VM (Java JVM, .NET CLR). On embedded, bytecode interpreters appear in bootloaders or scripting layers but are uncommon for tight real-time loops. Paravirtualization and VMs are covered in Virtualization.


Relevant topics


Starting points

  1. Build a minimal project and list every command the IDE runs (verbose build log).
  2. Open the .map file — find where main and your buffers landed in memory.
  3. Change linker script RAM size deliberately wrong — observe link failure message.
  4. Compare .elf size vs .bin size — what was stripped?

Focus points

  • Cross-compile is normal — host ≠ target.
  • Linker errors are often missing objects or wrong library order.
  • Linker script is part of architecture — flash/RAM layout matters.
  • Optimisation changes timing — test release builds for real-time.

Key points

  • Compilers translate full programs to machine code; interpreters execute step by step.
  • Cross-compilers build firmware on a PC for an MCU target.
  • Toolchain = compiler + assembler + linker + flash/debug tools.
  • Linker scripts place code and data in the correct memory regions.