1. 1. 1. The big picture
    1. 1.1. Normal boot sequence
  2. 2. 2. Memory architecture and firmware layout
    1. 2.1. 2.1 The address space
    2. 2.2. 2.2 Firmware image versus runtime memory
  3. 3. 3. Vector table and firmware image
    1. 3.1. 3.1 Why does the vector table exist?
    2. 3.2. 3.2 Decode your actual BIN example
    3. 3.3. 3.3 Why does the Reset Handler address end in an odd number?
    4. 3.4. 3.4 Your Intel HEX example
    5. 3.5. 3.5 BIN versus HEX
  4. 4. 4. Cortex-M reset and startup sequence
    1. 4.1. 4.1 What happens immediately after reset?
    2. 4.2. 4.2 What is Reset_Handler?
    3. 4.3. 4.3 What does SystemInit() do?
    4. 4.4. 4.4 What happens before main()?
  5. 5. 5. MSP, linker layout, and C runtime initialization
    1. 5.1. 5.1 Why is the initial MSP determined by the linker?
    2. 5.2. 5.2 Understanding .data, .bss, stack, heap, and NOINIT
    3. 5.3. 5.3 Why does .data need copying?
    4. 5.4. 5.4 Why does .bss need clearing?
    5. 5.5. 5.5 What is NOINIT?
    6. 5.6. 5.6 Why did your two firmware images have different initial MSP values?
    7. 5.7. 5.7 How to verify this in IAR
  6. 6. 6. Vector table relocation: VTOR versus memory remapping
    1. 6.1. 6.1 What is VTOR?
    2. 6.2. 6.2 How does the processor find an interrupt handler?
    3. 6.3. 6.3 Your application at 0x08010000
    4. 6.4. 6.4 What does memory remapping do?
    5. 6.5. 6.5 Reconstructing your previous SRAM vector-table design
    6. 6.6. 6.6 Does Cortex-M3 require SRAM remapping?
    7. 6.7. 6.7 Is 48 words enough for your vector table?
    8. 6.8. 6.8 Which method should you use?
  7. 7. 7. IAP bootloader → application handover
    1. 7.1. 7.1 What is IAP?
    2. 7.2. 7.2 Why is jumping to an application different from resetting?
    3. 7.3. 7.3 What does the application need from the bootloader?
    4. 7.4. 7.4 Conceptual C implementation
    5. 7.5. 7.5 Why does the application run its startup code again?
    6. 7.6. 7.6 Application validation
    7. 7.7. 7.7 A useful mental model
  8. 8. 8. Practical inspection and debugging with IAR
    1. 8.1. 8.1 Understand the startup files
    2. 8.2. 8.2 IAR runtime entry point
    3. 8.3. 8.3 A useful debugging sequence
    4. 8.4. 8.4 A practical investigation exercise
  9. 9. 9. Complete boot sequence: one-page reference
    1. 9.1. Normal hardware boot
    2. 9.2. IAP handover
  10. 10. 10. Quick reference
    1. 10.1. Important registers and functions
    2. 10.2. Important concepts
    3. 10.3. Five distinctions worth remembering
  11. 11. 11. Self-check questions
  12. 12. 12. Answers
  13. 13. Final mental model

STM32 Boot Process & Firmware Startup

Personal learning notes · Cortex-M · IAR · IAP bootloader
Scope: From MCU reset to application main(), including IAP bootloader handover. RTOS scheduling and context switching are excluded.

This note focuses on the mechanisms that are easiest to confuse: vector-table lookup, MSP initialization, linker-defined memory layout, C runtime initialization, VTOR versus memory remapping, and bootloader handover.


1. The big picture

A Cortex-M application is not simply a collection of C functions stored in Flash. It is a firmware image with a specific memory layout, an entry point, and initialization requirements.

At reset, the processor needs to answer two questions:

  1. Where should the stack start?
  2. Where should execution begin?

The vector table provides both answers.

Normal boot sequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MCU reset
|
v
Vector table lookup
| Load initial MSP and Reset Handler address
v
Reset_Handler
|
v
SystemInit()
| Low-level MCU initialization (project-dependent)
v
C runtime initialization
| Initialize .data, .bss, and runtime environment
v
main()

There are three distinct layers:

