Project 8.01 Section 8 ⚡ Embedded Relevance: Critical Pointers Address-of & Dereference * MMIO volatile ARM Cortex-M

8.01 Memory Addresses, Dereferencing & Type-Safe Memory-Mapped I/O (MMIO) Peripheral Access

Executive Summary: Exploring the fundamentals of pointers: memory addresses, the address-of operator (&), and dereferencing (*). In embedded firmware, pointers are the foundational mechanism for communicating directly with hardware peripherals via Memory-Mapped I/O (MMIO) register addresses.

💻 1. Annotated Source Code

#include <iostream>
using namespace std;

int main() {

	int myLovelyInt = 150;
	int* somePtr = &myLovelyInt;
	double myDouble = 3.14;
	double* doublePtr = &myDouble;

	cout << "myLovelyInt is originally: " << myLovelyInt << endl;
	cout << "pointer holds value: " << somePtr << endl;
	cout << "pointer dereferenced: " << *somePtr << endl;

	*somePtr = 200;

	cout << "myLovelyInt is now: " << myLovelyInt << endl;

	cout << doublePtr << endl;
	cout << *doublePtr << endl;


	return 0;
}

📐 2. Architecture & UML Class Model

📐 Raw Pointer Memory Addresses & Indirection Model
+ Public - Private # Protected
<<compilation-unit>> PointerAccessModel 32-Bit Address Space
-myVal : int32_t = 150 (@ 0x20000010)
-pVal : int32_t* = &myVal (Holds 0x20000010 in 4 bytes)
+dereference() : int32_t[*pVal]
+modifyTarget(newVal: int32_t) : void[*pVal = newVal]

📚 3. Core C++ Concepts Deep-Dive

1. Pointer Mechanics: Addresses vs Values

A pointer is a variable that stores the physical or virtual memory address of another object. The address-of operator (&) retrieves an object's memory address, while the dereference operator (*) reads or writes the data stored at that address.

2. Pointer Sizing & Architecture

The size of a pointer matches the CPU architecture's address bus width: 4 bytes (32 bits) on 32-bit microcontrollers (e.g. ARM Cortex-M0/M3/M4/M7, ESP32) and 8 bytes (64 bits) on 64-bit systems (x86_64, AArch64).

⚡ 4. Embedded Systems & Hardware Reality

1. Memory-Mapped I/O (MMIO) and the volatile Keyword

In microcontrollers, hardware peripherals (GPIO, Timers, UART, SPI) are mapped directly to specific physical memory addresses in the CPU memory map (e.g., STM32 GPIOA output data register at 0x40020014).

Because peripheral registers can change asynchronously due to external hardware events or clock edges, pointers to MMIO registers must always be qualified with volatile. This prevents the compiler's optimizer from caching register reads in CPU general-purpose registers.

⚠️ MISRA C++:2008 Rule 5-2-7 & Rule 5-2-8

Casting an integer memory address to a pointer is prohibited in general application code, except in low-level hardware abstraction layers (BSP/HAL) accessing hardware registers.

💡 5. Production-Ready Embedded Refactoring

Modern embedded C++ wraps raw MMIO addresses in type-safe, zero-overhead register abstractions:

💡 Production-Ready Refactor
#include <cstdint>

// Type-safe, zero-cost MMIO Register Wrapper
template <uintptr_t Address, typename T = uint32_t>
struct MmioRegister {
    static void write(T value) noexcept {
        *reinterpret_cast<volatile T*>(Address) = value;
    }
    
    static T read() noexcept {
        return *reinterpret_cast<volatile T*>(Address);
    }
    
    static void set_bit(uint8_t bit) noexcept {
        *reinterpret_cast<volatile T*>(Address) |= (1UL << bit);
    }
    
    static void clear_bit(uint8_t bit) noexcept {
        *reinterpret_cast<volatile T*>(Address) &= ~(1UL << bit);
    }
};

// Concrete GPIO Pin Definition (STM32 GPIOA ODR at 0x40020014)
using GpioA_ODR = MmioRegister<0x40020014, uint32_t>;

void toggle_status_led() noexcept {
    GpioA_ODR::set_bit(5);   // Set Pin 5 HIGH (LED ON)
    GpioA_ODR::clear_bit(5); // Set Pin 5 LOW (LED OFF)
}

📝 Knowledge Verification Quiz

Test your understanding of the C++ concepts and embedded microcontroller trade-offs covered in this guide. Click any option for instant feedback.

Q1. Why is the volatile qualifier required when creating pointers to hardware MMIO peripheral registers?
A It forces the compiler to generate actual load/store instructions on every access, preventing the optimizer from caching values in CPU registers
B It allocates the pointer in battery-backed SRAM
C It encrypts the memory bus against side-channel attacks
D It converts 32-bit pointers into 64-bit pointers
Detailed Explanation: volatile informs the compiler that the value at the address can change outside the program's control (e.g., by hardware circuitry), preventing the compiler from omitting or reordering reads/writes.
Q2. On a 32-bit ARM Cortex-M4 microcontroller, what is the value of sizeof(void*)?
A 4 bytes
B 8 bytes
C 2 bytes
D 1 byte
Detailed Explanation: In 32-bit architectures, memory addresses are 32 bits wide, making all pointers exactly 4 bytes in size.
Q3. What is the result of dereferencing a nullptr or unaligned pointer on an ARM Cortex-M microcontroller with UNALIGN_TRP set?
A A hardware UsageFault or HardFault exception is triggered immediately
B The CPU prints a segmentation fault to the console
C The pointer is automatically rounded to the nearest word boundary
D The instruction executes normally with 2 clock cycles delay
Detailed Explanation: Dereferencing invalid or unaligned addresses triggers a hardware UsageFault (or HardFault), causing the microcontroller to jump into its fault ISR.
Q4. What does the address-of operator (&) return when applied to a local variable?
A The memory address on the CPU stack where that variable is currently stored
B The size of the variable in bytes
C The ASCII value of the variable's name
D The CPU clock cycle timestamp
Detailed Explanation: Applying & to a local variable yields its stack memory address.