Startup Code STM32: Reset Handler, Vector Table và những gì xảy ra trước main()

Giải thích startup code STM32: vector table, reset handler, MSP, copy .data, zero .bss - những gì firmware phải làm trước khi gọi main().

6 phút đọc
STM32 / Firmware cover

Mỗi lần STM32 reset - dù là power-on, watchdog, hay bạn nhấn nút RESET - CPU không nhảy thẳng vào main(). Nó đọc từ một địa chỉ cố định, tìm vector table, rồi nhảy vào reset handler. Chỉ sau khi reset handler làm xong công việc chuẩn bị, main() mới được gọi.

Hiểu cái chuỗi này giúp debug được những lỗi “lạ nhất” - firmware bị crash trước khi main() chạy, biến global bị sai, bootloader không jump được vào application.

Vector Table là gì?

Vector table là một mảng các địa chỉ (pointer) nằm ở đầu Flash, bắt đầu từ 0x08000000 trên STM32.

/* Cấu trúc vector table - simplified */
uint32_t VectorTable[] = {
    (uint32_t)&_estack,           /* [0] MSP - giá trị khởi tạo Stack Pointer */
    (uint32_t)Reset_Handler,      /* [1] Reset Vector - địa chỉ reset handler */
    (uint32_t)NMI_Handler,        /* [2] NMI */
    (uint32_t)HardFault_Handler,  /* [3] HardFault */
    /* ... các exception và interrupt handler khác ... */
    (uint32_t)SysTick_Handler,    /* [15] SysTick */
    (uint32_t)WWDG_IRQHandler,    /* [16] Peripheral interrupt 0 */
    /* ... */
};

Hai entry đầu tiên đặc biệt quan trọng:

  • Entry [0] - không phải địa chỉ hàm, mà là giá trị khởi tạo của Main Stack Pointer (MSP). CPU load giá trị này vào register SP ngay khi reset.
  • Entry [1] - địa chỉ của Reset Handler - hàm đầu tiên CPU nhảy vào.

Chuỗi khởi động từ reset đến main()

01 RESET

CPU reset

CPU đọc 0x08000000 → load MSP. Đọc 0x08000004 → nhảy vào Reset_Handler.

02 COPY

Copy .data Flash → RAM

Reset handler copy vùng initialized data từ LMA (Flash) sang VMA (RAM).

03 ZERO

Zero-fill .bss

Reset handler zero-fill toàn bộ section .bss trong RAM.

04 INIT

SystemInit()

Cấu hình clock, PLL, flash wait state. Gọi trước main().

05 MAIN

main()

Application code bắt đầu chạy với môi trường C đã sẵn sàng.

Từ khi CPU reset đến khi main() được gọi.

Reset Handler - assembly + C

File startup_stm32g0xx.s (tạo bởi CubeMX) thực chất làm rất ít:

Reset_Handler:
  /* 1. Nếu có FPU, enable Coprocessor Access Register */

  /* 2. Copy .data từ Flash sang RAM */
  ldr   r0, =_sdata      /* VMA start */
  ldr   r1, =_edata      /* VMA end */
  ldr   r2, =_sidata     /* LMA start (nguồn trong Flash) */
copy_loop:
  cmp   r0, r1
  ittt  lt
  ldrlt r3, [r2], #4
  strlt r3, [r0], #4
  blt   copy_loop

  /* 3. Zero-fill .bss */
  ldr   r0, =_sbss
  ldr   r1, =_ebss
  mov   r2, #0
zero_loop:
  cmp   r0, r1
  itt   lt
  strlt r2, [r0], #4
  blt   zero_loop

  /* 4. Gọi SystemInit() */
  bl    SystemInit

  /* 5. Gọi main() */
  bl    main

  /* 6. Nếu main() return - vòng lặp vô tận */
infinite_loop:
  b     infinite_loop

Đây là lý do biến global int x = 5; có đúng giá trị khi main() bắt đầu - bước 2 đã copy từ Flash sang RAM. Và lý do int y; bằng 0 - bước 3 đã zero-fill.

MSP - Stack Pointer được khởi tạo ở đâu?

Entry [0] của vector table là giá trị nạp vào MSP. Giá trị này thường là địa chỉ cuối RAM (stack grows down trên Cortex-M):

/* Trong linker script */
_estack = ORIGIN(RAM) + LENGTH(RAM);  /* 0x20000000 + 144K = 0x20024000 */
/* Trong vector table */
uint32_t VectorTable[] = {
    (uint32_t)&_estack,   /* MSP = 0x20024000 */
    ...
};

Nếu giá trị này sai (ví dụ linker script khai báo sai RAM LENGTH), CPU vẫn khởi động nhưng stack sẽ overlap với data - dẫn đến corruption khó debug.

VTOR - Vector Table Offset Register

Mặc định, CPU tìm vector table tại 0x00000000 - được map tới 0x08000000 (Flash) qua BOOT pin hoặc alias.

VTOR (Vector Table Offset Register) cho phép đặt vector table ở vị trí khác. Đây là cơ chế quan trọng khi làm bootloader:

/* Application cần relocate vector table về địa chỉ của mình */
#define APP_BASE_ADDRESS  0x08010000  /* Application bắt đầu tại đây */

SCB->VTOR = APP_BASE_ADDRESS;
/* Từ giờ, CPU tìm vector table tại 0x08010000, không phải 0x08000000 */

Nếu bootloader jump vào application mà không set VTOR, application sẽ dùng vector table của bootloader - interrupt handler sẽ nhảy sai, crash ngay khi có interrupt đầu tiên.

Mục Giá trị Ghi chú
0x08000000 Entry [0]: MSP initial value CPU load vào SP register. Phải là địa chỉ hợp lệ trong RAM.
0x08000004 Entry [1]: Reset Handler address CPU nhảy vào đây. Bit 0 = 1 (Thumb mode flag, không phải địa chỉ thật).
0x08000008+ Exception và interrupt handlers NMI, HardFault, MemManage, BusFault, UsageFault, SysTick, peripheral IRQ.

Debug: firmware crash trước main()

Nếu firmware crash mà breakpoint tại main() chưa kịp hit:

Checklist debug crash trước main()

Liên quan đến bootloader

Khi viết bootloader, hai thứ phải làm trước khi jump sang application:

void JumpToApplication(uint32_t app_address) {
    /* 1. Tắt tất cả interrupt đang pending */
    __disable_irq();

    /* 2. Reset peripheral đang dùng nếu cần */
    /* ... deinit UART, DMA, ... */

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

    /* 4. Load MSP từ entry [0] của application vector table */
    __set_MSP(*(uint32_t*)app_address);

    /* 5. Lấy Reset Handler address từ entry [1] */
    typedef void (*FuncPtr)(void);
    FuncPtr app_reset = (FuncPtr)(*(uint32_t*)(app_address + 4));

    /* 6. Jump */
    app_reset();
}

Chi tiết về bootloader, jump sequence và OTA update sẽ có trong bài blog riêng.

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.