Layer Responsibility
Cortex-M hardware Initial stack and exception entry
Startup code Prepare the MCU and enter the runtime
C runtime Prepare C/C++ program memory and call main()

Key idea: main() is not the processor’s actual entry point. A considerable amount of initialization happens before it.


2. Memory architecture and firmware layout

Medium depth

2.1 The address space

A typical STM32 has several memory regions:

Memory Typical address Purpose
Flash 0x08000000 Firmware and constant data
SRAM 0x20000000 Runtime variables, stack, heap
Peripheral 0x40000000 GPIO, UART, timers, etc.
System memory Device-dependent ROM bootloader and system functions

These are address ranges in the processor’s memory map. The processor accesses them through addresses; it does not need a different instruction set to access Flash or SRAM.

For example:

1
2
3
#define APP_ADDRESS  0x08010000

uint32_t value = *(volatile uint32_t *)APP_ADDRESS;

This reads a 32-bit word from the memory mapped at that address.

2.2 Firmware image versus runtime memory

A firmware image is usually programmed into Flash, but the program does not necessarily execute using all its data directly from Flash.

Consider:

1
2
3
int counter = 10;
int status;
const int version = 3;

A typical arrangement is:

  • counter: initialized variable, stored initially in Flash but used in SRAM.
  • status: zero-initialized variable, allocated in SRAM.
  • version: constant data, typically stored in Flash.

This introduces an important distinction:

The firmware image describes initial contents; the runtime memory layout describes where the program uses those contents.

The linker decides where these sections are placed and how startup code initializes them.


3. Vector table and firmware image

Deep dive

3.1 Why does the vector table exist?

When the processor resets, it cannot call main() directly because it does not yet know where the application’s stack is or where its startup code begins.

It needs a standardized place to find these addresses.

That place is the vector table.

A vector table is an array of 32-bit entries. Each entry contains an address or initial value associated with the processor’s reset and exception/interrupt handling.

The first two entries are especially important:

Offset Entry Meaning
0x00 Initial MSP Initial Main Stack Pointer
0x04 Reset Handler Address of startup code
0x08 NMI Handler Non-maskable interrupt handler
0x0C HardFault Handler Hard fault handler
0x10 MemManage Handler Memory management fault handler, where implemented

The first entry is not a function address. It is a stack address.

The second entry is the function address at which execution starts.

3.2 Decode your actual BIN example

You previously inspected this binary dump:

1
2
3
4
5
Offset  Bytes
0000 F0 05 00 20
0004 D5 40 00 08
0008 61 65 00 08
000C 65 5A 00 08

Cortex-M stores these 32-bit values in little-endian format.

Little endian means the least significant byte is stored first.

For example:

1
2
Bytes:  F0 05 00 20
Value: 0x200005F0

The decoded table is:

Offset Bytes 32-bit value Meaning
0x00 F0 05 00 20 0x200005F0 Initial MSP
0x04 D5 40 00 08 0x080040D5 Reset Handler
0x08 61 65 00 08 0x08006561 NMI Handler
0x0C 65 5A 00 08 0x08005A65 HardFault Handler

Notice the different address regions:

  • 0x200005F0 belongs to SRAM: this is the initial stack pointer.
  • 0x080040D5 belongs to Flash: this is the Reset Handler.

This is exactly what we expect from a normal STM32 application.

3.3 Why does the Reset Handler address end in an odd number?

You may notice:

1
0x080040D5

The least significant bit is 1.

This is intentional.

Cortex-M processors execute Thumb instructions. A function pointer uses bit 0 to indicate Thumb state. The actual instruction address is aligned, while bit 0 carries the state indication.

So:

1
2
3
Stored function address: 0x080040D5
Instruction address: 0x080040D4
Thumb indicator: bit 0 = 1

Do not simply clear bit 0 when interpreting a vector-table entry. The processor expects the Thumb-state indication.

3.4 Your Intel HEX example

You also showed:

1
2
:020000040800F2
:1000000040090020D5000008511E00...

This is not a raw BIN file. It is Intel HEX, a text representation of memory records.

The record format is:

1
:LL AAAA TT [DD...] CC
Field Meaning
LL Number of data bytes
AAAA 16-bit address offset
TT Record type
DD Data bytes
CC Checksum

