Flash Operations STM32: Erase, Write, Polling và tại sao program đứng

Cách thao tác Flash STM32 qua HAL: unlock, erase, write, polling vs interrupt, và nguyên nhân program freeze khi xóa Flash.

6 phút đọc
STM32 / Firmware cover

Thao tác Flash trên STM32 không giống ghi RAM. Có một số ràng buộc phần cứng mà nếu không biết, firmware sẽ hoạt động sai theo những cách rất khó debug.

Trình tự bắt buộc: Unlock → Erase → Write → Lock

Flash của STM32 có cơ chế bảo vệ ghi mặc định. Muốn thay đổi nội dung phải unlock trước, làm xong thì lock lại:

/* Unlock Flash */
HAL_FLASH_Unlock();

/* --- Thao tác erase/write tại đây --- */

/* Lock lại khi xong */
HAL_FLASH_Lock();

Nếu quên HAL_FLASH_Unlock(), thao tác ghi sẽ bị hardware block và trả về lỗi. Nếu quên HAL_FLASH_Lock(), Flash vẫn ở trạng thái unlock - an toàn hơn nếu lock lại sau mỗi lần thao tác.

Erase - xóa cả page

Trên STM32G0, đơn vị erase là page (2 KB). Không thể erase nhỏ hơn.

FLASH_EraseInitTypeDef erase_config = {
    .TypeErase   = FLASH_TYPEERASE_PAGES,
    .Banks       = FLASH_BANK_1,
    .Page        = 60,        /* page number, bắt đầu từ 0 */
    .NbPages     = 2,         /* số page muốn erase */
};

uint32_t page_error = 0;
HAL_StatusTypeDef status;

HAL_FLASH_Unlock();
status = HAL_FLASHEx_Erase(&erase_config, &page_error);
HAL_FLASH_Lock();

if (status != HAL_OK) {
    /* page_error chứa page bị lỗi đầu tiên */
}

Sau erase: toàn bộ page chứa giá trị 0xFF (tất cả bit = 1).

Tính địa chỉ page từ address

/* STM32G0: Flash bắt đầu tại 0x08000000, page size 2048 bytes */
#define FLASH_PAGE_SIZE  2048UL
#define FLASH_BASE_ADDR  0x08000000UL

uint32_t PageNumberFromAddress(uint32_t address) {
    return (address - FLASH_BASE_ADDR) / FLASH_PAGE_SIZE;
}

Write (Program) - ghi theo double word

STM32G0 ghi Flash theo 64-bit (double word) - không phải byte, không phải word. Địa chỉ đích phải align 8 byte.

uint64_t data = 0x0123456789ABCDEFULL;
uint32_t address = 0x0803C000;  /* Phải align 8 byte */

HAL_FLASH_Unlock();
HAL_StatusTypeDef status = HAL_FLASH_Program(
    FLASH_TYPEPROGRAM_DOUBLEWORD,
    address,
    data
);
HAL_FLASH_Lock();

Nếu cần ghi mảng byte, phải pack thành uint64_t trước:

void FlashWriteBytes(uint32_t address, const uint8_t *data, size_t len) {
    /* Đảm bảo address align 8, len là bội số 8 */
    HAL_FLASH_Unlock();
    for (size_t i = 0; i < len; i += 8) {
        uint64_t dword;
        memcpy(&dword, data + i, 8);
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD,
                          address + i, dword);
    }
    HAL_FLASH_Lock();
}

Quan trọng: chỉ có thể ghi 0 vào bit đang là 1. Không thể ghi 1 vào bit đang là 0. Phải erase trước khi ghi lại.

Polling vs Interrupt - cái nào dùng được?

Hai mode thao tác Flash HAL cung cấp:

Mục Giá trị Ghi chú
Polling (mặc định) HAL_FLASHEx_Erase(), HAL_FLASH_Program() Gọi hàm, CPU chờ tới khi xong (blocking). Đơn giản, dễ dùng.
Interrupt mode HAL_FLASHEx_Erase_IT(), HAL_FLASH_Program_IT() Trả về ngay, callback khi xong. Có vẻ non-blocking nhưng không giải quyết được freeze (xem bên dưới).

