Linker Script Fundamentals for New MCUs: Memory Maps Without

Linker Script Fundamentals for New MCUs: Memory Maps Without

Linker scripts are often treated as arcane build artifacts copied from reference projects, tweaked cautiously, and rarely revisited. That approach works until it doesn’t. As modern MCUs introduce fragmented memory regions, tightly coupled RAM, external flash, and DMA-sensitive buffers, vague linker assumptions quietly turn into runtime failures.

This article reframes linker scripts for what they really are: the contract between your firmware and the physical memory of the system. Drawing from real bring-up experience on SoM-based devices, we’ll explore how memory maps are constructed, why section placement matters, and how thoughtful linker design enables predictable boot, safe DMA, and scalable firmware architecture—without turning linker scripts into something you fear touching.


The Problem: Why Linker Scripts Feel Scarier Than They Should

Most engineers encounter linker scripts indirectly.

You start with a vendor example.
It builds.
You don’t touch it.

That works until you move to a new MCU or SoM and suddenly:

  • Some buffers mysteriously break DMA.
  • Early boot code crashes before main().
  • External memory “works” in debug but fails in production.
  • Adding one feature pushes the system into random instability.

At that point, the linker script becomes a black box you have to open.

The fear usually isn’t about syntax.
It’s about not knowing what assumptions the script is making about your system.


Why It Matters: Linker Scripts Are System Architecture, Not Build Glue

A linker script answers fundamental questions about your device:

  • Where does code actually live at reset?
  • Which memory is safe for DMA?
  • Which data must survive resets?
  • Which memory can tolerate cache, speculation, or reordering?
  • How does boot ROM hand off control to firmware?

These are architecture questions, not toolchain trivia.

On modern MCUs especially those used in scalable SoM designs—the memory map is no longer a single flat block. You’re dealing with:

  • Multiple RAM regions with different performance and coherency rules
  • External flash or RAM with delayed availability
  • Tightly coupled memory that bypasses cache
  • Peripheral-visible memory windows
  • Bootloader and application coexistence

The linker script is where all of this becomes explicit—or dangerously implicit.


Think of the Linker Script as a Contract

A useful mental model is this:

The linker script is a contract between your firmware and the silicon.

On one side:

  • Your C/C++ code
  • Your startup logic
  • Your RTOS
  • Your drivers and middleware

On the other:

  • Physical memory regions
  • Bus fabric and arbitration
  • Cache and DMA behavior
  • Boot ROM expectations

The linker script defines where every assumption meets reality.

If that contract is vague, the system works only accidentally.


How Memory Maps Are Actually Built

Let’s strip this down to first principles.

1. MEMORY Describes What Physically Exists

At the lowest level, a linker script defines what memory regions exist and their properties.

Not “where variables go”—but what the silicon actually provides.

Think in terms of:

  • Internal RAM vs tightly coupled RAM
  • Cacheable vs non-cacheable memory
  • Internal flash vs external memory
  • Regions visible to DMA vs CPU-only regions

This is the ground truth of the device.

If your MEMORY map is wrong, everything above it is compromised.


2. SECTIONS Describe Intent, Not Mechanics

Sections like .text, .data, .bss, .noinit, or .dma are semantic groupings, not physical ones.

They answer questions like:

  • Is this code needed at reset?
  • Does this data need initialization?
  • Can this buffer tolerate caching?
  • Should this memory survive a soft reset?

The power move is realizing that sections describe intent, and the linker decides where that intent lands physically.


3. Placement Is Where Bugs Are Born or Prevented

When you place a section into a memory region, you’re making a system-level decision.

Examples:

  • Putting .text in external flash assumes availability at reset.
  • Putting .data in cached RAM assumes correct cache maintenance.
  • Putting DMA buffers in normal RAM assumes bus coherency that may not exist.

None of these are “linker problems.”
They are architecture choices expressed through the linker.


Architecture-Level Reasoning: Why Section Placement Matters