The first line:

1
:020000040800F2

is an Extended Linear Address record:

  • 02: two data bytes.
  • 0000: record offset.
  • 04: extended linear address record.
  • 0800: upper 16 bits of the address.
  • F2: checksum.

It establishes the upper address portion as 0x0800.

The next record starts with:

1
40 09 00 20 D5 00 00 08

Decoding it gives:

1
2
Initial MSP    = 0x20000940
Reset Handler = 0x080000D5

The same vector-table interpretation applies. Only the representation of the firmware file is different.

3.5 BIN versus HEX

Feature BIN Intel HEX
Format Raw binary bytes ASCII text records
Address information No explicit address in file Encoded in records
Gaps Usually represented by padding or omitted depending on export Can be represented by missing address ranges
Common use Programming at a known base address Firmware distribution and programming

A BIN file needs an external base address. Without that address, the bytes alone do not tell you where they belong in Flash.

A HEX file contains address information, so its records can describe data at different locations.

Remember: a BIN file beginning with a vector table is a common export arrangement, not a universal guarantee. Headers, padding, or custom image formats can precede it.


4. Cortex-M reset and startup sequence

Deep dive

4.1 What happens immediately after reset?

On a normal Cortex-M boot, the processor obtains two initial values from the boot-mapped vector table:

  1. The initial MSP value from offset 0x00.
  2. The Reset Handler address from offset 0x04.

Conceptually:

1
2
MSP = *(uint32_t *)(vector_base + 0x00);
PC = *(uint32_t *)(vector_base + 0x04);

The processor then begins executing the Reset Handler.

This is hardware behavior. It does not require main() or your C runtime to run first.

The actual memory visible at the boot address depends on the device’s boot configuration and memory mapping.

4.2 What is Reset_Handler?

Reset_Handler is the first software entry point after the processor’s reset sequence.

It is usually implemented in assembly startup code.

Its job is to prepare the system for normal C execution.

A simplified conceptual startup sequence looks like this:

1
2
3
4
Reset_Handler:
BL SystemInit
BL C_Runtime_Initialization
BL main

This is illustrative pseudocode, not an exact startup file. Real startup code may use different branch instructions and runtime entry symbols.

4.3 What does SystemInit() do?

SystemInit() is usually provided by the STM32 device support package or project startup code.

Depending on the project, it may:

  • Configure the clock tree.
  • Set up the PLL.
  • Configure the FPU.
  • Set the vector table offset.
  • Perform early MCU-specific initialization.

It is not a universal fixed sequence. Always inspect the actual implementation in your project.

4.4 What happens before main()?

The C runtime prepares the memory required by your application.

For example:

1
2
3
4
5
6
7
8
9
10
int counter = 10;
int status;

int main(void)
{
while (1)
{
counter++;
}
}

Before main():

  • counter must contain 10.
  • status must contain 0.

The runtime initialization performs this work.

This leads to the next important topic: how the linker and startup code cooperate to prepare RAM.


5. MSP, linker layout, and C runtime initialization

Deep dive

5.1 Why is the initial MSP determined by the linker?

The processor needs a valid stack before it can execute normal C code.

But the processor does not know where your project wants the stack to be.

The linker knows the memory regions and section placement defined by the project configuration. It can therefore determine the stack region and provide its initial top address.

The initial MSP is commonly the top of the reserved stack region.

Remember that the Cortex-M stack normally grows downward, toward lower addresses.

For example, imagine this simplified SRAM layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Higher SRAM addresses
+------------------------------+
| Stack region |
| Initial MSP at stack top |
| Stack grows downward v |
+------------------------------+
| Heap region |
| Often grows upward ^ |
+------------------------------+
| .bss / ZI |
| Zero-initialized variables |
+------------------------------+
| .data |
| Initialized variables |
+------------------------------+
Lower SRAM addresses

This is a conceptual layout only. Real section ordering and heap/stack placement depend on the linker configuration.

5.2 Understanding .data, .bss, stack, heap, and NOINIT

These names describe different types of runtime memory.

Section / region Purpose Startup behavior
.data Initialized global/static variables Copy initial values from Flash to RAM
.bss Zero-initialized global/static variables Clear RAM to zero
Stack Function calls, local variables, saved registers MSP starts at stack top
Heap Dynamic memory allocation Managed by allocator
NOINIT Memory that should retain its existing contents Not initialized by normal C runtime

