Firmware & RTOS/Troubleshooting

[MCU Flash] 플래시 메모리 데이터 깨짐 원인: Erase-before-Write 시퀀스 누락

임베디드 친구 2026. 6. 25. 20:40
반응형

내용 요약

현상: 비휘발성 플래시 메모리(Non-volatile Flash Memory)에 데이터 저장 후, 읽을 시 깨진 데이터가 읽히는 현상.

원인: 기존 데이터가 남아있는 영역에 지우기 시퀀스 없이 Write(Program) 명령을 직접 실행함.

해법: 쓰기 명령을 실행하기 전, 저장될 섹터(Sector) 또는 페이지(Page)에 Flash Erase 시퀀스를 선행 호출

내장 플래시 메모리 데이터 오염(Data Corruption) 발생 증상

임베디드 시스템 개발 중 설정값, 캘리브레이션 데이터, 혹은 사용자 로그를 MCU 내장 또는 외장 플래시 메모리(Flash Memory)에 저장할 때 발생하는 대표적인 버그입니다. 최초 펌웨어 다운로드 이후 첫 번째 쓰기 동작은 정상적으로 수행되지만, 동일한 주소 영역에 새로운 데이터를 덮어쓰기를 시도한 직후부터 데이터 읽기 시 비정상적인 결과가 반환됩니다.
이 오류가 발생하면 런타임에 유효하지 않은 메모리 참조가 일어나 구조체 바인딩 오류가 발생하거나, 심각한 경우 잘못된 포인터 주소를 참조하여 코어가 즉시 HardFault_Handler 무한 루프로 진입할 수 있습니다. 데이터를 읽어와 제어를 수행하는 루프에서는 하드웨어 예외인 Alignment Fault나 유효하지 않은 분기로 인한 시스템 락업(Lock-up)으로 이어지기도 합니다. 개발자는 저장 로직이나 통신 인터페이스 프로토콜을 의심하기 쉽지만, 이는 플래시 제어 시퀀스의 물리 하드웨어 제약 조건을 위배하여 데이터 무결성(Data Integrity)이 무너진 전형적인 하드웨어 드라이버 오작동 구조입니다.

플래시 메모리 셀(Cell) 구조와 Erase-before-Write 시퀀스의 물리적 인과관계

플래시 메모리는 플로팅 게이트 트랜지스터(Floating Gate Transistor) 어레이 구조로 디지털 논리 상태를 영구 저장합니다. 반도체 물리 계층의 동작 원리상, 플래시 셀에 데이터를 기록하는 프로그래밍(Program/Write) 연산은 셀 내부로 전자를 주입하여 논리적 상태를 1에서 0으로 바꾸는 단방향 연산만 가능합니다. 주입된 전자를 방출시켜 논리적 상태를 다시 0에서 1로 되돌리는 역방향 연산은 오직 물리적인 지우기(Erase) 명령을 통해서만 수행될 수 있습니다.지우기(Erase) 연산이 완료된 물리 플래시 영역의 초기 상태는 모든 비트가 1로 채워진 0xFF 상태가 됩니다. 만약 기존 데이터가 저장되어 있던 주소 영역에 지우기 시퀀스를 누락한 상태로 새로운 바이트를 덮어쓰면, 플래시 컨트롤러 하드웨어는 기존의 0 상태인 비트를 1로 변경하지 못합니다.결과적으로 물리 메모리 셀 단에서는 기존 데이터 비트와 새로 기입하려는 데이터 비트 간의 물리적인 Bitwise AND 결합(하드웨어적 비트 마스킹 밀림)이 발생하여 완전히 왜곡된 데이터가 저장됩니다. 이를 수식적 구조로 표현하면 아래와 같습니다.
$$\text{Resulting Flash Data} = \text{Existing Data} \ \text{AND} \ \text{New Data}$$
예를 들어 기존 데이터 0x55 (01010101)가 존재하는 주소에 지우기 없이 0xAA (10101010)를 쓰려고 시도하면, 논리 1 구조를 가지는 비트들이 원래의 0에 갇혀 최종적으로 0x00 (00000000)이라는 엉뚱한 데이터가 래칭됩니다. 이것이 플래시 제어 시퀀스 설계 시 반드시 Erase-before-Write 규칙을 지켜야하는 이유입니다.

데이터 왜곡을 유발하는 잘못된 플래시 덮어쓰기 C 코드 예시 (Bad Case)

아래 소스코드는 기존 데이터가 남아 있는 특정 섹터 영역에 물리적 지우기 동작 없이, 곧바로 플래시 프로그래밍 API를 호출하여 데이터 변형을 유발하는 잘못된 예시입니다.

#include "stm32f4xx_hal.h"

#define FLASH_TARGET_ADDRESS  0x08060000 /* Flash Sector 7 */

void Update_System_Config_Bad(uint32_t *p_data, uint32_t length)
{
    /* Unlock Flash control register access */
    HAL_FLASH_Unlock();

    /* BAD CASE: Directly programming data without executing Sector/Page Erase */
    for(uint32_t i = 0; i < length; i++)
    {
        uint32_t current_addr = FLASH_TARGET_ADDRESS + (i * 4);

        /* This operation fails to write '1' bits over existing '0' bits */
        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, current_addr, p_data[i]) != HAL_OK)
        {
            /* Error handling */
        }
    }

    /* Lock Flash back to protect against accidental writes */
    HAL_FLASH_Lock();
}

