Reset Vector và Bootloader trên Cortex-M: VTOR, MSP và jump sequence

Reset vector là gì, VTOR hoạt động ra sao, và chuỗi bước cần thiết để bootloader jump đúng cách vào application - tránh crash sau khi có interrupt đầu tiên.

6 phút đọc
STM32 / Firmware cover

Khi CPU Cortex-M reset, nó làm đúng hai việc: đọc MSP initial value, rồi nhảy vào Reset Handler. Hai giá trị này đến từ vector table - mảng pointer nằm ở địa chỉ được chỉ ra bởi VTOR (Vector Table Offset Register).

Hiểu cơ chế này là nền tảng để viết bootloader đúng. Sai một bước trong jump sequence là crash khi interrupt đầu tiên xảy ra.

CPU startup - từ reset đến Reset_Handler

Ngay sau reset (power-on, NRST, watchdog, hay software reset):

1. CPU đọc [VTOR + 0x00] → load vào SP (MSP)
2. CPU đọc [VTOR + 0x04] → nhảy vào địa chỉ đó (Reset_Handler)

Mặc định VTOR = 0x00000000, được alias tới 0x08000000 (Flash Bank 1) qua BOOT pin config.

Vì vậy khi power-on bình thường:

[0x08000000] = MSP initial value  (ví dụ: 0x20024000 = top of RAM)
[0x08000004] = Reset_Handler addr (ví dụ: 0x08000251 - bit 0 = 1 là Thumb flag)

CPU nạp SP = 0x20024000, nhảy vào Reset_Handler.

VTOR - relocate vector table

VTOR (địa chỉ: 0xE000ED08, SCB->VTOR) cho phép đặt vector table ở bất kỳ đâu trong memory (phải align theo số mũ 2, tối thiểu 128 byte).

Đây là cơ chế bootloader cần khi jump vào application:

Bootloader: vector table tại 0x08000000
Application: vector table tại 0x08010000  (application base)

Nếu bootloader jump vào application mà không set VTOR, CPU vẫn dùng vector table cũ của bootloader. Khi interrupt xảy ra, CPU nhảy vào handler của bootloader - thường là HardFault ngay lập tức.

/* Trong application startup - set VTOR về địa chỉ của mình */
SCB->VTOR = 0x08010000;

Hoặc trong CubeMX-generated system_stm32g0xx.c, SystemInit() sẽ set VTOR tự động nếu VECT_TAB_OFFSET được define đúng.

Jump sequence từ bootloader sang application

Đây là đoạn code nhiều người viết sai:

/**
 * Jump từ bootloader vào application.
 * app_base: địa chỉ bắt đầu của application (phải align 256 bytes cho VTOR)
 */
void BootloaderJumpToApp(uint32_t app_base) {

    /* 1. Validate: kiểm tra MSP value hợp lệ */
    uint32_t msp_value = *(uint32_t*)app_base;
    if ((msp_value & 0xFF000000) != 0x20000000) {
        /* Không phải địa chỉ RAM - không có application hợp lệ */
        return;
    }

    /* 2. Disable tất cả interrupt và clear pending */
    __disable_irq();
    for (int i = 0; i < 8; i++) {
        NVIC->ICER[i] = 0xFFFFFFFF;  /* Disable */
        NVIC->ICPR[i] = 0xFFFFFFFF;  /* Clear pending */
    }

    /* 3. Deinit peripheral nếu cần
     *    (SysTick, UART, DMA, ...) */
    SysTick->CTRL = 0;

    /* 4. Set VTOR về vector table của application */
    SCB->VTOR = app_base;

    /* 5. Set MSP từ entry [0] của application */
    __set_MSP(msp_value);

    /* 6. Lấy Reset_Handler address từ entry [1] */
    uint32_t app_reset_addr = *(uint32_t*)(app_base + 4);
    typedef void (*FuncPtr)(void);
    FuncPtr app_reset = (FuncPtr)app_reset_addr;

    /* 7. Enable interrupt (application sẽ re-enable theo ý mình) */
    __enable_irq();

    /* 8. Jump */
    app_reset();

    /* Không bao giờ đến đây */
    while (1) {}
}

Tại sao phải validate MSP trước?

