This document explains how the nRF52833 microcontroller's memory map physically works with the address bus, Flash memory, RAM, and peripheral registers.
When your Rust code writes to 0x5000_0508, here's what happens at the hardware level:
The nRF52833's ARM Cortex-M4 processor has a 32-bit address bus that can address 4GB of memory space (2^32 = 4,294,967,296 bytes). Each address line can be either HIGH (1) or LOW (0).
Address 0x5000_0508 on the bus:
Address: 0x5000_0508 = 0101 0000 0000 0000 0000 0101 0000 1000 (binary)
Physical Address Lines:
A31 A30 A29 A28 A27 A26 A25 A24 A23 A22 A21 A20 A19 A18 A17 A16
0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0
A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 0
The nRF52833 has address decoding logic that routes addresses to different physical memory/peripheral blocks:
Memory Map (Physical Hardware Blocks):
┌─────────────────────┬─────────────────────┬─────────────────────┐
│ Address Range │ Physical Hardware │ What Lives There │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ 0x0000_0000 │ Internal Flash │ Your program code │
│ to 0x0007_FFFF │ Memory Controller │ Vector table │
│ (512KB) │ │ Constants │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ 0x2000_0000 │ Internal SRAM │ Variables │
│ to 0x2001_FFFF │ Memory Controller │ Stack │
│ (128KB) │ │ Heap │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ 0x5000_0000 │ GPIO Peripheral │ Pin control │
│ to 0x5000_0FFF │ Registers │ Input/output │
├─────────────────────┼─────────────────────┼─────────────────────┤
│ 0x4000_0000 │ Timer Peripherals │ Hardware timers │
│ to 0x4FFF_FFFF │ UART, SPI, I2C etc │ Communication │
└─────────────────────┴─────────────────────┴─────────────────────┘
When you execute core::ptr::write_volatile(0x5000_0508 as *mut u32, value):
- 🖥️ CPU Issues Transaction: Cortex-M4 puts
0x5000_0508on address bus - 🔍 Address Decoder: Hardware logic examines address bits:
- Bits [31:28] =
0x5→ "This is for GPIO peripheral block" - Bits [11:0] =
0x508→ "This is the OUTSET register within GPIO"
- Bits [31:28] =
- 📡 Chip Select: Address decoder activates GPIO peripheral's chip select line
- 💾 Register Write: GPIO peripheral receives the data and updates the OUTSET register
- ⚡ Pin Change: GPIO hardware immediately changes the physical pin voltage
┌─────────────────────────────────────────────────────────────────┐
│ nRF52833 SoC │
│ │
│ ┌─────────────────┐ 32-bit Address Bus ┌─────────────────┐│
│ │ ARM Cortex-M4 │◄──────────────────────►│ Address ││
│ │ Core │ 32-bit Data Bus │ Decoder ││
│ │ │◄──────────────────────►│ Logic ││
│ │ - 64MHz CPU │ │ ││
│ │ - 32-bit arch │ │ ││
│ └─────────────────┘ └─────────────────┘│
│ │ │
│ ▼ │
│ ┌──────────────────────┼──────────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Internal │ │ Internal │ │ GPIO │ │
│ │ Flash │ │ SRAM │ │ Peripheral │ │
│ │ 512KB │ │ 128KB │ │ Registers │ │
│ │ │ │ │ │ │ │
│ │0x0000_0000 │ │0x2000_0000 │ │0x5000_0000 │ │
│ │ to │ │ to │ │ to │ │
│ │0x0007_FFFF │ │0x2001_FFFF │ │0x5000_0FFF │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Floating │ │6-Transistor │ │ Pin │ │
│ │ Gate │ │ SRAM Cells │ │ Drivers │ │
│ │Transistors │ │(Flip-flops) │ │ │ │
│ │(Non-vol.) │ │(Volatile) │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │Timer/UART/SPI │ │ Bluetooth │ │Other Peripherals│ │
│ │ Peripherals │ │ Radio │ │ (ADC, etc.) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼ (GPIO pins only)
┌─────────────────┐
│ micro:bit v2 │
│ PCB │
│ - LED matrix │
│ - Buttons │
│ - Sensors │
│ - Edge pins │
└─────────────────┘
This diagram shows both the logical address bus architecture and the physical nRF52833 SoC structure:
- Top level: Everything inside the nRF52833 chip
- Address bus: How the CPU communicates with different blocks
- Memory blocks: Internal Flash and SRAM with their address ranges
- Physical storage: The actual transistor types used for storage
- External interface: Only GPIO pins connect to the micro:bit PCB
From your Rust code's perspective, everything looks like memory:
*0x0000_1000→ Reads from flash memory*0x2000_1000→ Reads from RAM*0x5000_0508→ Reads from GPIO register
But physically, these go to completely different hardware blocks! This unified memory architecture makes embedded programming much simpler - you don't need special I/O instructions like on x86 processors.
When you write 1 << 21 to GPIO_P0_OUTCLR at 0x5000_050C:
- Address
0x5000_050Ctravels down the address bus - Address decoder routes it to GPIO peripheral
- GPIO register at offset
0x50C(OUTCLR) receives the data - Pin 21 driver sees bit 21 is set and pulls P0.21 LOW
- Physical pin P0.21 changes voltage from 3.3V to 0V
- LED matrix row 1 becomes active (current can flow)
- Photons emerge from the LED! 💡
This is the complete journey from Rust code to photons - all orchestrated by the address bus routing your memory write to the right physical hardware!
RAM access works differently than peripheral registers - it's actual memory storage:
Writing to RAM (let mut counter: u32 = 42;):
// Rust compiler might place this variable at 0x2000_1000
let mut counter: u32 = 42;Physical Process:
- 🖥️ Address Generation: CPU puts
0x2000_1000on address bus - 🔍 Address Decoder: Recognizes bits [31:17] =
0x1000_0→ "This is SRAM" - 📡 SRAM Controller: Gets activated with remaining address bits [16:0] =
0x1000 - 💾 Memory Array: SRAM controller accesses the physical memory cell at row/column determined by address
- ⚡ Storage: Value
42gets stored in the SRAM cell (6-transistor flip-flop circuit)
SRAM Physical Structure:
SRAM Memory Array (128KB):
┌─────────────────────────────────────────────────────────────┐
│ Row Address [16:7] selects one of 512 rows │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐ │
│ │Cell │Cell │Cell │Cell │Cell │Cell │Cell │Cell │ ... │ │
│ │0,0 │0,1 │0,2 │0,3 │0,4 │0,5 │0,6 │0,7 │ │ │
│ ├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤ │
│ │Cell │Cell │Cell │Cell │Cell │Cell │Cell │Cell │ ... │ │
│ │1,0 │1,1 │1,2 │1,3 │1,4 │1,5 │1,6 │1,7 │ │ │
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ │
│ Column Address [6:2] selects which 32-bit word │
└─────────────────────────────────────────────────────────────┘
Why RAM is Fast:
- ⚡ Direct Access: No protocol - just put address on bus, data appears
- 🔄 Volatile Storage: Data stored in flip-flops (powered circuits)
- ⏱️ Single Cycle: Read/write typically completes in 1 CPU cycle
- 🧠 Cache Friendly: ARM can prefetch sequential addresses
Reading from RAM:
let value = counter; // Load from 0x2000_1000- Address
0x2000_1000on bus - SRAM controller decodes row/column
- Memory cell drives data onto data bus
- CPU reads the value (42) in ~1 clock cycle
Flash memory is fundamentally different - it's non-volatile storage using floating gate transistors:
Reading from Flash (Normal Operation):
// Your program code and constants live here
const GREETING: &str = "Hello, micro:bit!"; // Stored in flashPhysical Process:
- 🖥️ Address Request: CPU puts flash address on bus (e.g.,
0x0000_1000) - 🔍 Flash Controller: Decodes address and activates flash memory array
- ⚡ Cell Read: Floating gate transistor's charge level determines if it's 0 or 1
- 📤 Data Return: Flash controller returns data on data bus
- 🧠 CPU Execution: CPU can execute code directly from flash (XIP - eXecute In Place)
Flash Memory Physical Structure:
Flash Memory Array (512KB):
┌─────────────────────────────────────────────────────────────┐
│ Each cell is a floating gate transistor │
│ │
│ Control Gate (Word Line) │
│ │ │
│ ┌────▼────┐ ┌─────────┐ ┌─────────┐ │
│ │ Oxide │ ──► │Floating │ ──► │ Oxide │ │
│ │ Layer │ │ Gate │ │ Layer │ │
│ └─────────┘ │(Stores │ └─────────┘ │
│ │ charge) │ │
│ Source ◄────────┤ ▼▼▼▼ ├────────► Drain │
│ │ Channel │ (Bit Line) │
│ └─────────┘ │
│ │
│ Charged floating gate = 0 │
│ Uncharged floating gate = 1 │
└─────────────────────────────────────────────────────────────┘
Why Flash is Different:
- 🔋 Non-volatile: Data survives power loss (charge trapped in floating gates)
- 📖 Read-Only in Normal Operation: Can't write during program execution
- ⏱️ Slower than RAM: ~3-4 CPU cycles per read (need charge sensing)
- 🗂️ Block Organized: Erased in large blocks (4KB pages)
Writing to Flash (Programming/Flashing): Flash writing requires special high-voltage operations and can only be done by external programmers or bootloaders:
// This happens during `cargo run` - probe-rs writes to flash
// Your compiled program gets stored here
#[no_mangle]
pub extern "C" fn Reset() -> ! {
// This code physically lives in flash memory
main();
}Programming Process (What probe-rs does):
- 🔓 Unlock Flash: Send special command sequence to flash controller
- ⚡ High Voltage: Apply ~12V to control gate to force electrons onto floating gate
- 🗂️ Page Programming: Write entire 4KB pages at once
- ✅ Verify: Read back and confirm data was stored correctly
- 🔒 Lock Flash: Protect against accidental writes
Memory Type | Access Time | Volatility | Use Case
─────────────────────────────────────────────────────────
CPU Registers | 0 cycles | Volatile | Active computation
Cache (L1) | 1 cycle | Volatile | Recently used data
SRAM | 1-2 cycles | Volatile | Variables, stack
Flash | 3-4 cycles | Non-vol. | Program code, constants
External Flash| 10+ cycles | Non-vol. | Large data storage
When your program runs:
- 🚀 Boot: CPU reads reset vector from flash address
0x0000_0000 - 📋 Code Execution: Instructions fetched from flash, executed by CPU
- 📊 Variable Access: Data reads/writes go to SRAM for speed
- ⚡ Register Operations: GPIO writes go to peripheral address space
- 🔄 Constant Access: String literals and
constvalues read from flash
Example Memory Journey:
const MESSAGE: &str = "LED ON!"; // Flash: 0x0000_2000
let mut counter: u32 = 0; // SRAM: 0x2000_1004
loop {
counter += 1; // SRAM read+write
if counter % 1000 == 0 {
unsafe {
// Peripheral register write
core::ptr::write_volatile(0x5000_0508, 1 << 21);
}
println!("{}", MESSAGE); // Flash read for string
}
}Each memory access uses the same address bus but hits completely different physical hardware optimized for different purposes! 🎯
Important Clarification: The BBC micro:bit v2 uses internal memory only - both Flash and RAM are built into the nRF52833 chip itself.
- Everything is internal: Flash, SRAM, and peripherals are all built into the nRF52833 chip
- Unified addressing: Same address bus reaches Flash (0x0000_0000), SRAM (0x2000_0000), and peripherals (0x5000_0000)
- Different storage types: Flash uses floating gate transistors, SRAM uses flip-flop circuits
- External connections: Only GPIO pins connect to micro:bit PCB components
Benefits of Internal Memory:
- 🚀 Speed: Direct connection to CPU via internal buses (no external protocol overhead)
- ⚡ Power: No external memory controller needed, lower power consumption
- 📦 Size: Reduces PCB complexity and physical size
- 💰 Cost: Cheaper than adding external memory chips
- 🔒 Reliability: No external connections that can fail
Comparison with External Memory:
Internal Memory (nRF52833): External Memory (hypothetical):
┌─────────────────────────┐ ┌─────────────────────────────┐
│ CPU ←→ Internal Bus │ │ CPU ←→ SPI/I2C ←→ Ext Chip │
│ ←→ Flash (512KB) │ │ ←→ Controller ←→ Flash │
│ ←→ SRAM (128KB) │ │ ←→ SRAM │
│ │ │ │
│ Access: 1-4 cycles │ │ Access: 10-100+ cycles │
│ Power: Low │ │ Power: Higher │
│ Pins: 0 external │ │ Pins: 4-8+ external │
└─────────────────────────┘ └─────────────────────────────┘
The micro:bit v2 PCB does have other components, but they're sensors and peripherals, not memory:
On the micro:bit PCB (external to nRF52833):
- 🔈 Speaker - Audio output
- 🎤 Microphone - Audio input with LED indicator
- 🧭 Magnetometer/Accelerometer - LSM303AGR sensor (I2C)
- 💡 25x LED Matrix - Directly driven by nRF52833 GPIO
- 🔘 2x Buttons - Connected to nRF52833 GPIO
- 📡 Radio Antenna - For Bluetooth/802.15.4
- 🔌 Edge Connector - 25 pins for external connections
- 🔋 Battery Connector - Power input
- 🔗 USB Connector - Programming and power
None of these are memory - they're all input/output devices or sensors that the nRF52833 communicates with via GPIO, I2C, or SPI.
So when we talk about the memory map:
0x0000_0000 - 0x0007_FFFF: Internal Flash (in nRF52833 silicon)
0x2000_0000 - 0x2001_FFFF: Internal SRAM (in nRF52833 silicon)
0x5000_0000 - 0x5000_0FFF: GPIO Registers (in nRF52833 silicon)
All of these addresses refer to circuits inside the nRF52833 chip itself. There's no external memory bus going off-chip to separate Flash or RAM components.
This is typical for modern microcontrollers - they integrate everything needed for basic operation into a single chip to minimize size, cost, and complexity! 🎯
- Example 02 - Direct register manipulation with minimal dependencies
- Example 03 - Zero dependencies with custom linker scripts
- deep_dive.md - Complete compilation pipeline from Rust to hardware