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.
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 nBOOT0 và nBOOT_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
Validate
Kiểm tra MSP value tại app_base có trỏ vào RAM hợp lệ không.
Disable IRQ
Disable tất cả interrupt, clear pending. SysTick CTRL = 0.
Set VTOR
SCB->VTOR = app_base. CPU sẽ dùng vector table của application.
Set MSP
__set_MSP(msp_value). Stack pointer về đúng stack của application.
Jump
Gọi Reset_Handler của application. Application tự init từ đầu.
Checklist khi debug bootloader không jump được
Bài liên quan
- Startup Code STM32: Reset Handler và Vector Table
- Linker Script STM32: MEMORY, sections và LMA/VMA
- STM32G0 Dual Bank Flash
Tài liệu tham khảo
- ARM Cortex-M0+ TRM - Vector Table, VTOR
- STM32G0B1 Reference Manual RM0444 - Option Bytes, BOOT0
- AN2606 - STM32 microcontroller system memory boot mode
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.
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().
STM32 Bootloader: Execute from RAM, UART Update và Jump to Application
Xây dựng bootloader thực tế trên STM32G0: execute from RAM để erase flash không freeze, nhận firmware qua UART, jump đúng cách vào application với VTOR và MSP.
Flash, EEPROM và RAM khác nhau như thế nào?
Giải thích nguyên lý hoạt động của Flash, EEPROM và RAM.
Biến note thành bài viết hoàn chỉnh
Notes là nơi ghi nhanh khái niệm.