Boot Is a Special Phase

Early boot is hostile.

Caches may be disabled.
Clocks may be unstable.
External memory may not exist yet.

Anything that executes before your system is fully initialized must live in the safest, most deterministic memory available.

This is why:

  • Startup code
  • Vector tables
  • Critical init routines

…often belong in tightly coupled or internal memory, even if most code later runs elsewhere.

A linker script that ignores boot phases creates systems that work in debug and fail cold.


DMA Is a Contract Too

DMA doesn’t care about your abstractions.

It sees:

  • Physical addresses
  • Bus visibility
  • Alignment
  • Cache state

If a buffer lives in memory that the DMA engine can’t see—or sees incoherently—your driver may look correct while silently corrupting data.

A well-designed linker script:

  • Creates explicit DMA-safe sections
  • Makes unsafe placement impossible by default
  • Forces engineers to opt-in consciously

This is how linker scripts prevent bugs before they compile.


Scaling Firmware Without Memory Chaos

As systems grow, memory pressure increases:

  • More sensors
  • More buffers
  • More pipelines
  • More concurrency

Without structure, memory usage becomes accidental.

With a thoughtful linker design:

  • Critical paths get predictable memory
  • Optional features land in flexible regions
  • Debug-only features are isolated cleanly
  • Adding a feature doesn’t destabilize unrelated subsystems

This is what allows firmware to scale without rewrites.


Subtle Real-World Context: SoM-Based Systems at Hoomanely

In a multi-device ecosystem like Hoomanely’s, linker design becomes even more important.

Across a modular SoM-based architecture:

  • Trackers gather motion, environmental, and positional signals.
  • EverBowl captures weight, sound events, images, and physiological cues to infer behavior.
  • EverHub aggregates telemetry locally and makes edge decisions before cloud upload.

Despite their differences, these devices share architectural patterns:

  • Common boot flows
  • Shared drivers
  • Reusable RTOS layers
  • Consistent memory contracts across hardware variants

Linker scripts become the unifying layer that allows the same firmware architecture to scale across devices—while respecting the physical differences of each system.

The goal isn’t identical memory maps.
It’s consistent reasoning about memory.


Implementation Thinking

You don’t need to memorize linker grammar to design good linker scripts.

You need to ask the right questions:

  • Which code must run before memory is fully initialized?
  • Which data must be DMA-safe?
  • Which memory can tolerate caching?
  • Which buffers are large but non-critical?
  • Which regions should never contain application logic?

Answer those first—then express them in linker terms.

A good script reads like documentation:

  • Memory regions reflect physical reality
  • Sections reflect software intent
  • Placement reflects system behavior

If you can explain your linker script in plain English, it’s probably doing its job.


Common Pitfalls

Pitfall 1: Copying Vendor Scripts Blindly
Vendor examples optimize for demonstration, not products.

Fix: Treat them as a starting point, not a reference architecture.


Pitfall 2: One RAM to Rule Them All
Flat RAM assumptions collapse with DMA, cache, and concurrency.

Fix: Segment memory by behavior, not just size.


Pitfall 3: Letting Features Pick Their Own Memory
Uncontrolled globals quietly land wherever space is available.

Fix: Use explicit sections for critical classes of data.


Pitfall 4: Debug Builds That Lie
Debuggers often mask timing and memory ordering issues.

Fix: Design linker contracts that hold even without a debugger attached.


Takeaways: Memory Without Fear

Linker scripts don’t need to be feared—or worshipped.

They need to be understood at the right level.

Key ideas to carry forward:

  • A linker script is a system contract, not a build artifact
  • Memory maps express physical reality
  • Sections express software intent
  • Placement expresses architectural decisions
  • Good linker design prevents entire classes of runtime bugs

When you approach linker scripts as architecture—not syntax—you gain confidence, predictability, and scalability.

And once you stop fearing them, you’ll wonder how you ever built systems without treating memory layout as a first-class design decision.


Read more