Từ lúc microcontroller được reset đến khi application bắt đầu chạy được thực hiện qua nhiều bước liên tiếp. CPU phải tìm được lệnh đầu tiên, khởi tạo môi trường chạy, bootloader chọn chế độ khởi động, firmware được kiểm tra rồi quyền điều khiển mới được chuyển giao cho application.
Chuỗi này diễn ra rất nhanh nên thường bị nhìn thành một bước duy nhất. Tuy nhiên, mỗi giai đoạn có trạng thái đầu vào và nhiệm vụ riêng. Nắm được đúng thứ tự sẽ giúp bootloader được thiết kế gọn hơn, đồng thời tránh chồng chéo trách nhiệm với startup code và application.
Mục lục
Reset mở đầu quá trình khởi động
Reset có thể xuất hiện khi vừa cấp nguồn (power-on/cold boot), khi chân reset được kích hoạt, khi watchdog hết thời gian hoặc khi phần mềm chủ động yêu cầu (soft reset/warm boot). Dù nguyên nhân nào xảy ra (thường gọi là reset reason hoặc reset cause), CPU cũng quay về trạng thái khởi đầu do nhà sản xuất quy định.
Một phần ngoại vi được đưa về giá trị mặc định, CPU dừng chương trình đang chạy và quá trình lấy lệnh được bắt đầu lại. Tuy vậy, không phải mọi trạng thái đều luôn bị xóa. Backup register hoặc vùng RAM giữ nguồn điện vẫn có thể còn dữ liệu, tùy thiết kế của chip.
Nguyên nhân reset là dữ liệu đầu vào của bootloader. Power-on reset; watchdog reset hoặc yêu cầu cập nhật (programming request/software update request) từ application có thể dẫn tới những nhánh xử lý khác nhau.
CPU tìm lệnh đầu tiên
Sau reset, CPU không tự biết hàm nào trong chương trình cần được gọi. Điểm bắt đầu được kiến trúc phần cứng quy định. Trên Cortex-M, hai giá trị đầu trong vector table thường là stack pointer ban đầu và địa chỉ của reset handler.
Nếu bootloader nằm ở đầu flash (Ví dụ địa chỉ 0x00000000 trên MCU 32-bit), vector table tại địa chỉ khởi động sẽ thuộc về bootloader. CPU nạp stack pointer, lấy địa chỉ reset handler rồi bắt đầu chạy phần mã khởi động của bootloader.
flowchart TD
reset([Reset]) --> stack[Đọc stack pointer ban đầu]
stack --> handler[Đọc địa chỉ reset handler]
handler --> startup[Chạy startup code của bootloader]
Cách lấy lệnh đầu tiên thay đổi theo từng dòng MCU. Một số chip còn có ROM bootloader của nhà sản xuất hoặc cơ chế ánh xạ bộ nhớ theo chân boot. Vì vậy, datasheet và tài liệu về chế độ khởi động vẫn là nguồn cần kiểm tra trước tiên.
Startup code chuẩn bị môi trường
Reset handler chưa gọi thẳng hàm chính của bootloader. Startup code thường phải sao chép vùng dữ liệu khởi tạo từ flash sang RAM, xóa vùng BSS và thực hiện những thiết lập tối thiểu mà chương trình C cần sử dụng.
Một số thư viện còn gọi hàm khởi tạo hệ thống trước khi vào main. Clock hoặc FPU có thể được thiết lập tại đây, nhưng chỉ nên cấu hình những thành phần thật sự cần thiết. Phần cứng bị thay đổi quá nhiều ở giai đoạn này sẽ làm việc bàn giao cho application khó kiểm soát hơn.
Startup code chuẩn bị môi trường thực thi. Bootloader quyết định firmware nào được chạy. Hai phần liên tiếp nhau nhưng không cùng một nhiệm vụ.
Bootloader xác định chế độ khởi động
Khi môi trường tối thiểu đã sẵn sàng, phần logic chính của bootloader bắt đầu chạy. Nguyên nhân reset, chân yêu cầu cập nhật, cờ lưu trong RAM hoặc metadata trong flash được đọc để xác định thiết bị nên khởi động bình thường hay ở lại chế độ cập nhật.
Thứ tự ưu tiên cần được quy định rõ. Chẳng hạn, firmware không hợp lệ phải được xử lý trước yêu cầu boot bình thường. Một cờ cập nhật cũng cần cơ chế xóa hoặc xác nhận, nếu không thiết bị có thể quay lại bootloader sau mọi lần reset.
flowchart TD
state([Đọc trạng thái khởi động]) --> update{Có yêu cầu cập nhật?}
update -- Có --> updateMode[Chế độ cập nhật]
update -- Không --> valid{Application hợp lệ?}
valid -- Không --> recovery[Chế độ phục hồi]
valid -- Có --> boot[Boot application]
Với bootloader tối giản, quyết định có thể chỉ dựa trên một chân GPIO và kết quả kiểm tra application. Với sản phẩm hỗ trợ cập nhật từ xa (Firmware-Over-The-Air / FOTA), metadata, trạng thái image mới và số lần boot thử thường phải được tính thêm.
Application được kiểm tra trước khi chạy
Trước khi chuyển quyền điều khiển, bootloader cần xác định vùng application có chứa firmware có thể chạy hay không. Mức kiểm tra phụ thuộc vào yêu cầu của sản phẩm và định dạng image đã chọn.
- Stack pointer ban đầu có nằm trong vùng RAM hợp lệ không?
- Reset handler có trỏ vào vùng flash của application không?
- Kích thước và địa chỉ image có nằm đúng phân vùng không?
- Checksum hoặc CRC có khớp với dữ liệu đã lưu không?
- Chữ ký số có hợp lệ nếu secure boot được sử dụng không?
Kiểm tra stack pointer và reset handler chỉ loại được những image sai rõ ràng. CRC giúp phát hiện dữ liệu bị hỏng nhưng không chứng minh được nguồn gốc firmware. Khi firmware cần được xác thực, chữ ký số và khóa tin cậy phải được bổ sung.
Mỗi phép kiểm tra trả lời một câu hỏi khác nhau: image có tồn tại, có nguyên vẹn và có được phép chạy hay không.
Nếu application không hợp lệ, quyền điều khiển không nên được chuyển sang một địa chỉ chưa chắc chắn. Bootloader có thể chờ firmware mới, dùng image dự phòng hoặc đưa thiết bị vào chế độ phục hồi (recovery mode), tùy flash layout và yêu cầu vận hành.
Trạng thái được chuẩn bị để bàn giao
Khi application đã được chấp nhận, bootloader chuẩn bị chuyển giao context cho application. Những interrupt đang bật, cờ pending, SysTick, DMA và ngoại vi đã sử dụng cần được xử lý theo thỏa thuận giữa hai phía.
Không nhất thiết mọi ngoại vi đều phải được reset về mặc định. Điều quan trọng là application biết chính xác mình sẽ nhận trạng thái nào. Nếu application luôn giả định một phiên khởi động sạch, bootloader nên dọn dẹp cấu hình phần cứng đã thay đổi trước khi bàn giao.
Chuyển giao từ bootloader sang application là một giao diện phần mềm. Trạng thái CPU và ngoại vi chính là dữ liệu của giao diện đó.
Quyền điều khiển được chuyển sang application
Trên Cortex-M, bootloader thường đọc stack pointer và reset handler từ vector table của application. Vector table base được chuyển sang vùng application nếu kiến trúc hỗ trợ, stack pointer được nạp lại rồi CPU nhảy tới reset handler mới.
flowchart TD
bootloader([Bootloader]) --> irq[Dừng nguồn interrupt đã sử dụng]
irq --> cleanup[Dọn trạng thái cần thiết]
cleanup --> vector[Chuyển vector table]
vector --> stack[Nạp stack pointer của application]
stack --> handler[Nhảy tới reset handler của application]
Lệnh nhảy này chưa đưa chương trình thẳng vào main. Reset handler của application vẫn chạy startup code riêng, chuẩn bị vùng data và BSS của application rồi mới gọi các hàm khởi tạo và hàm main.
Đây là điểm dễ gây nhầm: startup code được chạy hai lần cho hai image độc lập. Bootloader có môi trường C của bootloader; application có môi trường C của application. Mỗi linker script mô tả vùng nhớ tương ứng và không được ghi đè lên vùng của bên còn lại.
Application tiếp quản hệ thống
Khi main() của application bắt đầu chạy, vòng đời khởi động cơ bản đã hoàn tất. Application tiếp tục khởi tạo driver, scheduler, giao tiếp và logic sản phẩm. Từ thời điểm này, bootloader không còn tham gia cho tới lần reset hoặc cập nhật tiếp theo.
Nếu firmware mới cần được boot thử, application còn một nhiệm vụ liên quan đến bootloader: xác nhận phiên bản hiện tại hoạt động ổn định. Cờ xác nhận được ghi vào metadata để lần reset sau không quay về image cũ.
flowchart TD
reset([Reset]) --> vector[CPU lấy stack pointer và reset handler]
vector --> bootStartup[Startup code của bootloader]
bootStartup --> decision[Bootloader chọn chế độ khởi động]
decision --> validation[Application được kiểm tra]
validation --> handoff[Trạng thái được chuẩn bị để bàn giao]
handoff --> appStartup[Startup code của application]
appStartup --> main([main của application])
Vòng đời thay đổi khi có update
Không có cập nhật firmware, chuỗi khởi động có thể được giữ rất ngắn: kiểm tra application rồi chạy. Khi update được bổ sung, vòng đời có thêm các trạng thái nhận image, ghi flash, xác thực, boot thử, xác nhận hoặc rollback.
Những trạng thái này không nên được nhồi hết vào đường boot bình thường nếu sản phẩm chưa cần. Với MCU nhỏ, lựa chọn tốt nhất vẫn là chuỗi khởi động đơn giản nhất đáp ứng đủ yêu cầu cập nhật và phục hồi thực tế.
Bootloader càng làm nhiều việc, thời gian khởi động và phần mã phải kiểm thử càng tăng. Tính năng chỉ nên được thêm khi có một yêu cầu vận hành rõ ràng.
Kết luận
Vòng đời khởi động trên microcontroller là một chuỗi có thứ tự: reset, lấy vector khởi động, chuẩn bị môi trường, chọn chế độ boot, kiểm tra firmware, bàn giao trạng thái rồi chạy application.
Bootloader nằm ở giữa chuỗi đó. Nó không thay startup code và cũng không thực hiện logic sản phẩm. Vai trò chính là quyết định application nào được phép chạy, chuẩn bị điều kiện bàn giao và giữ lại một đường cập nhật hoặc phục hồi khi cần.








