Course contentsShow
← Rust
Lesson 1831 of 1,91016. Specialized Runtime TargetsFree lesson

Bootstrapping an Embedded Project

Create the entry-point code required to initialize the CPU and jump to the main execution logic.

In this lesson, you will learn how to bridge the gap between hardware power-on and your main() function by manually crafting the entry-point code required to initialize the CPU.

When a microcontroller powers up, it doesn't know about Rust’s safety guarantees or even its runtime stack. The hardware simply jumps to a hardcoded address—the Reset Vector—where it expects to find executable instructions. Since we are operating at a level below the standard library, we must define this entry point ourselves using the #[entry] macro (or a raw #[no_mangle] extern "C") to prevent the compiler from generating a C-style crt0 initialization.

Defining the Reset Sequence

The primary responsibility of your entry point is to prepare the environment for your Rust code. This typically involves three distinct phases:

  1. Stack Pointer Initialization: Before executing any logic, you must ensure the stack pointer register (SP) points to the end of your RAM section, as defined in your linker script.
  2. Memory Initialization: Global variables (static mut) need to be copied from Flash to RAM (the .data section), and uninitialized memory (.bss) must be zeroed out.
  3. Jump to main: Once the runtime state is consistent, you branch into your Rust entry point.

Here is what the bare-metal setup looks like in practice:

#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
    // 1. Initialize the stack pointer (usually handled by hardware/startup.s)
    
    // 2. Initialize memory (the "relocation" phase)
    extern "C" {
        static mut _sdata: u32;
        static mut _edata: u32;
        static _sidata: u32;
    }
    
    // Copy data section from Flash to RAM
    let count = & _edata as *const u32 as usize - & _sdata as *const u32 as usize;
    core::ptr::copy_nonoverlapping(&_sidata, &mut _sdata, count / 4);

    // 3. Jump to the user-defined entry point
    extern "Rust" { fn main() -> !; }
    main();
}

By manually orchestrating these steps, you gain complete control over the CPU state. If you fail to initialize the .bss section or set the stack pointer correctly, your main() function will encounter undefined behavior, such as reading garbage data from unitialized globals or overwriting code segments with stack frames.

Key Takeaway: The boot sequence is the bridge between raw silicon and Rust's safety model; failing to initialize the memory layout manually before invoking main() renders all subsequent safety guarantees void.