Bootloader trong STM32: Cơ chế Boot & Update Firmware

Bootloader trong STM32 là chương trình đầu tiên được thực thi mỗi khi vi điều khiển khởi động hoặc reset. Nó quyết định chương trình (Firmware) nào sẽ được chạy và là nền tảng để thực hiện cập nhật phần mềm từ xa (OTA – Over The Air) mà không cần tháo thiết bị hay cắm mạch nạp như ST-Link. Bài viết này trình bày chi tiết cơ chế boot của STM32F1: quá trình cập nhật Firmware, quá trình Reset và Vector Table, cách dời bảng vector (VTOR), kèm một ví dụ thực hành viết chương trình bootloader nhảy sang App Blinkled.

Quá trình cập nhật Firmware (Update Firmware)

Khi đã tung một sản phẩm ra thị trường, việc nâng cấp phần mềm cho thiết bị đang hoạt động ngoài thực tế được thực hiện khái quát qua 4 bước sau:

  • Bước 1: Sau khi cung cấp và lắp đặt thiết bị cho khách hàng, công ty sản xuất thu thập ý kiến đóng góp về thiếu sót, lỗi thường gặp khi triển khai thực tế. Đội ngũ kỹ sư sẽ sửa chữa và tạo một Firmware mới để khắc phục các lỗi đó.
  • Bước 2: Phát triển và build ra tệp tin chương trình mới, đưa lên Server và đặt cho nó một Version mới.
  • Bước 3: Người dùng chủ động điều khiển thiết bị vào quá trình cập nhật từ xa. Sau khi kiểm tra version của Firmware mới, thiết bị sẽ tải tệp tin chứa Firmware về.
  • Bước 4: Khi tải xong, thiết bị tự động kiểm tra và nạp chương trình vào bộ nhớ, sau đó khởi chạy Firmware mới. Kết thúc quá trình cập nhật.

Việc cập nhật này không thể yêu cầu người dùng cắm các thiết bị nạp Firmware chuyên dụng như ST-Link. Thay vào đó, nhà sản xuất ưu tiên dùng thêm một kênh để nhận file cập nhật từ Web, Cloud hoặc các kết nối khác như Wi-Fi, Bluetooth. Sau đó Firmware mới được ghi vào Flash để thiết bị hoạt động. Dữ liệu của Firmware mới sẽ được lưu vào một vùng nhớ riêng, ví dụ như hình:

Hình 1. Phân vùng bộ nhớ Flash cho bootloader và các bản Firmware – bắt đầu tại địa chỉ 0x08000000
Vùng nhớChức năng
App IndicationVùng Flash chứa các cờ chỉ thị (trạng thái cập nhật).
App UpdateApp cập nhật nhận được từ thiết bị/kết nối khác.
App CurrentApp đang chạy hiện tại trên STM32.
BootloaderChương trình đầu tiên được chạy khi khởi động.

Sau khi reset, MCU sẽ chọn một trong các chương trình được lưu ở các vùng nhớ và thực thi. Tuy nhiên trước đó, Bootloader luôn được nạp sẵn và chạy trước. Đây là chương trình có chức năng quản lý và chỉ định MCU sẽ thực thi Firmware nào cùng các thiết lập hệ thống ban đầu. Ví dụ: Bootloader kiểm tra vùng App Indication để xác nhận có file Firmware trong vùng App Update hay không, từ đó quyết định thực thi Firmware nào và tiến hành cập nhật lại App Current cho chip.

Quá trình Reset của vi điều khiển

Địa chỉ bắt đầu của bộ nhớ (với STM32 là bộ nhớ Flash) là 0x08000000. Phần đầu tiên trong bộ nhớ của một chương trình là Vector Table – chứa các vector ngắt, điển hình nhất là Reset Vector.

Hình 2. Khi reset, bộ xử lý nạp giá trị MSP (_estack) và địa chỉ Reset_Handler từ Vector Table đầu Flash

Quá trình Reset gồm các bước sau:

  • CPU tìm kiếm bảng Vector Table của chương trình trên bộ nhớ.
  • Bộ xử lý tìm nạp giá trị tại ô nhớ đầu tiên của Vector Table vào thanh ghi MSP (Main Stack Pointer). Mặc định với STM32F1 là 0x08000000.
  • Tiếp theo, bộ xử lý lấy địa chỉ của Reset Handler đặt trong ô nhớ thứ 2 của Vector Table, load vào PC để thực hiện hàm Reset Handler.
  • Sau khi nhảy đến Reset Handler, bộ xử lý bắt đầu thực hiện các lệnh khởi tạo ban đầu của hệ thống. Reset Handler thường được viết bằng Assembly (hoặc C).
  • Bước cuối cùng của Reset Handler là gọi đến hàm main() trong file main.c, sau đó thực hiện các tác vụ trong main().

Trong thực tế, một thiết bị có thể lưu nhiều chương trình khác nhau ở các vùng khác nhau của bộ nhớ. Quá trình Reset lúc này sẽ chọn một trong các chương trình để thực hiện, theo đúng 5 bước trên.