Example:

1
2
3
4
int counter = 10;           // .data
int status; // .bss
static int retry_count = 3; // .data
static int error_code; // .bss

The .data and .bss sections are particularly important because RAM is not automatically initialized to the values required by the C program.

5.3 Why does .data need copying?

Consider:

1
int counter = 10;

The initial value 10 must be stored somewhere in the firmware image so it survives power loss.

Therefore, the initial value is stored in Flash.

However, the program expects counter to be a writable variable in SRAM.

The linker describes both locations:

1
2
Flash: initial value = 10
SRAM: runtime variable counter

At startup, the runtime copies the initial value from Flash into SRAM.

Conceptually:

1
2
3
4
5
6
7
for (src = __data_load_start,
dst = __data_start;
dst < __data_end;
)
{
*dst++ = *src++;
}

This is illustrative pseudocode. Actual startup code uses linker-generated symbols and may copy multiple sections.

5.4 Why does .bss need clearing?

Consider:

1
int status;

The C language requires a static-storage-duration object without an explicit initializer to be zero-initialized.

The runtime therefore clears the corresponding RAM region:

1
2
3
4
for (dst = __bss_start; dst < __bss_end; )
{
*dst++ = 0;
}

Again, this is conceptual code.

Without this step, status could contain whatever value was previously in that SRAM location.

5.5 What is NOINIT?

A NOINIT section is a memory region that startup code intentionally leaves untouched.

This is useful when data must survive a software restart or when another component has already prepared the memory.

Your previous IAP design uses this concept for the SRAM vector table:

  • Bootloader copies the application vector table into SRAM.
  • Application reserves the same SRAM area.
  • C runtime must not clear or overwrite that area.

If the vector-table reservation were accidentally included in .bss, startup could erase the copied vector entries.

If it were included in .data, startup could overwrite them with the application’s initial data image.

So the reservation must be correctly placed and excluded from normal initialization.

5.6 Why did your two firmware images have different initial MSP values?

You showed two initial MSP values:

1
2
Image A: 0x200005F0
Image B: 0x20000940

The difference is:

1
2
0x20000940 - 0x200005F0 = 0x350
= 848 bytes

The key point:

The initial MSP is determined by the final linked memory layout, not simply by the configured stack size.

The linker may place different amounts of data into SRAM before the stack region. Differences can result from:

  • More or fewer global/static variables.
  • Different initialized or zero-initialized sections.
  • Additional library or middleware data.
  • Different alignment and section placement.
  • Different linker configuration or memory regions.
  • Different stack-region placement.

Identical stack sizes do not guarantee identical initial MSP addresses.

5.7 How to verify this in IAR

Rather than guessing from the address, inspect the linker output.

Useful files:

  • .icf: linker configuration file.
  • .map: linker-generated memory map.

Look for:

  • SRAM memory definitions.
  • CSTACK and HEAP placement.
  • Read/write sections.
  • Zero-initialized sections.
  • Any NOINIT section.
  • The initial stack symbol, often associated with CSTACK boundaries.

Exact symbol names depend on the IAR runtime and project configuration.

The important investigation is:

  1. Find the SRAM region.
  2. Identify where .data and .bss are placed.
  3. Find the stack reservation.
  4. Determine its top address.
  5. Compare that address with the first word of the vector table.

Do not assume every project uses the same section ordering.


6. Vector table relocation: VTOR versus memory remapping

Deep dive

This is one of the most important distinctions in your previous bootloader design.

The two mechanisms solve related problems, but they operate at different levels:

  • VTOR changes where the processor looks for the vector table.
  • Memory remapping changes which physical memory is visible at a particular address.

6.1 What is VTOR?

VTOR stands for Vector Table Offset Register.

It is part of the Cortex-M System Control Block (SCB).

1
SCB->VTOR = 0x08010000;

This tells the processor to use the vector table located at 0x08010000.

Imagine an application is stored at:

1
Application Flash base = 0x08010000

Its vector table is also located at that address.

Setting VTOR to this address allows the processor to find the application’s exception and interrupt handlers there.

