[STM32F1] 如何擦除闪存的单个页

[复制链接]
139|32
zerorobert 发表于 2026-3-14 10:16 | 显示全部楼层
STM32F1系列擦除后全为0xFFFFFFFF
geraldbetty 发表于 2026-3-14 10:46 | 显示全部楼层
关键要匹配芯片的页大小和总 Flash 容量,避免页号越界
uytyu 发表于 2026-3-14 14:49 | 显示全部楼层
STM32F1 擦除单个 Flash 页的核心是解锁 Flash→计算页地址→调用擦除函数→锁定 Flash,且必须按页擦除
jtracy3 发表于 2026-3-14 15:55 | 显示全部楼层
Flash 操作期间,建议关闭全局中断,防止中断服务程序访问 Flash 导致总线冲突或 HardFault。
wilhelmina2 发表于 2026-3-14 16:26 | 显示全部楼层
在STM32F1系列中,擦除内部Flash必须以页为单位进行,无法单独擦除某个字节或半字。
uptown 发表于 2026-3-15 22:41 | 显示全部楼层
STM32F1 闪存操作必须在解锁状态下进行
sesefadou 发表于 2026-3-17 12:08 | 显示全部楼层
#include "stm32f1xx.h"
#include <stdint.h>
#include <string.h>

// 定义 Flash 页大小 (根据具体型号修改,常见为 1024 或 2048)
#define FLASH_PAGE_SIZE  1024

// 定义要操作的页索引 (0 表示第一页)
// 注意:不要操作存放当前代码的页!通常使用最后几页作为数据存储区
#define TARGET_PAGE_INDEX  62  // 例如操作倒数第二页 (假设 128KB 容量共 64 页)

// 计算目标页的起始地址
// STM32F103 Flash 基地址通常为 0x08000000
#define FLASH_BASE_ADDR    0x08000000
#define TARGET_PAGE_ADDR   (FLASH_BASE_ADDR + (TARGET_PAGE_INDEX * FLASH_PAGE_SIZE))

// 定义一个缓冲区用于暂存数据 (必须大于等于页大小)
uint8_t page_buffer[FLASH_PAGE_SIZE];

/**
* @brief 解锁 Flash
*/
void Flash_Unlock(void) {
    if (FLASH->CR & FLASH_CR_LOCK) {
        FLASH->KEYR = 0x45670123;
        FLASH->KEYR = 0xCDEF89AB;
    }
}

/**
* @brief 锁定 Flash
*/
void Flash_Lock(void) {
    FLASH->CR |= FLASH_CR_LOCK;
}

/**
* @brief 等待 Flash 操作完成
* @return 0: 成功, 1: 错误 (如写保护)
*/
uint8_t Flash_WaitReady(void) {
    volatile uint32_t timeout = 0xFFFFF;
    while ((FLASH->SR & FLASH_SR_BSY) && (timeout--));
   
    if (timeout == 0) return 1; // 超时
   
    if (FLASH->SR & (FLASH_SR_WRPRTERR | FLASH_SR_PGERR)) {
        FLASH->SR |= (FLASH_SR_WRPRTERR | FLASH_SR_PGERR); // 清除错误标志
        return 1;
    }
    return 0;
}

/**
* @brief 擦除指定的一页
* @param PageAddress: 页的起始地址 (必须是页对齐的)
* @return 0: 成功, 1: 失败
*/
uint8_t Flash_Erase_Page(uint32_t PageAddress) {
    Flash_Unlock();
   
    // 等待就绪
    if (Flash_WaitReady()) {
        Flash_Lock();
        return 1;
    }

    // 设置页擦除模式 (PER)
    FLASH->CR |= FLASH_CR_PER;
   
    // 设置要擦除的地址 (AR 寄存器)
    FLASH->AR = PageAddress;
   
    // 开始擦除 (STRT)
    FLASH->CR |= FLASH_CR_STRT;
   
    // 等待擦除完成
    if (Flash_WaitReady()) {
        FLASH->CR &= ~FLASH_CR_PER; // 清除 PER 位
        Flash_Lock();
        return 1;
    }
   
    // 清除 PER 位
    FLASH->CR &= ~FLASH_CR_PER;
   
    Flash_Lock();
    return 0;
}

