Embedded Systems Memory and Execution
A microcontroller has no operating system to load your program.
Learning Objectives
Read an MCU memory map: flash, SRAM, EEPROM, external memory, peripherals and system regions.
Trace boot: boot ROM, bootloader, reset sequence, reset vector, start-up code and C runtime initialisation (.data copy, .bss clear).
Use linker scripts, scatter files, sections, symbols and map files; build flash and RAM budgets.
Handle alignment, MPU versus MMU, memory-mapped peripherals and volatile, ISR context, MSP versus PSP, interrupt stack usage and nesting.
Manage DMA buffers and cache coherency; partition bootloader and application; relocate vector tables; understand secure boot and isolation.
Diagnose watchdog resets and crashes; measure stack high-water marks; avoid heap fragmentation; use static allocation and pools.
Size RTOS task stacks, choose RTOS heap schemes, understand context switching, and isolate tasks with MPU regions.
Plain-Language Explanation
A microcontroller has no operating system to load your program. When power comes on, the chip reads two numbers from the start of flash: where the stack should start, and where the first instruction is. Your start-up code then copies the initial values of global variables from flash into RAM, zeroes the rest, and calls main — which usually never returns. Everything has a fixed address decided by the linker script, and every byte of flash and RAM must be budgeted, because there is no swap, no virtual memory and often only a few kilobytes of RAM.
Precise Technical Explanation
MCU memory map An illustrative Cortex-M map (exact addresses come from the device's reference manual):
flash (often aliased at 0), boot ROM data, bss, heap, stacks GPIO, UART, timers, ADC registers External RAM / devices FMC/QSPI memory NVIC, SysTick, SCB, MPU, debug Flash, SRAM, EEPROM and external memory
Flash holds code and constants (erasable in sectors, ~10k–100k cycles); SRAM holds variables (fast, volatile, scarce); EEPROM or emulated EEPROM in flash holds configuration; external QSPI flash or SDRAM extends capacity at lower speed.
Boot ROM and bootloader Many MCUs start in a vendor boot ROM that checks boot pins/option bytes and can load firmware over UART/USB/CAN. A user bootloader at the start of flash validates and starts the application, and performs updates.
On Cortex-M: reset → the core reads the initial main stack pointer from vector table entry 0 and the reset handler address from entry 1 → executes the reset handler in privileged Thread mode on the MSP.
Reset vector and startup code The reset handler (usually startup_<device>.s or a C function) sets up clocks (or calls SystemInit), initialises memory, optionally enables the FPU and caches, calls C++ constructors and then main.
C runtime initialization Before main: copy .data, zero .bss, initialise the C library (newlib __libc_init_array runs .init_array constructors).
Copying initialized data from flash to RAM .data has a load address (LMA) in flash and a run address (VMA) in RAM. Start-up code copies _sidata → [_sdata, _edata) (symbol names vary by vendor).
Clearing .bss Zero [_sbss, _ebss). Until then, globals hold random power-up contents.
Stack and heap boundaries Typical layout: .data and .bss at the bottom of RAM, then the heap growing upward, and the main stack at the top growing downward. Nothing stops them colliding unless an MPU guard or a stack-limit register (Armv8-M MSPLIM/PSPLIM) is configured.
Linker scripts and scatter files GNU ld/LLD use linker scripts (MEMORY + SECTIONS); Arm Compiler uses scatter files; IAR uses .icf files. They all assign input sections to output regions and define symbols for start-up code.
Sections and symbols Beyond .text/.rodata/.data/.bss: .isr_vector, .noinit (not cleared at reset — keeps crash data across a watchdog reset), .ramfunc (code copied to RAM for speed or flash programming), .ccmram/DTCM (tightly-coupled memory), DMA-able RAM sections.
The linker map lists every section and symbol with address and size — the authoritative memory report. Sort by size to find what to shrink.
Static RAM and flash budget Flash = .isr_vector +.text +.rodata +.data (initial values). RAM = .data +.bss + heap + main stack + task stacks. Keep margins (e.g. 20%) for future features and for measurement uncertainty.
Memory alignment constraints Cortex-M0/M0+ fault on unaligned word accesses; M3/M4/M7 allow them for normal loads but not for LDM/STM/LDRD or device memory. Vector tables need alignment to a power of two ≥ their size (at least 128 bytes); DMA buffers often need word or cache-line alignment.
Memory protection unit (MPU) 8–16 regions with base/size/attributes. Armv7-M regions must be power-of-two sized and aligned; Armv8-M uses base/limit pairs. Attributes: access permissions (privileged/unprivileged RO/RW), execute-never, cacheability and shareability.
MMU versus MPU virtual → physical, per page none (addresses are physical) a few large regions Per-process address spaces Cortex-A, x86, RISC-V with Sv39 Cortex-M, Cortex-R, RISC-V PMP Memory-mapped peripheral registers
Peripherals are controlled by reading/writing registers at fixed addresses, described in vendor headers as structs (GPIOA->ODR) generated from SVD files.
volatile and peripheral registers Register accesses must use volatile so the compiler performs every read (status can change on its own) and every write (writes have side effects). volatile does not make read-modify-write sequences atomic: an interrupt between read and write can lose an update — use set/reset registers (BSRR), bit-banding, or disable interrupts briefly.
Interrupt service routine (ISR) context ISRs run in Handler mode, always privileged, on the MSP. Keep them short, non-blocking and re-entrant-aware; defer work to tasks (flags, queues, semaphores with FromISR APIs).
Main stack versus process stack (architecture-specific) Cortex-M has two stack pointers: MSP (used by handlers and, by default, the main program) and PSP (used by RTOS tasks in Thread mode). Exceptions always use the MSP, so interrupt stack usage is concentrated on one stack instead of being added to every task's stack.
Interrupt stack usage and nesting The MSP must hold the deepest possible nesting of interrupts: sum of each priority level's worst-case handler frame plus hardware-stacked context (8 words, 26 with FPU state). Lazy FPU stacking reserves space but saves registers only if the handler uses the FPU.
DMA moves data between peripherals and memory without the CPU. Buffers must be in DMA-accessible RAM (not all regions are), suitably aligned, and not touched by the CPU while the transfer runs; double buffering (ping-pong) gives the CPU one half while DMA fills the other.
Cache coherency for DMA On cores with data caches (Cortex-M7, Cortex-A), clean the cache before DMA reads a buffer the CPU wrote, invalidate before the CPU reads a buffer DMA wrote, or place buffers in an MPU region marked non-cacheable (Modules 8 and 13).
Bootloader/application partitioning Flash is split: bootloader sectors (protected), application slot(s) (A/B for safe updates), configuration and logs. Partitions are aligned to erase sectors so an update never erases the bootloader.
Firmware image layout Image = vector table + code + constants + .data init values, often followed by a header or trailer with version, length, CRC/hash and signature for the bootloader to check.
Vector tables and relocation The bootloader's vector table sits at the flash base; the application's at its own offset. Before jumping, the bootloader sets SCB->VTOR to the application's table, loads the application's initial MSP and branches to its reset handler.
Secure boot and memory isolation concepts A chain of trust: immutable boot ROM verifies the bootloader's signature, which verifies the application. Armv8-M TrustZone splits memory into Secure and Non-secure worlds with SAU/IDAU attributes; readout protection blocks debug access to flash.
Watchdog resets and crash diagnostics An independent watchdog resets the MCU if software stops refreshing it. Record the reset cause (reset status register), and save crash context (fault status registers CFSR/HFSR, stacked PC/LR) in a .noinit RAM area or flash for analysis after reboot.
Stack watermarking and high-water measurement Fill each stack with a known pattern at start-up ("stack painting", e.g. 0xA5), run worst-case scenarios, then scan for the first overwritten byte: the untouched remainder is the margin (implemented below). FreeRTOS: uxTaskGetStackHighWaterMark. Static analysis (-fstack-usage + call graph) gives an upper bound without recursion or function pointers.
Heap fragmentation in long-running firmware A device that allocates and frees varying sizes for months can fail an allocation with plenty of free bytes in total. Hence static allocation, pools and allocate-once policies (Module 6).
Static allocation, pools and deterministic execution Allocate every buffer, queue and task control block statically (xTaskCreateStatic), use fixed-size pools for messages, and avoid unbounded loops and recursion so worst-case execution time can be analysed.
RTOS task stacks and per-task memory Every task has its own stack (and control block). Size = deepest call chain + locals + saved context (+ FPU context) + library usage (printf can need 1–2 KiB) + margin. Too small → silent corruption; too large → wasted RAM.
RTOS heap schemes (overview) FreeRTOS offers heap_1 (allocate only, never free — deterministic), heap_2 (best fit without coalescing; deprecated), heap_3 (wraps malloc), heap_4 (first fit with coalescing), heap_5 (heap_4 across several non-contiguous regions). Many safety projects use only static allocation.
Task creation, context switch and scheduler stack Creating a task builds an initial fake exception frame on its stack. On Cortex-M the context switch runs in the PendSV handler: hardware stacks r0–r3, r12, lr, pc, xPSR on the current PSP; the handler pushes r4–r11 (and FPU s16–s31 if used), saves PSP into the old TCB, picks the next task, restores its registers and PSP, and returns.
MPU regions and task isolation An MPU-aware RTOS (FreeRTOS-MPU, Zephyr userspace, SafeRTOS) gives each unprivileged task access only to its stack and declared regions, reprogramming the MPU on every context switch; a stray write faults instead of corrupting another task.
Safety-oriented memory design Static allocation, MPU isolation, stack guards and monitoring, RAM tests (March C), ECC/parity memory where available, CRC over flash at start-up, watchdogs, and documented memory budgets — practices required by standards such as IEC 61508 and ISO 26262.
Example memory map and budget exercises Below: a real Cortex-M4 link, and a budget checker.
Why It Matters in Real Systems
Most deployed processors are microcontrollers. Firmware failures from stack overflow, heap fragmentation, missing cache maintenance or a wrong linker script are expensive: devices in the field cannot simply be restarted by an administrator. Memory budgets also drive hardware cost — choosing the next-smaller MCU saves money on every unit built.
Key Terms
Fixed assignment of address ranges to memories and peripherals First code executed after reset
Load (flash) / run (RAM) address of a section Linker script / scatter file
Instructions to the linker for placing sections Linker output listing every section and symbol
Main / process stack pointer on Cortex-M Memory protection unit
Filling a stack with a pattern to measure use Vector table offset register RAM section not cleared at reset
Syntax
Linker script essentials (GNU ld / LLD):
Peripheral register access (illustrative address — real ones come from the reference manual; not runnable on a desktop):
volatile constrains certain compiler optimizations on these accesses; it does not by itself provide atomicity, inter-thread synchronisation or hardware barriers (__DSB()/__ISB() are needed after some system-register writes).
ENTRY(Reset_Handler)
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K }
_estack = ORIGIN(RAM) + LENGTH(RAM); /* top of RAM: initial MSP */
SECTIONS {
.isr_vector : { KEEP(*(.isr_vector)) } > FLASH
.text : { *(.text*) *(.rodata*) } > FLASH
_sidata = LOADADDR(.data); /* where the init values are stored */
.data : { _sdata = .; *(.data*) _edata = .; } > RAM AT > FLASH
.bss (NOLOAD) : { _sbss = .; *(.bss*) *(COMMON) _ebss = .; } > RAM
}#include <stdint.h>
/* Example address only. Real addresses come from the MCU reference manual. */
#define PERIPHERAL_STATUS (*(volatile uint32_t *)0x40000000u)
uint32_t read_status(void)
{
return PERIPHERAL_STATUS;
}Complete Runnable Example
A complete, minimal Cortex-M4 firmware — vector table, reset handler that copies .data and clears .bss, and main — linked with the linker script above by a real cross toolchain (Clang 21 and LLD, bundled with Zig, target thumb-freestanding-eabi, -mcpu=cortex_m4). It cannot run on a PC; we inspect the image instead.
/* m16_fw.c — bare-metal Cortex-M4 firmware (illustrative device, no vendor headers) */
#include <stdint.h>
extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss, _estack; /* from the linker script */
int main(void);
void Reset_Handler(void)
{
uint32_t *src = &_sidata;
uint32_t *dst = &_sdata;
while (dst < &_edata) { /* copy .data initial values: flash -> RAM */
*dst++ = *src++;
}
for (dst = &_sbss; dst < &_ebss;) {
*dst++ = 0; /* clear .bss */
}
main();
for (;;) { /* main must not return on bare metal */
}
}
void Default_Handler(void) { for (;;) { } }
__attribute__((section(".isr_vector"), used))
void (*const vector_table[])(void) = {
(void (*)(void))&_estack, /* 0x00: initial main stack pointer */
Reset_Handler, /* 0x04: reset */
Default_Handler, /* 0x08: NMI */
Default_Handler, /* 0x0C: HardFault */
};
uint32_t boot_count = 7; /* .data: 7 stored in flash, copied to RAM */
uint32_t samples[64]; /* .bss: zeroed by Reset_Handler */
const char version[] = "fw 1.0"; /* .rodata
…import subprocess
import sys
script = """ENTRY(Reset_Handler)
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K }
_estack = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS {
.isr_vector : { KEEP(*(.isr_vector)) } > FLASH
.text : { *(.text*) *(.rodata*) } > FLASH
_sidata = LOADADDR(.data);
.data : { _sdata = .; *(.data*) _edata = .; } > RAM AT > FLASH
.bss (NOLOAD) : { _sbss = .; *(.bss*) *(COMMON) _ebss = .; } > RAM
/DISCARD/ : { *(.ARM.exidx*) }
}
"""
open("m16_link.ld", "w").write(script)
cmd = [sys.executable, "-m", "ziglang", "cc", "-target", "thumb-freestanding-eabi", "-mcpu=cortex_m4",
"-Os", "-g", "-ffreestanding", "-nostdlib", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables",
"-Wl,-T,m16_link.ld", "-Wl,-e,Reset_Handler", "-Wl,--gc-sections", "m16_fw.c", "-o", "m16_fw.elf"]
r = subprocess.run(cmd, capture_output=True, text=True)
print("link:", "ok" if r.returncode == 0 else r.stderr)
subprocess.run([sys.executable, "-m", "ziglang", "objcopy", "-O", "binary", "m16_fw.elf", "m16_fw.bin"], check=True)
data = open("m16_fw.bin", "rb").read()
print(f"flash image (m16_fw.bin): {len(data)
…echo "entry point: $(readelf -h m16_fw.elf | awk '/Entry point/ {print $4}')"
echo "--- sections: name, type, address (VMA), size ---"
readelf -S -W m16_fw.elf | sed 's/\[ */[/' | awk '$2 ~ /^\./ && $2 !~ /debug|comment|ARM.attributes|shstrtab|symtab|strtab/ {print $2, $3, $4, $6}'
echo "--- load segments: VirtAddr = run address, PhysAddr = load address ---"
readelf -l -W m16_fw.elf | awk '$1=="LOAD" {f=$7; if (NF>8) f=f" "$8; print "LOAD vaddr", $3, " paddr", $4, " filesz", $5, " memsz", $6, " flags", f}'
echo "--- symbols ---"
readelf -s -W m16_fw.elf | awk '$8 ~ /^(_sidata|_sdata|_edata|_sbss|_ebss|_estack|boot_count|samples|version|Reset_Handler|vector_table|main)$/ {print $8, $2, "size", $3}' | sortLine-by-Line Explanation
The linker script places .isr_vector first in flash, code and constants after it, .data in RAM with its initial values stored right after the code (AT > FLASH), and .bss in RAM without file contents (NOLOAD).
_sidata = LOADADDR(.data) exports the flash address of the initial values; _sdata/_edata and _sbss/_ebss mark the RAM ranges.
The vector table's first word is the initial stack pointer — the top of RAM, 0x20004000 — and the second is the reset handler's address with bit 0 set (Thumb state).
Reset_Handler copies words from flash to RAM until _edata, zeroes .bss, calls main, and parks in an infinite loop.
boot_count is in .data (it has an initial value) and samples in .bss (256 bytes, zeroed). version was meant for .rodata, but its only use (version[0]) was folded into a constant by the optimizer and --gc-sections removed the unused array — it does not appear in the image at all.
Expected Output
The .data segment has VirtAddr 0x20000000 (where the program uses it, in RAM) but PhysAddr in flash (where the initial value 7 is stored) — exactly the LMA/VMA split the reset handler bridges. .bss has a file size of 0 and a memory size of 256 bytes.
link: ok
flash image (m16_fw.bin): 180 bytes
vector table words: 0x20004000 0x08000011 0x080000ad 0x080000ad
initial MSP = 0x20004000 (top of 16 KiB RAM), reset handler = 0x08000011 (odd: Thumb bit set)entry point: 0x8000011
--- sections: name, type, address (VMA), size ---
.isr_vector PROGBITS 08000000 000010
.text PROGBITS 08000010 00009e
.data PROGBITS 20000000 000004
.bss NOBITS 20000004 000100
--- load segments: VirtAddr = run address, PhysAddr = load address ---
LOAD vaddr 0x08000000 paddr 0x08000000 filesz 0x00010 memsz 0x00010 flags R
LOAD vaddr 0x08000010 paddr 0x08000010 filesz 0x0009e memsz 0x0009e flags R E
LOAD vaddr 0x20000000 paddr 0x080000b0 filesz 0x00004 memsz 0x00004 flags RW
LOAD vaddr 0x20000004 paddr 0x20000004 filesz 0x00000 memsz 0x00100 flags RW
--- symbols ---
Reset_Handler 08000011 size 114
_ebss 20000104 size 0
_edata 20000004 size 0
_estack 20004000 size 0
_sbss 20000004 size 0
_sdata 20000000 size 0
_sidata 080000b0 size 0
boot_count 20000000 size 4
main 08000083 size 42
samples 20000004 size 256
vector_table 08000000 size 16Step-by-Step Execution Trace
Power-on reset: the core loads MSP ← 0x20004000 (word 0) and PC ← reset handler (word 1).
Reset_Handler copies 4 bytes (boot_count = 7) from flash to 0x20000000.
It zeroes 256 bytes of samples at 0x20000004.
It calls main, which fills samples and returns; the handler spins forever.
Interactive Visualization
The embedded memory map visualizer above lets you configure flash and RAM sizes and the sizes of .text, .data, .bss, heap and stack; it warns on overflow and overlap. Addresses are illustrative unless you pick the sample profile.
Common Mistakes and Failure Modes
Forgetting AT > FLASH (initial values lost) or the .bss clear (random globals).
Stack and heap colliding silently; no stack guard.
Missing volatile on registers or ISR-shared flags; non-atomic read-modify-write of shared registers.
Long ISRs, printf in ISRs, blocking calls in ISRs.
DMA buffers in cacheable memory without maintenance; DMA buffers on the stack.
Jumping to the application without relocating VTOR or resetting peripheral state.
Tasks sized by guesswork.
Corrected / Safe Pattern
Stack painting and a memory budget checker, both runnable on the host (the logic is identical on a target):
The stack measurement found a peak of 181 words: 70% of the stack, leaving a 29% margin. The budget checker caught that this RAM plan does not fit — it needs 16,512 bytes on a 16,384-byte part, 128 bytes too many — exactly the kind of error to catch in CI rather than in the field. Shrinking the heap or one task stack fixes it.
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define STACK_WORDS 256
#define PAINT 0xA5A5A5A5u
static uint32_t task_stack[STACK_WORDS]; /* stands in for an RTOS task stack */
static void paint(void) { for (int i = 0; i < STACK_WORDS; ++i) task_stack[i] = PAINT; }
static void simulate_use(int words) /* a descending stack uses the top words first */
{
for (int i = STACK_WORDS - words; i < STACK_WORDS; ++i) task_stack[i] = 0x12345678u + (uint32_t)i;
}
static int high_water_unused_words(void) /* scan from the bottom for the first overwritten word */
{
int unused = 0;
while (unused < STACK_WORDS && task_stack[unused] == PAINT) ++unused;
return unused;
}
struct Region { const char *name; uint32_t size; };
int main(void)
{
paint();
simulate_use(57);
simulate_use(181); /* worst case seen during testing */
simulate_use(90);
int unused = high_water_unused_words();
printf("task stack: %d words, peak use %d words (%d%%), margin %d words\n",
STACK_WORDS, STACK_WORDS - unused, (STACK_WORDS - unused) * 100 / STACK_WORDS, unused);
const uint32_t flash_size = 64 *
…task stack: 256 words, peak use 181 words (70%), margin 75 words
flash: 47464 / 65536 bytes (72%)
RAM : 16512 / 16384 bytes (100%) ERROR: does not fitDebugging and Diagnostic Techniques
Map files (-Wl,-Map=fw.map), arm-none-eabi-size, nm --size-sort, Puncover, Bloaty.
Debug probes (J-Link, ST-LINK, CMSIS-DAP) with GDB/IDE: memory and peripheral (SVD) views, watchpoints on stack guards, live variables.
Fault analysis: read CFSR/HFSR/MMFAR/BFAR and the stacked PC after a HardFault; keep a fault handler that saves them to .noinit.
RTOS-aware debugging and trace: task lists with stack high-water marks, SEGGER SystemView, Percepio Tracealyzer.
-fstack-usage + call-graph tools for static worst-case stack estimates.
Performance Implications
Code in flash may run with wait states; caches/accelerators (ART, flash prefetch) or running hot code from RAM/TCM help. DMA frees the CPU but adds coherency work on cached cores. Tightly coupled memory gives deterministic single-cycle access for ISRs and stacks.
Security and Safety Implications
Enable readout protection; verify firmware signatures (secure boot); use the MPU to make flash read-only, RAM non-executable and tasks isolated; place stack guards; never ship with debug ports open; validate update images before erasing the old one (A/B slots); store crash data rather than silently rebooting.
Platform and Language Caveats
All addresses and symbol names above are illustrative (STM32-like); every vendor and start-up file differs.
Armv6-M (M0/M0+) lacks VTOR on some parts, unaligned access support and some MPU features; Armv8-M adds stack-limit registers and TrustZone.
RISC-V MCUs use PMP instead of an MPU and mtvec instead of a vector table of addresses (vectored mode jumps into a table of instructions).
Rust (cortex-m-rt), Ada/SPARK and Zig all provide equivalent start-up code and linker scripts.
Real-World Application
Automotive ECUs, medical devices, industrial controllers, IoT sensors and consumer electronics all follow this pattern: bootloader + application partitions, static allocation, MPU isolation, watchdogs and documented flash/RAM budgets reviewed at every release.
Best Practices Checklist
[ ] Keep the linker script, start-up code and memory map under review with the hardware team.
[ ] Check the map file and budgets in CI; fail the build above thresholds.
[ ] Paint stacks and record high-water marks in testing; keep ≥ 20% margin.
[ ] Allocate statically; use pools; avoid general-purpose heap after start-up.
[ ] Configure the MPU: stack guards, no-execute RAM, read-only flash.
[ ] Use volatile for registers and ISR flags; protect read-modify-write sequences.
[ ] Handle cache maintenance for every DMA buffer on cached cores.
[ ] Record reset causes and fault registers.
Exercises
E1 · Beginner. Why must .data occupy space in both flash and RAM, while .bss occupies only RAM?
Answer: .data's initial values must survive power-off (flash) and the variables must be writable at run time (RAM). .bss starts as all zeroes, which start-up code can create without stored values.
E2 · Intermediate. An application starts at flash offset 0x8000. List what the bootloader must do before jumping to it.
Answer: Validate the image (CRC/signature), de-initialise peripherals and interrupts it used, set SCB->VTOR to 0x08008000, load MSP from the application's vector word 0, then branch to the address in word 1.
E3 · Advanced challenge. A FreeRTOS task with a 512-word stack crashes rarely. How do you find out whether stack overflow is the cause, and how do you size it properly?
Answer: Enable configCHECK_FOR_STACK_OVERFLOW = 2 (canary check at context switch) and/or an MPU or PSPLIM guard; log uxTaskGetStackHighWaterMark under worst-case load (including the deepest library calls such as printf and floating-point formatting); combine with -fstack-usage static analysis; then set the size to the measured/derived worst case plus margin (and the FPU context if the task uses floating point).
Quiz
? What does a Cortex-M core read from the first two words of the vector table at reset?
+ The initial main stack pointer and the reset handler address :: Word 0 = MSP, word 1 = reset vector.
- The reset handler and the NMI handler :: The NMI handler is word 2.
- The firmware version and CRC :: Those are application conventions, not hardware.
- The clock configuration :: Clocks are configured by software.
? In the linked firmware, why did .data have a VirtAddr in RAM but a PhysAddr in flash?
- The linker made an error. :: This is the intended LMA/VMA split.
+ Its initial values are stored in flash and copied to RAM, where the program uses it, by the reset handler. :: AT > FLASH sets the load address.
- RAM is mapped into flash on Cortex-M. :: They are separate memories.
- .data is read-only. :: .data holds writable variables.
? Which practices are typical in safety-oriented firmware? (select all that apply)
+ Static allocation instead of a general-purpose heap :: Predictable, no fragmentation.
+ Stack painting and high-water monitoring :: Measures real stack use.
- Large recursive algorithms on the main stack :: Unbounded stack use is avoided.
+ MPU regions for stack guards and is
…Summary
On a microcontroller the reset vector starts your code directly; start-up code initialises the stack, copies .data from flash to RAM and clears .bss before main. The linker script fixes every address; the map file is the memory report. Budget flash and RAM, measure stacks, allocate statically, use the MPU for protection, treat registers as volatile, keep ISRs short, and handle DMA coherency on cached cores.
References and Further Reading
Joseph Yiu, *The Definitive Guide to Arm Cortex-M3 and Cortex-M4 Processors*.
Arm, *Armv7-M Architecture Reference Manual*; *Armv8-M Architecture Reference Manual*.
FreeRTOS documentation: "Memory Management", "Stack Usage and Stack Overflow Checking".
GNU ld manual, "Linker Scripts"; LLD documentation.
Interrupt by Memfault blog: "From zero to main()", "How to debug a HardFault".