STM32G0 Dual Bank Flash: Read-While-Write và EEPROM Emulation không freeze

STM32G0B1 dual bank mode: DBANK option bit, read-while-write, tại sao erase Bank 2 không block code đang chạy ở Bank 1, và gotcha về prefetch khi jump giữa bank.

5 phút đọc
STM32 / Firmware cover

STM32G0B1 có 512KB Flash chia thành hai bank 256KB độc lập. Tính năng này không chỉ đơn thuần là “nhiều Flash hơn” - nó thay đổi căn bản cách firmware có thể thao tác với Flash mà không bị freeze.

Vấn đề của single-bank - nhắc lại ngắn

Trên chip single-bank, Flash controller chỉ có một bus. Khi erase/program đang chạy, bus bị chiếm - CPU không fetch được instruction → program đứng.

Erase một page 2KB mất ~20-40ms. Trong RTOS, đây đủ để bỏ lỡ nhiều SysTick, làm lỗi timeout communication, hoặc trigger watchdog.

Dual bank: hai bus độc lập

STM32G0B1 có hai Flash interface riêng biệt:

CPU
 ├─→ AHB Bus → Flash Interface 1 → Bank 1 (0x08000000 - 0x0803FFFF)
 └─→ AHB Bus → Flash Interface 2 → Bank 2 (0x08040000 - 0x0807FFFF)

Khi Flash Interface 2 đang erase Bank 2, Flash Interface 1 vẫn phục vụ đọc Bank 1 bình thường. CPU đang chạy code từ Bank 1 không bị gián đoạn.

Timeline dual-bank erase Bank 2:
Bank 1: |--run--|--run--|--run--|--run--|  ← không bị ảnh hưởng
Bank 2:          |---ERASE 20-40ms---|

Interrupt, RTOS tick, UART - tất cả vẫn chạy bình thường trong khi Bank 2 đang bị erase.

Bật dual bank: DBANK option bit

Dual bank mode không tự động bật - phải set DBANK bit trong Flash Option Bytes:

/* Đọc option bytes hiện tại */
FLASH_OBProgramInitTypeDef ob_config;
HAL_FLASHEx_OBGetConfig(&ob_config);

/* Set DBANK */
ob_config.OptionType = OPTIONBYTE_USER;
ob_config.USERType   = OB_USER_DBANK;
ob_config.USERConfig = OB_DBANK_128_BITS;  /* Enable dual bank */

HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&ob_config);
HAL_FLASH_OB_Launch();  /* Reset và apply option bytes */

HAL_FLASH_OB_Launch() trigger system reset - MCU sẽ khởi động lại với dual bank mode.

Lưu ý: Khi bật DBANK, mỗi double-word (64-bit) word của Flash sẽ được đọc theo 2 lần 32-bit thay vì 1 lần 64-bit. Wait state có thể cần điều chỉnh.

Single bank vs Dual bank - địa chỉ thay đổi

ModeBank 1Bank 2
Single bank0x08000000 - 0x0807FFFF (512K)-
Dual bank0x08000000 - 0x0803FFFF (256K)0x08040000 - 0x0807FFFF (256K)

Linker script phải reflect đúng layout khi đổi mode.

Erase/Program Bank 2 trong khi chạy từ Bank 1

Khi code đang chạy từ Bank 1, thao tác Flash trên Bank 2 không cần execute-from-RAM trick:

/* Code này đang chạy từ Bank 1 */
void SaveConfigToBank2(uint32_t page_in_bank2) {
    /* page_in_bank2 = page number tính từ đầu Bank 2 */
    FLASH_EraseInitTypeDef cfg = {
        .TypeErase = FLASH_TYPEERASE_PAGES,
        .Banks     = FLASH_BANK_2,      /* ← chỉ rõ Bank 2 */
        .Page      = page_in_bank2,
        .NbPages   = 1,
    };
    uint32_t err;
    HAL_FLASH_Unlock();
    HAL_FLASHEx_Erase(&cfg, &err);  /* Không block Bank 1! */

    /* Ghi data */
    HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,
                      0x08040000 + page_in_bank2 * 2048,
                      my_data);
    HAL_FLASH_Lock();
}

RTOS, interrupt, UART - tiếp tục hoạt động trong khi hàm này đang erase Bank 2.

Gotcha: Prefetch phải disable khi jump giữa bank

Đây là điều ít tài liệu đề cập nhưng quan trọng trên STM32G0.

Flash prefetch buffer đọc trước instruction - nhưng nó không biết bạn sắp nhảy sang bank khác. Nếu nhảy từ Bank 1 sang Bank 2 (hoặc ngược lại) mà prefetch đang active, có thể đọc instruction sai → crash không xác định.

void JumpToBank2(uint32_t address_in_bank2) {
    /* PHẢI disable prefetch trước khi jump giữa bank trên G0 */
    __HAL_FLASH_PREFETCH_BUFFER_DISABLE();

    typedef void (*FuncPtr)(void);
    FuncPtr fn = (FuncPtr)(*(uint32_t*)(address_in_bank2 + 4));

    __set_MSP(*(uint32_t*)address_in_bank2);
    SCB->VTOR = address_in_bank2;
    fn();
}

Lưu ý: Đây là G0-specific. Trên STM32G4, ST xác nhận không có vấn đề tương tự khi jump giữa bank.

Layout điển hình cho EEPROM emulation với dual bank

Bank 1: 0x08000000 ─────────────────── 0x0803FFFF (256K)
  ├─ Application code:   0x08000000 - 0x0803BFFF (240K)
  └─ [có thể dùng thêm nếu cần]

Bank 2: 0x08040000 ─────────────────────────────── 0x0807FFFF (256K)
  ├─ EEPROM page A:  0x08040000 - 0x08040800 (2K, page 0)
  └─ EEPROM page B:  0x08040800 - 0x08041000 (2K, page 1)
  [phần còn lại tùy dự án]

EEPROM emulation rotate giữa page A và B trong Bank 2. Code chạy từ Bank 1 erase/ghi Bank 2 mà không bị freeze. Đây là lý do dual bank G0 rất phù hợp cho POS system hoặc thiết bị cần lưu config thường xuyên trong khi vẫn phải xử lý giao tiếp real-time.

Tóm tắt

Mục Giá trị Ghi chú
Single bank Erase/program freeze toàn bộ program ~20-40ms/page Interrupt không chạy được. RTOS tick bị bỏ lỡ. Phải dùng execute-from-RAM.
Dual bank Erase Bank 2 không block Bank 1 Code từ Bank 1 chạy bình thường. Interrupt, RTOS, UART không bị ảnh hưởng.
DBANK bit Option byte - phải set thủ công Cần reset để apply. Thay đổi memory map (512K → 2×256K).
Prefetch gotcha Disable trước khi jump giữa bank G0-specific. Không disable có thể crash khi jump.

Checklist dual bank setup

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.