확정적 데이터 기록을 보장하는 올바른 Erase-before-Write 방어적 C 코드 (Good Case)

문제를 해결하기 위해서는 데이터를 기록하기 전, 타겟 주소가 속한 페이지 또는 섹터를 0xFF로 밀어버리는 지우기 시퀀스가 선행되어야 합니다. 아래 소스코드는 타겟 섹터를 먼저 클리어한 후 새 값을 할당하는 방식입니다.

#include "stm32f4xx_hal.h"

#define FLASH_TARGET_ADDRESS  0x08060000 /* Flash Sector 7 */
#define FLASH_TARGET_SECTOR   FLASH_SECTOR_7

void Update_System_Config_Good(uint32_t *p_data, uint32_t length)
{
    FLASH_EraseInitTypeDef erase_init;
    uint32_t sector_error = 0;

    /* Unlock Flash control register access */
    HAL_FLASH_Unlock();

    /* 1. Configure the sector erase parameters */
    erase_init.TypeErase    = FLASH_TYPEERASE_SECTORS;
    erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    erase_init.Sector       = FLASH_TARGET_SECTOR;
    erase_init.NbSectors    = 1;

    /* 2. Execute Sector Erase sequence to force all bits back to 0xFF */
    if(HAL_FLASHEx_Erase(&erase_init, &sector_error) != HAL_OK)
    {
        /* Erase failure handling */
        HAL_FLASH_Lock();
        return;
    }

    /* 3. Safe defensive programming loop: write data to the initialized 0xFF area */
    for(uint32_t i = 0; i < length; i++)
    {
        uint32_t current_addr = FLASH_TARGET_ADDRESS + (i * 4);

        if(HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, current_addr, p_data[i]) != HAL_OK)
        {
            /* Program failure handling */
            break;
        }
    }

    /* Lock Flash back to protect against accidental writes */
    HAL_FLASH_Lock();
}

##핵심 수정 포인트 설명

  • FLASH_EraseInitTypeDef 구조체 선언 및 초기화: 지우기를 수행할 대상의 단위 유형(FLASH_TYPEERASE_SECTORS)과 고유 섹터 식별 심볼(FLASH_TARGET_SECTOR)을 명시하여 타겟 바운더리를 명확히 격리했습니다.
  • HAL_FLASHEx_Erase 지우기 함수 최우선 실행: 데이터 루프를 가동하기 전에 물리적 전자를 방출시켜 타겟 영역 전체의 메모리 상태를 완벽히 0xFF로 바인딩(초기화)했습니다.
  • 순차적 워드 단위 쓰기 매핑: 지우기가 완료되어 메모리 블록 위에 HAL_FLASH_Program을 안전하게 수행함으로써 데이터 누락과 Bitwise AND 결합 왜곡 현상을 원천 차단했습니다.

플래시 메모리(Flash Memory) 제어 디버깅 가이드

실무 가이드라인 및 디버거(ST-LINK, J-Link) 연동을 통한 빠른 추적 노하우입니다.

  • 메모리 윈도우 뷰어(Memory Window Viewer)의 크로스 체킹: 플래시 제어 함수 실행 전후로 IDE의 메모리 주소 모니터링 창에 타겟 물리 주소(0x08060000)를 매핑하십시오. 쓰기 명령 직전에 해당 메모리 데이터 영역이 전체적으로 FF 바이트 스트림을 유지하고 있는지 확인해야 합니다. 만약 FF가 아닌 다른 지저분한 심볼이 보인다면 지우기 함수 호출 타이밍이나 섹터 오프셋 주소 연산이 꼬인 상태입니다.
  • 플래시 상태 레지스터(Flash Status Register)의 에러 플래그 모니터링: 덮어쓰기 오동작이 일어날 때 MCU 내부의 플래시 제어 상태 레지스터(FLASH_SR)를 SFR(Special Function Register) 뷰를 통해 확인하십시오. 지우지 않은 영역에 쓰기를 시도하면 많은 ARM Cortex-M 계열의 하드웨어 컨트롤러가 프로그래밍 순서 오류 플래그(PGSERR)나 보호 오류 플래그(WRPERR)를 세트하고 데이터 바인딩을 즉시 중단하므로 원인을 직관적으로 파악할 수 있습니다.
  • 지우기 단위(Block/Sector/Page)와 주소 경계선 검증: 플래시는 주소 단위(Byte)로 지울 수 없으며 칩 스펙에 명시된 블록 단위로만 지워집니다. 메모리 맵(MAP) 링커 파일 혹은 데이터시트를 확인하여 자신이 지우려는 섹터 범위 내에 실행 코드가 위치해 있지는 않은지 검증하십시오. 엉뚱한 영역을 지우는 실수가 발생하면 정상적인 펌웨어 코드가 날아가 동작 중 즉시 코어가 정지(Crash)될 수 있습니다.
반응형