The vector table itself does not move. Only the address used for vector-table lookup changes.

6.2 How does the processor find an interrupt handler?

For a Cortex-M processor, the vector table contains 16 core exception entries before the external interrupt entries.

For an external interrupt:

1
Vector index = 16 + IRQ number

The handler entry address is:

1
Handler entry address = VTOR + 4 × vector index

For example, if an external interrupt has IRQ number 5:

1
2
3
Vector index = 16 + 5 = 21

Handler entry address = VTOR + 21 × 4

The processor reads the handler address from that location and uses it to enter the interrupt handler.

This explains why an incorrect VTOR can cause the processor to jump to an invalid address when an interrupt occurs.

6.3 Your application at 0x08010000

Suppose the Flash layout is:

1
2
3
4
5
6
7
8
9
0x08000000 +----------------------+
| Bootloader |
0x08010000 +----------------------+
| Application |
| |
| Vector table |
| Reset_Handler |
| Other code |
+----------------------+

For a Cortex-M4 such as the STM32F469, the straightforward approach is:

1
SCB->VTOR = 0x08010000;

Now the processor uses the application’s vector table directly from Flash.

No vector-table copy to SRAM is required.

The address must satisfy the VTOR alignment requirements of the particular Cortex-M implementation. Do not assume any arbitrary Flash address is valid.

6.4 What does memory remapping do?

Some STM32 devices provide a SYSCFG memory-remapping mechanism.

For example, an applicable STM32 may support:

1
__HAL_SYSCFG_REMAPMEMORY_SRAM();

This changes which memory is visible through the address alias at 0x00000000.

Conceptually:

Before remapping:

1
2
3
Address 0x00000000
|
+-- Flash alias

After SRAM remapping:

1
2
3
Address 0x00000000
|
+-- SRAM alias

The important point is that remapping does not copy data. It changes the memory mapping.

If you previously copied the application’s vector table into SRAM, remapping can make that SRAM vector table visible at the zero address.

6.5 Reconstructing your previous SRAM vector-table design

Your previous design can be understood as follows.

Step 1 — Bootloader copies the application vector table to SRAM.

1
2
3
4
5
Application vector table in Flash
|
| Copy
v
SRAM at 0x20000000

Step 2 — Application reserves the beginning of SRAM.

The application reserves a region for the vector table.

You previously mentioned reserving 48 words:

1
48 × 4 = 192 bytes = 0xC0

Conceptually:

1
2
3
4
5
6
7
8
9
SRAM
0x20000000 +--------------------------+
| Application vector table |
| Reserved / NOINIT |
0x200000C0 +--------------------------+
| Other RAM sections |
| .data / .bss / stack |
| heap |
+--------------------------+

The reserved region must not be overwritten by the application’s C runtime initialization.

Step 3 — Application remaps SRAM to address zero.

1
__HAL_SYSCFG_REMAPMEMORY_SRAM();

The processor’s vector lookup at address zero now accesses the SRAM alias.

Step 4 — Interrupts use the SRAM vector table.

The vector table is physically in SRAM, but it is also visible through the zero-address alias.

The application code can still execute from its normal Flash address.

6.6 Does Cortex-M3 require SRAM remapping?

No. This is an important correction to an earlier explanation.

Cortex-M3 supports VTOR. STM32F103, which uses Cortex-M3, also supports VTOR.

SRAM remapping is an alternative mechanism that may be useful for particular boot or compatibility designs. It is not a requirement imposed by Cortex-M3.

Mechanism What changes? Example
VTOR Vector-table lookup base SCB->VTOR = 0x08010000
SRAM remapping Memory visible at an address alias SRAM becomes visible at 0x00000000

6.7 Is 48 words enough for your vector table?

Not necessarily.

You need to consider:

  1. The exact STM32 part number.
  2. The number of implemented core and external interrupt vectors.
  3. The required vector-table alignment.
  4. The size of the SRAM reservation.
  5. The linker placement of the reservation.

For example, a table with 16 core entries and 43 external interrupt entries needs:

1
2
3
16 + 43 = 59 words

59 × 4 = 236 bytes

A 192-byte reservation would not hold that complete table.

This is an example of why the exact MCU reference manual and startup vector definition matter. Do not copy a reservation size from another project without checking it.