Nếu vùng Flash chứa application chưa được flash (ví dụ: lần đầu boot sau factory, hoặc update bị ngắt giữa chừng), địa chỉ đó chứa 0xFFFFFFFF. Nếu jump vào 0xFFFFFFFF, CPU crash ngay lập tức.

Validate đơn giản: MSP phải trỏ vào RAM (0x20xxxxxx trên STM32). Nếu không đúng - không jump.

Tại sao phải disable interrupt trước khi jump?

Nếu có interrupt pending khi đang jump:

  • CPU nhảy vào application reset handler
  • Trong quá trình application init (trước khi set VTOR đúng)
  • Interrupt fired → CPU tìm vector table tại VTOR cũ (bootloader)
  • Nhảy vào handler của bootloader → crash

Disable interrupt + clear pending đảm bảo application bắt đầu sạch.

Application: cần set VTOR không?

Nếu application được build với FLASH_ORIGIN = 0x08010000 và CubeMX đã set VECT_TAB_OFFSET đúng trong system_stm32g0xx.c, thì SystemInit() sẽ tự set VTOR:

/* Trong system_stm32g0xx.c - được generate bởi CubeMX */
void SystemInit(void) {
    /* ... clock config ... */
#if defined(USER_VECT_TAB_ADDRESS)
    SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
#endif
}

Nếu không dùng CubeMX hoặc USER_VECT_TAB_ADDRESS không được define, phải set VTOR thủ công ở đầu Reset_Handler hoặc main().

Memory layout điển hình: bootloader + application

Flash 512K (STM32G0B1 dual bank):

Bank 1 (0x08000000 - 0x0803FFFF, 256K):
  ├─ Bootloader:   0x08000000 - 0x0800FFFF (64K)
  └─ Application:  0x08010000 - 0x0803FFFF (192K)

Bank 2 (0x08040000 - 0x0807FFFF, 256K):
  ├─ App Bank 2:   0x08040000 - 0x0807BFFF (240K)  ← OTA target
  └─ Config/EEPROM: 0x0807C000 - 0x0807FFFF (16K)

Linker script của application phải khai báo:

MEMORY {
  FLASH (rx) : ORIGIN = 0x08010000, LENGTH = 192K
  RAM  (xrw) : ORIGIN = 0x20000000, LENGTH = 144K
}

Option bytes: BOOT0 pin và system bootloader

STM32 có một system bootloader trong ROM (system memory) - dùng để flash firmware lần đầu qua UART/USB mà không cần ST-Link. Được kích hoạt bằng cách kéo BOOT0 = HIGH trước khi reset.

Trên STM32G0, có thể lock BOOT0 bằng option byte nBOOT0nBOOT_SEL - hữu ích cho production device không muốn ai boot vào system bootloader.

Mục Giá trị Ghi chú
nBOOT_SEL = 0 BOOT0 pin quyết định boot source Default. BOOT0 = 0 → Flash, BOOT0 = 1 → System Memory.
nBOOT_SEL = 1 nBOOT0 option bit quyết định BOOT0 pin bị ignore. Dùng cho sealed production device.
nBOOT0 = 1 Boot từ Flash (Main Flash Memory) Khi nBOOT_SEL = 1.
nBOOT0 = 0 Boot từ System Memory (ROM bootloader) Khi nBOOT_SEL = 1.

Tóm tắt jump sequence

01 1

Validate

Kiểm tra MSP value tại app_base có trỏ vào RAM hợp lệ không.

02 2

Disable IRQ

Disable tất cả interrupt, clear pending. SysTick CTRL = 0.

03 3

Set VTOR

SCB->VTOR = app_base. CPU sẽ dùng vector table của application.

04 4

Set MSP

__set_MSP(msp_value). Stack pointer về đúng stack của application.

05 5

Jump

Gọi Reset_Handler của application. Application tự init từ đầu.

Bootloader jump sang application - đúng thứ tự.

Checklist khi debug bootloader không jump được

Bài liên quan

Tài liệu tham khảo

Thấy nội dung này hữu ích?

Lưu lại hoặc chia sẻ cho người cũng đang học firmware, BIOS/UEFI và embedded systems.

Nội dung liên quan

Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.

Biến note thành bài viết hoàn chỉnh

Notes là nơi ghi nhanh khái niệm.