Chương trình Bootloader

Bootloader là một chương trình và là chương trình đầu tiên chạy khi chip hoạt động. Nhiệm vụ của Bootloader là kiểm tra các điều kiện để lựa chọn thực thi một trong các chương trình mỗi khi CPU reset:

  • Firmware Update – bản Firmware mới nhất vừa nhận được.
  • Firmware gốc do hãng nạp vào trước khi xuất xưởng.
  • Current Firmware – bản Firmware hiện tại đang chạy.

Mục tiêu chính của Bootloader là nâng cấp hoặc sửa đổi phần mềm hệ thống mà không cần công cụ nạp chuyên dụng – ví dụ cài đặt phần mềm từ xa, nâng cấp mà không cần kết nối mạch nạp. Bootloader có thể có nhiều chức năng nhưng chủ yếu dùng để quản lý ứng dụng, và có thể dùng nhiều giao thức khác nhau như UART, CAN, I2C, I2S, Ethernet hoặc USB để thiết lập giao tiếp và bắt đầu nâng cấp firmware.

Vị trí của chương trình Bootloader thường bắt đầu tại địa chỉ đầu tiên của bộ nhớ Flash – đây là địa chỉ mặc định CPU sẽ thực thi sau khi Reset. Với STM32 thì vị trí bắt đầu của Flash là 0x08000000.

Hoạt động của CPU

Nhìn chung, hoạt động của CPU được mô tả như sau:

  • Bắt đầu, hoặc mỗi khi CPU reset, PC nhảy đến địa chỉ bắt đầu (với boot 0-0 là thực thi vùng Flash, PC → 0x08000000) – đây là nơi lưu chương trình bootloader, nên nó được chạy đầu tiên.
  • Lấy địa chỉ của App Current được lưu trên Flash.
  • Hàm boot nhảy đến vị trí lưu App Current.
  • Thực thi Firmware tại vị trí nhảy đến.
  • Lặp lại mỗi khi khởi động hoặc có Reset.

Hoạt động của hàm Boot

  • Lấy địa chỉ để chương trình nhảy đến.
  • Xóa hết các ngắt, các trình xử lý lỗi.
  • Set Main Stack Pointer (MSP) đến địa chỉ bắt đầu của Firmware mới.
  • Set PC nhảy đến hàm Reset của Firmware mới.
  • Gọi ngắt Reset để thực thi Firmware mới.

Quá trình boot trên STM32 (Boot mode)

STM32F1 cho phép người dùng chọn thực thi chương trình được lưu từ các vùng nhớ khác nhau. Việc này thực hiện bằng cách thay đổi kết nối 2 jumper BOOT0/BOOT1 trên kit theo bảng dưới đây:

BOOT1BOOT0Boot modeVùng nhớ thực thi
x0Main Flash memoryFlash chính được chọn làm vùng boot
01System memoryBootloader hệ thống (ROM) của ST
11Embedded SRAMSRAM nội được chọn làm vùng boot
Hình 3. Hai jumper BOOT0/BOOT1 trên kit STM32F103 (Blue Pill) dùng để chọn vùng nhớ boot

Thông thường, 2 jumper được đặt ở 0-0, tức thực thi chương trình lưu ở vùng Flash. Sau khi khởi động, vi điều khiển tìm đến vùng Flash và boot chương trình được lưu tại đó.

Ví dụ thực hành: Bootloader nhảy sang App Blinkled

Ở phần này, ta minh họa quá trình boot bằng cách viết một App Blinkled và lưu nó ở một vùng nhớ khác 0x08000000 (cụ thể là 0x08008000) trên Flash. Sau đó viết thêm một chương trình boot nạp tại 0x08000000 để điều khiển nhảy đến vùng nhớ chứa App Blinkled và thực thi.

Bước 1: Nạp App vào địa chỉ 0x08008000

App Blinkled được lưu ở địa chỉ 0x08008000 trong Flash. Nghĩa là ta chia cho chương trình bootloader vùng nhớ từ 0x08000000 đến 0x08008000 (kích thước 32 KB), còn vùng Flash từ 0x08008000 trở đi dành để lưu App Blinkled. Ta phải thay đổi địa chỉ trong cấu hình để báo cho ST-Link nạp xuống đúng vùng nhớ:

Hình 4. Đặt IROM1 Start = 0x08008000 trong Options for Target của Keil để nạp App vào đúng vùng nhớ

Bước 2: Dời bảng vector (Vector Table Relocation)

Một chương trình Firmware bao gồm cả phần code (chương trình thực thi), các vùng nhớ đi kèm và một bảng vector tương ứng. Vì vậy ta phải dời bảng vector đúng với địa chỉ Firmware được nạp:

Hình 5. Mỗi Firmware có Vector Table riêng (MSP, Reset_Handler, .isr_vector…) đặt tại đầu vùng nhớ của nó

Ở đây ta dời bảng vector lên ứng với MSP tại 0x08008000. Sửa đổi giá trị Offset của bảng này trong file system_stm32f10x.c:

Hình 6. Đặt #define VECT_TAB_OFFSET 0x00008000 (phải là bội số của 0x200)

Khi đó, bảng vector sẽ được dời bằng phép OR – tức OR giữa 0x08000000 và 0x8000, kết quả là 0x08008000:

Hình 7. SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET – relocate Vector Table trong Flash

Tiến hành Build và nạp code như bình thường. Trước khi nạp, nên xóa Flash để tránh ghi đè dữ liệu bằng cách chọn Flash → Erase. Như vậy ta đã nạp thành công một App vào Flash cho STM32.

Bước 3: Viết chương trình Bootloader

Vì MCU mặc định thực hiện chương trình bắt đầu tại 0x08000000 trong Flash – đây là vùng ta lưu chương trình Bootloader – nên khi khởi động hoặc Reset, bootloader tại đây sẽ chạy đầu tiên. Chương trình bootloader được viết theo sơ đồ đã nêu ở phần trước:

int main(void)
{
    /* Reset bộ RCC về trạng thái mặc định */
    RCC_DeInit();

    /* Vô hiệu hóa các bit kiểm tra lỗi trong thanh ghi SCB->SHCSR */
    SCB->SHCSR &= ~(SCB_SHCSR_USGFAULTENA_Msk
                  | SCB_SHCSR_BUSFAULTENA_Msk
                  | SCB_SHCSR_MEMFAULTENA_Msk);

    /* Set MSP tới địa chỉ Firmware Blinkled đã nạp trước đó */
    __set_MSP(*(__IO uint32_t*)(ADDR_STR_BLINK));

    /* Lấy địa chỉ Reset_Handler (ô nhớ thứ 2 của Vector Table) */
    uint32_t JumpAddress = *(__IO uint32_t*)(ADDR_STR_BLINK + 4);
    void (*reset_handler)(void) = (void *)JumpAddress;

    /* Nhảy đến Reset_Handler của Firmware mới */
    reset_handler();

    while (1)
    {
    }
}

Giải thích luồng chạy của chương trình bootloader:

  • RCC_DeInit() reset bộ RCC về trạng thái reset mặc định.
  • Vô hiệu hóa các bit kiểm tra lỗi trong thanh ghi SCB->SHCSR.
  • Gọi __set_MSP() để điều khiển Main Stack Pointer đến địa chỉ Firmware Blinkled đã nạp trước đó.
  • Đặt PC là ô nhớ tiếp theo sau MSP, tức PC nhảy đến hàm Reset_Handler() của Firmware Blinkled.
  • Cấu hình con trỏ hàm reset_handler() để sau khi reset, PC nhảy đến vị trí đã chọn.
  • Gọi hàm reset để di chuyển PC, bắt đầu thực thi App Blinkled.

Bootloader nâng cao: cập nhật Firmware qua UART

Với các ứng dụng phức tạp hơn, chương trình boot có thể thực hiện nhiều chức năng khác. Ví dụ khi cập nhật chương trình từ dữ liệu nhận về bằng UART, chương trình có thể đảm nhiệm việc ghi dữ liệu (Firmware mới) vào Flash, sau đó mới gọi hàm boot để thực thi chương trình mới cập nhật này.

Hình 8. Cập nhật firmware qua UART: USART_Handler() ghi dữ liệu vào Flash rồi gọi hàm Bootloader()

Lưu ý khi làm việc với Bootloader STM32

  • Phải dời Vector Table (VTOR) của App sang đúng địa chỉ được nạp, nếu không các ngắt sẽ nhảy sai địa chỉ handler.
  • Offset của bảng vector phải là bội số của 0x200 (yêu cầu căn chỉnh của Cortex-M).
  • Kích thước vùng dành cho bootloader (ví dụ 32 KB) phải đủ chứa chương trình boot và không được chồng lấn vùng App.
  • Trước khi nhảy sang App, nên disable ngắt và các fault handler, reset ngoại vi (RCC_DeInit()) để App khởi động trong trạng thái sạch.
  • Nên Erase Flash vùng App trước khi nạp để tránh ghi đè dữ liệu cũ.

Kết luận

Bootloader là chương trình đầu tiên chạy khi STM32 khởi động, đóng vai trò quản lý và lựa chọn Firmware để thực thi, đồng thời là nền tảng để cập nhật phần mềm từ xa mà không cần mạch nạp chuyên dụng. Qua bài viết, bạn đã nắm được cơ chế boot của STM32F1: quá trình cập nhật Firmware theo 4 bước, quá trình Reset và vai trò của Vector Table (MSP, Reset Handler), cách chọn boot mode bằng jumper BOOT0/BOOT1, và cách dời bảng vector bằng VECT_TAB_OFFSET / SCB->VTOR.

Điểm mấu chốt của một chương trình bootloader là: set MSP → lấy địa chỉ Reset_Handler → nhảy sang App. Nắm vững cơ chế này, bạn có thể xây dựng các hệ thống automotive embedded hỗ trợ cập nhật OTA qua UART, CAN hay USB một cách an toàn và linh hoạt.

Leave a Reply