6.8 Which method should you use?

For your STM32F469 application, prefer the direct VTOR method:

1
SCB->VTOR = APP_ADDRESS;

It is simpler and avoids the need to:

  • Copy the vector table into SRAM.
  • Reserve the beginning of SRAM.
  • Protect the reservation from runtime initialization.
  • Remap SRAM to address zero.

Keep the SRAM-remapping method in your notes because it helps explain your older design and is useful when working with devices or boot architectures that need it.


7. IAP bootloader → application handover

Deep dive

7.1 What is IAP?

IAP means In-Application Programming.

In a typical STM32 bootloader design, the bootloader can:

  • Receive a firmware image.
  • Erase and program the application Flash region.
  • Validate the image.
  • Transfer execution to the application.

A simplified Flash layout:

1
2
3
4
5
6
7
8
9
10
11
12
13
Flash
0x08000000 +-------------------------+
| Bootloader |
| |
| Firmware update logic |
| |
0x08010000 +-------------------------+
| Application |
| |
| Vector table |
| Reset_Handler |
| Application code |
+-------------------------+

The bootloader and application are separate firmware images with separate vector tables and linker configurations.

7.2 Why is jumping to an application different from resetting?

When the MCU performs a hardware reset, the processor enters its reset sequence and initializes its architectural state according to the hardware reset behavior.

But when the bootloader jumps to the application, there is no hardware reset.

The processor continues running with much of its existing state.

For example:

  • Peripheral registers may retain their previous configuration.
  • NVIC interrupt enable and pending states may remain.
  • SysTick may still be running.
  • The clock configuration may have been changed by the bootloader.
  • The processor may have a different stack configuration or interrupt state.

Therefore, a bootloader must prepare the system before transferring control.

7.3 What does the application need from the bootloader?

The application vector table contains two important initial values:

1
2
APP_ADDRESS + 0x00 → Initial MSP
APP_ADDRESS + 0x04 → Reset_Handler

For example:

1
2
3
4
APP_ADDRESS = 0x08010000

Initial MSP = *(uint32_t *)0x08010000
Reset_Handler = *(uint32_t *)0x08010004

The bootloader must:

  1. Validate that the application image is usable.
  2. Stop or disable interrupt sources as appropriate.
  3. Stop SysTick.
  4. Clear or disable relevant NVIC interrupt states.
  5. Set the application’s vector-table base.
  6. Load the application’s initial MSP.
  7. Branch to the application’s Reset Handler.

The application then runs its own startup sequence.

7.4 Conceptual C implementation

The following shows the essential operations, but is not production-ready code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#define APP_ADDRESS  0x08010000U

typedef void (*AppEntry)(void);

void JumpToApplication(void)
{
uint32_t app_msp;
uint32_t app_reset;

app_msp = *(volatile uint32_t *)(APP_ADDRESS);
app_reset = *(volatile uint32_t *)(APP_ADDRESS + 4U);

/*
* Validate app_msp and app_reset here.
* Also validate the image itself if required.
*/

__disable_irq();

/* Stop SysTick. */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;

/*
* Disable and clear relevant NVIC interrupts.
* Exact handling depends on the MCU and bootloader.
*/

/* Point exception lookup to the application vector table. */
SCB->VTOR = APP_ADDRESS;

__DSB();
__ISB();

/*
* Set the application's initial stack pointer,
* then transfer execution to its Reset_Handler.
*
* This final handover is best implemented in
* carefully controlled assembly.
*/
__set_MSP(app_msp);

((AppEntry)app_reset)();
}

Important implementation caveat: Changing MSP inside an ordinary C function and then calling another C function is potentially unsafe. The compiler may generate stack accesses or function epilogues based on the original stack. A production bootloader normally uses a carefully controlled assembly handover (or a suitable naked function with an assembly sequence) to load MSP and branch without using the old stack afterward.

The example illustrates the required values and operations, not a complete safe implementation.

7.5 Why does the application run its startup code again?

After the bootloader branches to the application’s Reset Handler, the application starts from its own startup entry.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Bootloader
|
+-- Validate application
|
+-- Disable interrupts and prepare hardware
|
+-- Set application VTOR
|
+-- Load application MSP
|
+-- Branch to application Reset_Handler
|
v
SystemInit()
|
v
C runtime initialization
|
v
main()

