Flash layout cho bootloader trên microcontroller quyết định chuyện sống còn: mất điện đúng lúc ghi firmware có thể khiến thiết bị không khởi động lại được nữa. Đó là quyết định kiến trúc chi phối đường phục hồi, an toàn cập nhật và chi phí bảo trì ngoài hiện trường.
Bản firmware mới không có chỗ chứa, mất điện giữa lúc ghi khiến thiết bị không khởi động được, hoặc dữ liệu hiệu chuẩn bị xóa nhầm cùng application — những sự cố này luôn lộ ra muộn, nhưng nguyên nhân đã nằm sẵn từ lúc chia flash.
Câu hỏi cần trả lời không phải là chia bao nhiêu byte cho mỗi vùng, mà là: nếu lần cập nhật tới hỏng đúng vào thời điểm tệ nhất, thiết bị còn đường nào để tiếp tục hoạt động hoặc nhận lại firmware?
Flash layout tốt không phải layout có nhiều phân vùng nhất. Đó là layout giữ được đúng đường phục hồi mà sản phẩm cần, với mức tốn flash và độ phức tạp có thể chấp nhận.
Mục lục
Flash layout định hình bốn vùng chức năng
Flash layout chia bộ nhớ không mất dữ liệu thành các vùng dành cho bootloader, application, trạng thái cập nhật và dữ liệu riêng của thiết bị. Nếu firmware được cập nhật tại chỗ, vùng nhận bản mới hoặc vùng dự phòng cũng phải được dành sẵn.
Mỗi đường biên đều kéo theo một ràng buộc. Địa chỉ application phải khớp với linker script. Vùng trạng thái phải nằm đúng ranh giới xóa. Bootloader phải biết vùng nào được phép ghi, vùng nào chỉ được đọc và vùng nào tuyệt đối không được đụng tới.
flowchart LR
boot[Bootloader] --- meta[Trạng thái cập nhật]
meta --- app[Application]
app --- data[Cấu hình và hiệu chuẩn]
Bốn vùng trên có vòng đời khác nhau. Bootloader ít thay đổi. Application có thể được cập nhật nhiều lần. Trạng thái cũng thay đổi trong từng đợt cập nhật.
Cấu hình và dữ liệu hiệu chuẩn lại thường phải được giữ đến hết vòng đời thiết bị. Nếu tất cả nằm chung một vùng, một thao tác nhỏ có thể ảnh hưởng tới nhiều dữ liệu hơn cần thiết.
Đọc datasheet trước khi chia flash
Flash không được ghi như RAM. Phần lớn MCU yêu cầu xóa cả page hoặc sector trước khi ghi lại, dù dữ liệu cần sửa chỉ có vài byte. Kích thước vùng xóa vì thế mới là đơn vị cần nhìn đầu tiên, không phải kích thước hiện tại của từng cấu trúc dữ liệu.
Nếu trạng thái cập nhật dùng chung một page với cấu hình, mỗi lần đổi cờ boot có thể buộc firmware đọc toàn bộ page, xóa rồi ghi lại. Mất nguồn đúng lúc đó không chỉ làm hỏng một cờ trạng thái; cả cấu hình nằm cạnh nó cũng có thể mất theo.
Đường biên phân vùng nên đi theo đơn vị xóa của chip. Chia vừa khít theo số byte đang dùng thường chỉ đẹp trên sơ đồ.
- Kích thước page hoặc sector dùng cho thao tác xóa.
- Đơn vị ghi nhỏ nhất và yêu cầu căn địa chỉ.
- Số flash bank và khả năng đọc trong lúc bank khác đang được ghi.
- Số chu kỳ xóa mà từng vùng phải chịu.
- Cơ chế khóa ghi cho bootloader và dữ liệu nhạy cảm.
Với MCU chỉ có một flash bank, CPU có thể không đọc được mã lệnh từ flash trong lúc xóa hoặc ghi. Flash driver khi đó có thể phải chạy từ RAM.
Các ngắt cũng cần được kiểm soát để CPU không nhảy vào đoạn mã đang tạm thời không đọc được. Đây là giới hạn phần cứng, không thể sửa bằng cách đổi tên phân vùng.
Giữ bootloader nhỏ và ổn định
Khi application hỏng, bootloader là đường quay về. Vùng này thường được đặt ở đầu flash, tách khỏi application và khóa ghi trong điều kiện vận hành bình thường. Bootloader càng ít thay đổi thì nguy cơ tự xóa mất đường cứu hộ càng thấp.
Tuy nhiên, dành vừa đủ chỗ cho phiên bản đầu tiên cũng là một cái bẫy. Chỉ cần bổ sung xác thực chữ ký, thêm giao tiếp cập nhật hoặc đổi thư viện mật mã, bootloader có thể vượt phần đã cấp.
Dời application về sau khi sản phẩm đã phát hành sẽ kéo theo thay đổi ở linker script, vector table và cả quy trình cập nhật cũ. Việc giữ tương thích khi đó khó hơn nhiều so với chừa khoảng dự phòng ngay từ đầu.
Chừa thêm flash cho bootloader không phải càng nhiều càng tốt. Khoảng dự phòng cần dựa trên những chức năng có khả năng xuất hiện, thay vì một con số chọn cho yên tâm.
Với MCU rất nhỏ, lựa chọn hợp lý thường là giữ bootloader bất biến và tối giản. Tự cập nhật bootloader chỉ đáng làm khi đã có một tầng khởi động nhỏ hơn, không thay đổi, đủ khả năng xác minh và phục hồi tầng phía trên.
Đó cũng là điểm khởi đầu để so sánh các phương án layout cho phần application — nơi phần lớn quyết định về recovery thực sự được đưa ra.
Single image: ít flash, ít đường lui
Cách đơn giản nhất là chỉ dành một vùng cho application. Firmware mới được ghi trực tiếp vào vùng này; sau khi vùng cũ bị xóa, bootloader nhận và ghi bản mới. Layout gọn, logic dễ hiểu và phù hợp với MCU có dung lượng hạn chế.
flowchart LR
boot[Bootloader] --- meta[Trạng thái]
meta --- app[Application duy nhất]
app --- data[Dữ liệu thiết bị]
Điểm yếu cũng nằm ngay ở sự đơn giản đó. Khi application cũ đã bị xóa, thiết bị không còn firmware chính để quay lại. Nếu mất nguồn hoặc kết nối bị đứt, bootloader phải tự giữ được chế độ cập nhật và kênh nạp lại vẫn phải hoạt động.
Single image không mặc nhiên làm thiết bị bị “brick”. Nó vẫn dùng được nếu bootloader được bảo vệ tốt, có cách ép vào chế độ cập nhật và thiết bị dễ tiếp cận bằng cáp hoặc dụng cụ nạp. Nhưng với thiết bị đặt xa, chi phí tới tận nơi thường lớn hơn phần flash tiết kiệm được.
Single image hợp với sản phẩm dễ nạp lại. Nó không hợp với sản phẩm mà một lần cập nhật lỗi sẽ biến thành một chuyến bảo trì ngoài hiện trường.
Staging: nhận xong rồi mới cài
Staging bổ sung một vùng để chứa trọn firmware mới. Dữ liệu được nhận vào vùng này, kiểm tra kích thước, mã kiểm tra hoặc chữ ký, rồi mới chép sang vùng application. Firmware đang chạy chưa bị đụng tới trong suốt quá trình tải về.
flowchart LR
boot[Bootloader] --- app[Application đang chạy]
app --- stage[Vùng staging]
stage --- data[Dữ liệu thiết bị]
Staging xử lý được lỗi truyền chưa đủ dữ liệu nhưng chưa tự giải quyết được mất nguồn trong lúc cài. Khi bootloader đang chép từ staging sang application, bản cũ vẫn bị ghi đè. Trạng thái cài đặt phải cho phép tiếp tục từ điểm an toàn hoặc xóa và chép lại từ đầu.
flowchart TD
receive[Nhận đủ firmware vào staging] --> verify{Kiểm tra hợp lệ?}
verify -- Không --> discard[Loại bỏ bản mới]
verify -- Có --> install[Chép sang vùng application]
install --> done{Đã chép xong?}
done -- Có --> boot[Khởi động firmware mới]
done -- Không --> resume[Tiếp tục hoặc chép lại]
Cách này phù hợp khi application chỉ chạy được tại một địa chỉ cố định nhưng flash vẫn đủ chứa thêm một bản firmware. Dung lượng cần dùng gần bằng hai bản, còn khả năng quay lại phiên bản cũ sau khi cài vẫn kém hơn A/B. Đổi lại, linker và địa chỉ thực thi đơn giản hơn.
Khi phần cứng cho phép application chạy ở hai địa chỉ khác nhau, có một phương án mạnh hơn để đổi lấy khả năng rollback thật sự.
A/B: giữ nguyên bản đang chạy
Layout A/B dành hai vùng độc lập, mỗi vùng đủ chứa một application hoàn chỉnh. Firmware mới được ghi vào vùng không hoạt động. Bản đang chạy vẫn được giữ nguyên cho đến khi bản mới được kiểm tra và chọn cho lần khởi động tiếp theo.
flowchart LR
boot[Bootloader] --- meta[Trạng thái A/B]
meta --- slotA[Slot A]
slotA --- slotB[Slot B]
slotB --- data[Dữ liệu thiết bị]
Ưu điểm lớn nhất là khả năng chạy thử. Bản mới được đánh dấu chờ thử, bootloader cho phép khởi động một số lần, còn application chỉ xác nhận sau khi các chức năng quan trọng đã hoạt động. Nếu không nhận được xác nhận, bootloader chọn lại bản cũ.
stateDiagram-v2
[*] --> Confirmed
Confirmed --> Pending: Ghi xong bản mới
Pending --> Trial: Khởi động thử
Trial --> Confirmed: Application xác nhận
Trial --> Rollback: Hết số lần thử
Rollback --> Confirmed: Chọn lại bản cũ
A/B không miễn phí. Flash phải chứa được hai application. Trạng thái boot phức tạp hơn. Quan trọng hơn, application phải chạy được ở hai địa chỉ hoặc phần cứng phải có cơ chế ánh xạ để slot được chọn xuất hiện tại cùng một địa chỉ thực thi.
Đừng chọn A/B chỉ vì rollback nghe hấp dẫn. Trước hết phải chứng minh rằng cùng một firmware có thể chạy đúng từ cả hai slot trên MCU đang dùng.
Khi flash trong chip không đủ cho cả hai slot, bài toán staging vẫn còn một lối ra khác: đẩy vùng đệm ra bộ nhớ ngoài.
Bộ nhớ ngoài có thể làm vùng đệm
Khi flash trong chip không đủ, firmware mới có thể được giữ trong NOR flash ngoài hoặc một bộ nhớ không mất dữ liệu khác. Flash trong chip tiếp tục chứa bootloader và application; bộ nhớ ngoài chỉ làm vùng staging.
Giải pháp này giải phóng dung lượng bên trong nhưng thêm một chuỗi phụ thuộc mới. Bootloader phải khởi tạo được bus, đọc được chip nhớ và phát hiện dữ liệu lỗi. Nếu bộ nhớ ngoài mất kết nối, application hiện tại vẫn phải khởi động được; nếu không, vùng staging đã biến thành một điểm lỗi mới.
flowchart LR
subgraph internal[Flash trong MCU]
boot[Bootloader] --- app[Application]
app --- data[Dữ liệu thiết bị]
end
subgraph external[Bộ nhớ ngoài]
stage[Firmware mới]
end
stage -. Cài đặt .-> app
Bộ nhớ ngoài đáng cân nhắc khi bo mạch đã có sẵn chip nhớ hoặc khi đổi sang MCU nhiều flash hơn làm chi phí tăng mạnh. Nếu phải thêm riêng một linh kiện chỉ để chứa firmware, giá linh kiện, diện tích mạch, driver và công kiểm thử đều phải được tính chung.
Dù chọn single image, staging hay A/B, tất cả đều đứng trên một điểm chung dễ bị xem nhẹ: vùng trạng thái ghi lại bản nào đang chạy.
Trạng thái cập nhật cũng có thể hỏng
Bootloader cần biết bản nào đang hoạt động, bản nào chờ thử, bản nào đã được xác nhận và lần cài trước dừng ở đâu. Hai slot firmware sẽ không tạo thành hệ thống an toàn nếu mọi quyết định vẫn phụ thuộc vào một byte trạng thái có thể hỏng khi mất nguồn.
Một cách dễ kiểm soát là dùng hai bản ghi luân phiên. Mỗi bản có số thứ tự và mã kiểm tra riêng. Bản mới được ghi vào vị trí còn lại, đọc lại để xác minh, rồi mới được xem là trạng thái mới nhất. Nếu mất nguồn giữa chừng, bản cũ vẫn còn nguyên.
flowchart LR
old[Bản ghi cũ hợp lệ] --> write[Ghi bản mới]
write --> verify{Đọc lại hợp lệ?}
verify -- Có --> newer[Chọn số thứ tự mới hơn]
verify -- Không --> keep[Giữ bản cũ]
Một bản ghi trạng thái đầy đủ cần mang theo:
- Phiên bản định dạng và dấu nhận biết bản ghi.
- Địa chỉ, kích thước và phiên bản của từng firmware.
- Mã kiểm tra, hàm băm hoặc thông tin xác thực.
- Trạng thái chờ thử, đang thử và đã xác nhận.
- Số lần khởi động thử và số thứ tự bản ghi.
Vùng trạng thái thường được ghi nhiều hơn vùng firmware. Nếu bộ đếm khởi động buộc cả page phải xóa sau mỗi lần reset, tuổi thọ của page đó có thể hết trước phần còn lại của thiết bị. Cần tính cả tần suất ghi, không chỉ tính số byte cần lưu.
Tách dữ liệu theo vòng đời
Cấu hình, số sê-ri, dữ liệu hiệu chuẩn và khóa của thiết bị không có cùng vòng đời với application. Các dữ liệu này không nên nằm trong vùng bị xóa khi cập nhật, cũng không nên được đóng gói lại như một phần mặc định của firmware.
Tách vùng nhớ chỉ giải quyết được nguy cơ xóa nhầm. Nếu cấu trúc cấu hình thay đổi giữa hai phiên bản, application vẫn cần nhận biết phiên bản cũ, chuyển đổi dữ liệu và có cách xử lý khi quá trình chuyển đổi bị gián đoạn.
Firmware, dữ liệu được ghi tại nhà máy và dữ liệu phát sinh khi vận hành là ba loại tài sản khác nhau. Chúng không nên bị buộc phải cập nhật cùng một nhịp.
Khóa riêng hoặc khóa gốc còn cần mức bảo vệ cao hơn cấu hình thông thường. Tùy MCU, dữ liệu này có thể nằm trong vùng flash khóa ghi, OTP, vùng lưu trữ an toàn hoặc phần cứng bảo mật riêng. Chọn vị trí nào phải dựa trên mô hình đe dọa, không chỉ dựa trên chỗ còn trống.
Tất cả những ranh giới vừa nêu chỉ có ý nghĩa nếu công cụ build thực sự tôn trọng chúng.
Linker phải biến sơ đồ thành ràng buộc
Sơ đồ flash chỉ có giá trị khi linker script của bootloader và application dùng đúng các địa chỉ đã chọn. Vector table, vùng mã, vùng trạng thái và dữ liệu cố định phải khớp với cách bootloader kiểm tra và khởi động firmware.
Giới hạn kích thước nên được kiểm tra ngay khi build. Nếu application vượt khỏi slot, bản build phải thất bại thay vì tạo ra một file có thể ghi đè lên trạng thái hoặc dữ liệu thiết bị. Một con số chỉ xuất hiện trong tài liệu rất dễ bị quên khi dự án thay đổi.
Địa chỉ phân vùng chỉ đáng tin khi được kiểm tra ở cả linker, công cụ đóng gói firmware và bootloader.
Với A/B, cần thử bằng đúng file firmware sẽ phát hành và khởi động từ cả hai slot. Không nên mặc định rằng chép cùng một file nhị phân sang địa chỉ khác thì CPU vẫn tìm đúng vector table, địa chỉ tuyệt đối và các vùng dữ liệu liên quan.
Chọn layout từ chi phí phục hồi
Dung lượng flash còn trống chỉ là một phần của quyết định. Những câu hỏi quan trọng hơn là thiết bị được cập nhật bằng cách nào, có dễ tiếp cận không, được phép ngừng bao lâu và một lần cập nhật lỗi sẽ tốn bao nhiêu công để khắc phục.
- Single image: phù hợp với MCU nhỏ, cập nhật qua dây và thiết bị dễ nạp lại.
- Staging: phù hợp khi cần nhận đủ firmware trước khi cài nhưng application chỉ chạy tại một địa chỉ.
- A/B: phù hợp với cập nhật từ xa, cần quay lại bản cũ ngay tại thiết bị và phần cứng hỗ trợ hai slot thực thi.
- Staging trên bộ nhớ ngoài: phù hợp khi flash trong chip thiếu nhưng bộ nhớ ngoài đã có sẵn hoặc đáng để bổ sung.
flowchart TD
start([Chọn flash layout]) --> remote{Cập nhật từ xa?}
remote -- Không --> easy{Thiết bị dễ nạp lại?}
easy -- Có --> single[Single image]
easy -- Không --> staging[Staging]
remote -- Có --> dual{Chạy được ở hai slot?}
dual -- Có --> ab[A/B]
dual -- Không --> recover[Staging có thể tiếp tục sau mất nguồn]
Với sản phẩm MCU cập nhật từ xa, đủ flash và chạy được ở hai slot, A/B thường là phương án dễ vận hành nhất. Nếu địa chỉ thực thi bị cố định, staging kèm cơ chế tiếp tục cài sau mất nguồn sẽ thực tế hơn.
Còn với thiết bị luôn nằm trên bàn kỹ thuật và có thể nạp lại dễ dàng, single image vẫn là lựa chọn hợp lý. Không cần trả thêm dung lượng và độ phức tạp cho một đường phục hồi ít khi được dùng tới.
Quyết định này sẽ đổi khi chi phí bảo trì, dung lượng firmware hoặc yêu cầu an toàn thay đổi. Vì vậy, trước khi khóa layout, nên để lại khoảng trống hợp lý và ghi rõ các ràng buộc vào linker, công cụ đóng gói cùng tài liệu sản phẩm.
Checklist trước khi chốt layout
Trước khi khóa flash layout, đáng để tự trả lời từng câu sau:
- Đường biên phân vùng đã khớp với page hoặc sector xóa chưa?
- Bootloader có được khóa ghi và còn đủ chỗ cho chức năng dự kiến không?
- Mất nguồn ở từng bước cập nhật thì lần khởi động sau sẽ làm gì?
- Trạng thái cập nhật có bản dự phòng và mã kiểm tra không?
- Cấu hình, hiệu chuẩn và khóa thiết bị đã được tách khỏi firmware chưa?
- Linker có chặn application vượt khỏi vùng được cấp không?
- A/B đã được chạy thử từ cả hai địa chỉ thực tế chưa?
- Có cách ép thiết bị vào chế độ nạp lại khi application hỏng không?
Nếu không mô tả được hành vi sau khi mất nguồn ở từng bước, flash layout vẫn chưa hoàn chỉnh.
Kết luận
Flash layout quyết định bootloader còn làm được gì khi cập nhật bị gián đoạn hoặc firmware mới không chạy. Single image tiết kiệm flash nhất. Staging an toàn hơn trong lúc nhận dữ liệu.
A/B thuận lợi nhất cho chạy thử và quay lại bản cũ, nhưng phải trả bằng dung lượng cùng yêu cầu thực thi phức tạp hơn. Lợi ích này chỉ đáng giá khi sản phẩm thực sự cần phục hồi ngay tại thiết bị.
Phương án nên chọn là layout đơn giản nhất vẫn giữ được đường phục hồi mà sản phẩm thực sự cần. Dù chọn cách nào, ranh giới xóa, trạng thái cập nhật, dữ liệu thiết bị và linker script phải được thiết kế như một hệ thống thống nhất ngay từ đầu.








