RAM (Random Access Memory) is a form of volatile storage where any location can be read or written in roughly equal time regardless of its address. In embedded systems, RAM holds the call stack, heap allocations, global and static variables, and any data that must be modified at runtime.
In practice
RAM is one of the most constrained resources on a microcontroller. Unlike desktop systems where RAM is measured in gigabytes, a typical small MCU may have anywhere from 256 bytes to a few hundred kilobytes. Every local variable, every function call frame, every dynamically allocated buffer, and every DMA staging area competes for the same pool. Exhausting RAM at runtime causes stack overflows or heap fragmentation failures, often with no obvious error message just silent corruption or a hard fault. The blog post "Flood Fill, or: The Joy of Resource Constraints" is a practical illustration of working within tight RAM budgets.
RAM in embedded systems is usually split into distinct regions with different purposes and sometimes different physical banks. On many common architectures and toolchains, the stack commonly grows downward from a high address and the heap grows upward from a low address, though this convention is not universal and some architectures, RTOS configurations, or toolchains arrange these regions differently. Statically allocated data (.data and .bss sections) occupies a fixed region determined at link time. Understanding your linker script and map file is essential for knowing exactly how much RAM each section consumes before the program even starts.
A common pitfall is treating RAM as an isolated resource without accounting for peripherals. On most MCUs, peripheral registers are memory-mapped into the same CPU address space as RAM, but they occupy separate, dedicated address ranges. Reading or writing the wrong address range can accidentally hit a hardware register instead of a RAM location, or vice versa, typically as the result of an incorrect memory map configuration or a linker script mistake rather than any inherent overlap. "Memory Mapped I/O in C" covers how this addressing scheme works in practice.
Because RAM is volatile, its contents are lost on every power cycle or reset. Any data that must survive a reset -- calibration values, configuration, saved state -- must be explicitly written to non-volatile storage (flash, EEPROM, or external memory) before power is removed. Failing to account for this is a common source of bugs when first bring up firmware on new hardware.
Frequently asked
How do I find out how much RAM my firmware is actually using?
Check the
linker map file generated during your build. It lists the sizes of .data (initialized globals), .bss (zero-initialized globals), and the
stack and heap regions. Many toolchains also print a memory usage summary after linking. At runtime you can estimate remaining stack space by filling it with a known pattern at startup (stack painting) and later inspecting how far the pattern was overwritten.
What is the difference between SRAM and RAM in MCU datasheets?
SRAM (Static RAM) is the specific technology used for on-chip RAM in nearly all microcontrollers. Each bit is stored in a bistable latch circuit (typically a cross-coupled inverter pair) rather than a capacitor, so it does not require periodic refresh. When an
MCU datasheet says 'RAM' it almost always means SRAM. DRAM (Dynamic RAM), which requires refresh and is found in larger processors and SoCs, is rarely used as the primary working memory in small MCUs.
Why does my MCU have multiple RAM regions or banks?
Physical die constraints and bus architecture sometimes require splitting RAM across separate banks. Some MCUs add a small, fast tightly coupled memory (TCM) bank alongside a larger general-purpose
SRAM bank. Certain peripherals like
DMA controllers can only access specific RAM regions. Check the datasheet's memory map and your
linker script to make sure performance-critical buffers and DMA descriptors are placed in compatible regions.
Can I execute code from RAM?
Yes, on most MCUs you can copy functions into RAM and execute them from there. This is useful for routines that must run while
flash is being erased or programmed (since flash is typically unavailable during erase/write cycles), or to gain deterministic execution timing by avoiding flash wait states and
cache misses. The function must be explicitly placed in a RAM section via
linker directives or compiler attributes, and the copy from flash to RAM must happen before the function is called.
What happens when the stack and heap collide?
If the heap grows up into
stack space, or the stack grows down into heap space, memory corruption occurs. The processor has no hardware mechanism to detect this on most small MCUs. Symptoms include corrupted variables, wrong return addresses, and hard faults that are difficult to reproduce. Mitigations include sizing the heap and stack conservatively, using stack painting to monitor high-water marks, enabling an MPU guard region if the hardware supports it, and avoiding dynamic allocation entirely in deeply resource-constrained designs.
Differentiators vs similar concepts
RAM is sometimes loosely contrasted with ROM or
flash, which are non-
volatile and generally read-only at runtime. The key distinction is mutability and volatility: RAM can be freely read and written at any time but loses its contents without power, while flash and
EEPROM retain data without power but have write constraints (erase cycles, limited endurance, slower write speed). RAM is also distinct from CPU core
registers, which are on-chip storage inside the CPU core itself and are accessed via instructions rather than ordinary memory load/store operations. Note that peripheral and special-function registers on most microcontrollers are exposed through memory-mapped addresses and are therefore addressable as memory locations, even though CPU core general-purpose registers are not.