The application’s startup code initializes its own .data and .bss, prepares the runtime, and calls main().

This is why the application should be built as a standalone image with its own vector table and startup code.

However, remember: the startup code running again does not mean the MCU has been reset. The application’s peripherals and clocks may still be in the state left by the bootloader.

7.6 Application validation

Before jumping, don’t blindly trust the two vector-table words.

At minimum, consider checking:

  • The MSP lies within the application’s permitted SRAM range.
  • The MSP meets the required alignment.
  • The Reset Handler points into an allowed executable region.
  • The Reset Handler has the expected Thumb-state bit.
  • The image passes its integrity check, such as CRC or a cryptographic signature.

The exact validation rules depend on your memory layout and security requirements.

7.7 A useful mental model

Think of bootloader handover as preparing a new program environment, not rebooting the MCU.

The bootloader must provide the application with the conditions it expects:

Requirement Who prepares it?
Application vector-table base Bootloader
Application initial MSP Bootloader
Valid application image Bootloader
.data initialization Application runtime
.bss initialization Application runtime
Application peripherals and clock assumptions Bootloader and/or application startup, by design
main() execution Application runtime

8. Practical inspection and debugging with IAR

Medium depth

This section connects the concepts to your actual development workflow.

You don’t need to memorize every startup symbol. You need to know where to look when something goes wrong.

8.1 Understand the startup files

In an IAR STM32 project, look for files such as:

1
2
3
startup_stm32xxxx.s
system_stm32xxxx.c
stm32xxxx_flash.icf

Their roles are different:

File Responsibility
startup_stm32xxxx.s Reset Handler, vector table, initial startup sequence
system_stm32xxxx.c System-level configuration, often including SystemInit()
.icf Linker memory regions and section placement
.map Final linked memory layout and symbol information

The exact filenames depend on the STM32 device and project.

8.2 IAR runtime entry point

You may see a symbol such as:

1
__iar_program_start

This is associated with the IAR runtime startup process.

It is not the same symbol as the ARM Compiler entry point __main.

A common conceptual IAR startup flow is:

1
2
3
4
5
6
7
8
9
10
11
12
13
Reset_Handler
|
v
SystemInit()
|
v
__iar_program_start
|
v
Runtime initialization
|
v
main()

The exact order and implementation depend on the startup file and runtime configuration.

You may also encounter __low_level_init(). It is an IAR runtime hook that can be used for early initialization and can affect normal data initialization behavior. Check the IAR documentation and your actual project implementation before changing it.

Important distinction: SystemInit() is generally part of the MCU support/startup layer; __iar_program_start is part of the IAR runtime. They have different responsibilities.

8.3 A useful debugging sequence

Set breakpoints at these locations:

Breakpoint What to investigate
Reset_Handler Did the processor enter the expected startup code?
SystemInit() Are clocks and early hardware settings configured correctly?
__iar_program_start Did control reach the IAR runtime?
__low_level_init() Is early runtime initialization being customized?
main() Has startup completed successfully?

You can also inspect:

  • SCB->VTOR
  • MSP
  • The first two vector-table entries
  • .data and .bss addresses
  • The application Flash base
  • The linker map

8.4 A practical investigation exercise

Suppose your application is stored at 0x08010000, but it fails immediately after the bootloader jumps to it.

Investigate in this order:

  1. Read the first two words at 0x08010000.
  2. Check that the MSP points into valid SRAM.
  3. Check that the Reset Handler points into the application Flash region.
  4. Check that VTOR is set to 0x08010000.
  5. Check that interrupts are not firing through the wrong vector table.
  6. Break at the application’s Reset_Handler.
  7. Continue through SystemInit() and IAR runtime initialization.
  8. Check whether main() is reached.

This sequence follows the actual startup dependency chain instead of guessing at the cause.


9. Complete boot sequence: one-page reference

Normal hardware boot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Hardware reset
|
v
Boot memory mapping
| Determine which memory is visible at the boot address
v
Read vector table
| Word 0 -> MSP
| Word 1 -> Reset Handler
v
Reset_Handler
|
v
SystemInit()
| Clock and MCU initialization
v
C runtime
| Copy .data
| Clear .bss
| Prepare runtime
v
main()