Tại sao interrupt mode KHÔNG giải quyết được freeze trên single-bank?

Đây là điều nhiều người hiểu nhầm.

Trên chip single-bank (như nhiều dòng STM32F1, F4, và một số G0 đơn giản): Flash controller chỉ có một bus kết nối cả đọc và ghi. Khi đang erase hoặc program Flash, bus này bị chiếm dụng:

CPU → Instruction Bus → Flash Bus → Flash Array

                           Flash Controller
                        (đang erase/program)

CPU không thể fetch instruction trong lúc Flash controller đang thao tác. Dù bạn dùng interrupt mode, CPU vẫn bị stall - vì CPU cần fetch instruction để thực thi interrupt handler, mà instruction lại nằm trong Flash đang bận.

Kết quả: erase một page 2KB mất khoảng 20-40ms - trong khoảng thời gian đó, toàn bộ firmware đứng im, không có interrupt nào được xử lý, RTOS tick bị bỏ lỡ, watchdog không được feed.

Timeline single-bank erase:
|--run--|--ERASE (20-40ms, CPU stall)--|--run--|
         ^                            ^
         không có interrupt nào chạy được ở đây

FreeRTOS và single-bank Flash - đặc biệt nguy hiểm

Nếu task RTOS gọi Flash erase, toàn bộ RTOS bị đứng trong thời gian erase. Các task khác không chạy, RTOS scheduler không tick, timeout bị reset sai. Trên POS system chẳng hạn, giao tiếp với máy in hoặc đầu đọc thẻ sẽ bị ngắt quãng.

Giải pháp: Execute from RAM

Cách giải quyết trên single-bank: copy hàm erase vào RAM và chạy từ đó. Khi hàm đang chạy trong RAM, CPU không cần fetch instruction từ Flash → Flash bus được giải phóng → interrupt có thể được xử lý.

/* Attribute yêu cầu linker đặt hàm này vào RAM */
__attribute__((section(".RamFunc")))
void ErasePageFromRam(uint32_t page) {
    FLASH_EraseInitTypeDef cfg = {
        .TypeErase = FLASH_TYPEERASE_PAGES,
        .Banks     = FLASH_BANK_1,
        .Page      = page,
        .NbPages   = 1,
    };
    uint32_t err;
    HAL_FLASH_Unlock();
    HAL_FLASHEx_Erase(&cfg, &err);
    HAL_FLASH_Lock();
}

Linker script phải khai báo section .RamFunc:

.RamFunc :
{
    . = ALIGN(4);
    *(.RamFunc)
    *(.RamFunc.*)
    . = ALIGN(4);
} >RAM AT> FLASH

Chi tiết về execute-from-RAM và cách setup đúng sẽ có trong bài blog về Flash STM32G0.

Dual bank - giải pháp kiến trúc

STM32G0B1 hỗ trợ dual bank mode: hai bank Flash độc lập với bus riêng.

CPU → Bus → Bank 1 (đang execute code)

         Bank 2 (đang erase/program) ← không block Bank 1

Khi CPU đang chạy code từ Bank 1, có thể erase/program Bank 2 mà không freeze program. Đây là lý do dual bank là game changer cho EEPROM emulation - dùng một bank để chạy code, bank kia để lưu data và rotate.

Chi tiết dual bank và EEPROM emulation có trong note riêng và bài blog.

Verify sau khi ghi

Flash có thể ghi lỗi - đặc biệt sau nhiều chu kỳ erase/program. Nên verify sau khi ghi:

uint64_t written = *(uint64_t*)address;
uint64_t expected = data;
if (written != expected) {
    /* Ghi lỗi - xử lý error */
}

Common pitfalls

Pitfall thường gặp khi thao tác Flash

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.