This is the second post of my Cortex‑M7 without hardware series.

In the first part I nailed down the linker script and memory map. Now it is time to take a look at the other half of “boot”: the vector table and the Reset_Handler.

Below is minimal startup.c STM32F746 microcontroller unit.


#include <stdint.h>

extern int                                              main(void);

void                                                    Reset_Handler(void);
void                                                    Default_Handler(void);

__attribute__((section(".isr_vector"))) const uintptr_t vector_table[] = {
  (uintptr_t) (0x20000000 + 320 * 1024), // Initial stack pointer
  (uintptr_t) Reset_Handler,             // Reset handler
  (uintptr_t) Default_Handler,           // NMI
  (uintptr_t) Default_Handler,           // HardFault
};

void Reset_Handler(void)
{
  main();
}

void Default_Handler(void)
{
  while (1)
    ;
}

1) __attribute__((section(".isr_vector")))

The __attribute__ used here ensures that the const uintptr_t vector_table[] ends up in .isr_vector input section so the linker script can place it in Flash at the right memory region (> FLASH) and before any of the other executable instructions, as (KEEP(*(.isr_vector))) is at the top of .text output section.

2) const uintptr_t vector_table[]

Cortex‑M uses a vector table of 32‑bit entries, which are documented in Arm’s exception model/vector table description. 1.

Entries defined on the minimal startup are:

  • Entry 0 is the Main Stack Pointer value (MSP)
    • This is the value that is placed to stack pointer register upon reset by the microcontroller unit
  • Entry 1 is the address of Reset_Handler function.
  • Entry 2 is Non-Maskable Interrupts (NMI) vector
    • Here we simply point to Default_Handler
  • Entry 3 is the HardFault vector (here routed to Default_Handler).
    • Again pointing to Default_Handler

A good practical summary is also available from SEGGER: first entry is MSP, second is reset vector, then core exceptions (NMI, HardFault, …) and finally device interrupt requests (IRQ), all of which are not defined, and also not covered, in this minimal example. 2

Why MSP comes first

The Cortex‑M core loads MSP from entry 0 before executing any instruction, so the stack is valid immediately after reset. That’s one reason Cortex‑M startup can be mostly C code, meaning that is possible to call functions right away. 1 2

In this use case there is hard‑coded value the top of the 320KiB SRAM window:

(uintptr_t)(0x20000000 + 320 * 1024)

This way the the stack grows downward into RAM and not upwards, which is out of bounds of the SRAM of the microcontroller unit. Also note that this must match the RAM region in the linker script, thus any changes to the memory map must be reflected in this constant.

Also note: the stack is not “the SRAM”. SRAM is the whole RAM region, and the stack is just one region inside it. By setting MSP to the top of SRAM, the highest addresses is reserved.

3) Reset_Handler — minimal vs real

In a production startup, Reset_Handler usually:

  1. copies .data from Flash (LMA) to RAM (VMA),
  2. zeros .bss,
  3. runs any early system init,
  4. calls main().

This minimal startup skips the copy/zero step to keep the skeleton tiny. However if .data is not copied and .bss is not zeroed, the C/C++ runtime guarantees for global/static variables are not met and undefined behavior (i.e. initialized globals or C++ constructorshaving incorrect values) starts to appear.

Next

In Part 3 I’ll explain how to build a project using the linker script from first post and the startup file from this post.

References