IAP handover

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bootloader
|
+-- Validate application and prepare hardware
|
v
Set application VTOR and MSP
|
v
Branch to application Reset_Handler
|
v
Application startup
|
v
main()

In the IAP path, the bootloader performs the initial handover in software. There is no hardware reset between the two firmware images.


10. Quick reference

Important registers and functions

Name Purpose
SCB->VTOR Selects vector-table base
__set_MSP(value) Sets Main Stack Pointer
__get_MSP() Reads Main Stack Pointer
__disable_irq() Disables configurable interrupts globally
__DSB() Data Synchronization Barrier
__ISB() Instruction Synchronization Barrier
SystemInit() Project/device-specific early initialization
Reset_Handler Firmware startup entry
__iar_program_start IAR runtime startup entry
__low_level_init() Optional IAR early initialization hook
__HAL_SYSCFG_REMAPMEMORY_SRAM() Enables SRAM remapping on supported STM32 devices

Important concepts

Concept Remember it as
Vector table Where the processor finds initial stack and exception handlers
Initial MSP Top of the initial main stack
Reset Handler First software startup entry
VTOR Where the processor looks for the vector table
Memory remapping Which physical memory appears at an address alias
.data Initialized variables copied into RAM
.bss Zero-initialized variables
NOINIT Memory intentionally left untouched by runtime initialization
Linker Decides memory layout and section placement
C runtime Prepares the C program environment before main()

Five distinctions worth remembering

  1. Vector table vs startup code: The vector table tells the processor where to start; startup code prepares the application.
  2. MSP vs stack size: MSP is an address; stack size is a memory reservation.
  3. Flash image vs RAM runtime: Initial variable values may live in Flash but be used from RAM.
  4. VTOR vs remapping: VTOR changes the vector lookup base; remapping changes the memory visible at an address.
  5. Application jump vs hardware reset: A branch to Reset Handler does not reset peripheral or processor state.

11. Self-check questions

Try answering these without looking at the previous sections.

Q1. Immediately after a normal Cortex-M reset, where does the processor get its initial MSP and Reset Handler address?

Q2. Why does the first vector-table entry point to SRAM while the second usually points to Flash?

Q3. If two firmware images have identical stack and heap sizes, why might their initial MSP values still differ?

Q4. Why must a bootloader jump to the application’s Reset Handler instead of directly calling main()?

Q5. What is the difference between changing VTOR and remapping SRAM to address zero?

Q6. Why must an SRAM vector-table reservation be excluded from .data and .bss initialization?

Q7. After an IAP jump, why might an application behave differently from one started by a hardware reset?


12. Answers

Q1. Initial MSP and Reset Handler lookup

The processor reads the first two 32-bit entries from the boot-mapped vector table. Entry 0 supplies the initial MSP; entry 1 supplies the Reset Handler address.

Q2. Why SRAM and Flash?

The stack is a runtime memory structure, so it resides in writable SRAM. The startup code normally resides in nonvolatile Flash.

Q3. Why can initial MSP differ?

The linker determines the stack’s actual placement. Other RAM sections, alignment, and linker configuration can change the stack’s location even when its reserved size stays the same.

Q4. Why jump to Reset_Handler?

The Reset Handler performs startup work, including low-level initialization and entering the C runtime. Calling main() directly would bypass those required steps.

Q5. VTOR versus remapping?

VTOR changes the address used for vector-table lookup. Memory remapping changes which memory is visible through a particular address alias, such as 0x00000000.

Q6. Why protect the SRAM vector table?

Otherwise, runtime initialization could overwrite the vector entries copied by the bootloader, breaking exception and interrupt handling.

Q7. Why can IAP startup differ from hardware reset?

An IAP jump does not perform a hardware reset. Peripheral configuration, clock settings, interrupt states, and other hardware state may be inherited from the bootloader.


Final mental model

If you remember only one chain, remember this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vector table
|
v
Initial MSP + Reset Handler
|
v
Startup code
|
v
System initialization
|
v
C runtime initialization
|
v
main()

And if you remember only one distinction about IAP:

A bootloader jump transfers execution; it does not recreate the hardware reset state.

That distinction explains many of the precautions required when designing a reliable bootloader.


End of class note.