/**
* @brief 向 Flash 写入半字 (2 字节)
* @param Address: 写入地址 (必须 2 字节对齐)
* @param Data: 要写入的数据
* @return 0: 成功, 1: 失败
*/
uint8_t Flash_Write_HalfWord(uint32_t Address, uint16_t Data) {
    Flash_Unlock();
   
    if (Flash_WaitReady()) {
        Flash_Lock();
        return 1;
    }

    // 设置编程模式 (PG)
    FLASH->CR |= FLASH_CR_PG;
   
    // 写入数据 (直接操作地址)
    *(__IO uint16_t*)Address = Data;
   
    // 等待写入完成
    if (Flash_WaitReady()) {
        FLASH->CR &= ~FLASH_CR_PG;
        Flash_Lock();
        return 1;
    }
   
    // 验证数据 (可选,但推荐)
    if (*(__IO uint16_t*)Address != Data) {
        FLASH->CR &= ~FLASH_CR_PG;
        Flash_Lock();
        return 1;
    }

    FLASH->CR &= ~FLASH_CR_PG;
    Flash_Lock();
    return 0;
}

/**
* @brief 模拟修改 Flash 中部分数据 (读-改-写)
* @param offset: 页内的偏移量
* @param data: 新数据指针
* @param len: 数据长度
*/
void Flash_Modify_Data(uint16_t offset, uint8_t* data, uint16_t len) {
    uint32_t i;
    uint32_t page_start_addr = TARGET_PAGE_ADDR;
   
    // 1. 边界检查
    if (offset + len > FLASH_PAGE_SIZE) return;

    // 2. 【读】将整页数据复制到 RAM 缓冲区
    // 注意:如果该页是全新的(0xFFFF),直接复制即可
    memcpy(page_buffer, (uint8_t*)page_start_addr, FLASH_PAGE_SIZE);

    // 3. 【改】在 RAM 中修改数据
    for (i = 0; i < len; i++) {
        page_buffer[offset + i] = data;
    }

    // 4. 【擦】擦除整个页
    // !!! 关键步骤:此时该页所有数据变为 0xFFFF
    if (Flash_Erase_Page(page_start_addr) != 0) {
        return; // 擦除失败处理
    }

    // 5. 【写】将缓冲区数据写回 Flash
    // Flash 写入必须以 2 字节 (Half Word) 为单位
    for (i = 0; i < FLASH_PAGE_SIZE; i += 2) {
        uint16_t word_to_write;
        // 组合两个字节为一个半字 (小端模式)
        word_to_write = page_buffer | (page_buffer[i+1] << 8);
        
        // 如果写入全 0xFFFF,理论上可以跳过以节省时间,但为了数据一致性通常全写
        if (word_to_write != 0xFFFF) {
             if (Flash_Write_HalfWord(page_start_addr + i, word_to_write) != 0) {
                 // 写入失败处理
                 break;
             }
        }
    }
}

// 使用示例
/*
uint8_t new_data[] = {0xAA, 0xBB, 0xCC};
// 修改目标页偏移量为 10 的位置开始的 3 个字节
Flash_Modify_Data(10, new_data, 3);
*/
minzisc 发表于 2026-3-17 14:01 | 显示全部楼层
在STM32F1系列中,擦除闪存的单个页需通过特定的操作流程实现。
hilahope 发表于 2026-3-17 15:25 | 显示全部楼层
如果代码是从 Flash 运行的,擦除当前代码所在的页会导致程序跑飞。
sanfuzi 发表于 2026-3-17 16:18 | 显示全部楼层
不要在正在执行的代码页进行擦除              
fengm 发表于 2026-3-20 12:19 | 显示全部楼层
必须先解锁后擦除              
mattlincoln 发表于 2026-3-20 14:54 | 显示全部楼层
在对Flash进行编程或擦除前,必须先解锁。
芯路例程 发表于 2026-3-25 20:02 | 显示全部楼层
把当前页面剩余有用的数据存到随机存取存储器里。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部
0