18-FLASH闪存.md 9.8 KB

18-FLASH闪存

原理

STM32F103C8T6 Flash 结构

容量:64KB

起始地址          结束地址
0x08000000  ┌────────────────────┐
            │  主存储器 (64KB)    │ ← 用户程序+数据
            │  页 0~63 (1KB/页)  │
0x08010000  ├────────────────────┤
            │                    │
0x1FFFF000  ├────────────────────┤
            │  系统存储器 (2KB)   │ ← Bootloader (出厂固件)
0x1FFFF800  ├────────────────────┤
            │  选项字节 (16B)     │ ← 读保护/看门狗配置
0x1FFFF810  └────────────────────┘
区域 地址范围 大小 说明
主存储器 0x0800 0000 ~ 0x0800 FFFF 64KB 用户代码和数据
系统存储器 0x1FFF F000 ~ 0x1FFF F7FF 2KB 系统 Bootloader
选项字节 0x1FFF F800 ~ 0x1FFF F80F 16B 配置位
唯一 ID 0x1FFF F7E8 ~ 0x1FFF F7F0 12B 芯片唯一序列号

页大小:1KB (STM32F103C8T6)

Flash 特性

读 (任意时刻)
  │
Flash 存储单元: 浮栅晶体管
  │ 初始状态 = 1 (已擦除)
  │
写操作 (只能 1→0):
  ┌──────────────┐
  │   1 → 0 ✅   │ ← 写入 0 可以
  │   0 → 1 ❌   │ ← 写入 1 不行!必须先擦除
  └──────────────┘
  │
擦除操作 (恢复为全 1):
  页擦除 (1KB) 或 整片擦除

关键结论

  • Flash 写满 0xFF 的地址写入任意值都可以
  • Flash 写非 0xFF 的地址,如果想清 0 可以,想置 1 不行
  • 修改数据时:先擦除 → 再写入

Flash 操作流程

FLASH_Unlock()
  │
  ├── 写 KEY1 = 0x45670123 到 FLASH_KEYR
  └── 写 KEY2 = 0xCDEF89AB 到 FLASH_KEYR
  │
  ▼
检查 FLASH_SR_BSY (忙)
  │
  ├── 忙 → 等待
  │
  ▼
FLASH_ErasePage(addr)    ← 擦除指定页 (1KB)
  │
  ▼
FLASH_ProgramHalfWord/Word(addr, data)    ← 编程 (16/32 位)
  │
  ▼
FLASH_Lock()

读取芯片唯一 ID

// 唯一 ID 地址 (96 位 = 12 字节)
// X/Y: wafer X/Y 坐标, W: wafer 编号, LOT: 批次号
uint32_t* pUID = (uint32_t*)0x1FFFF7E8;
uint32_t uid0 = pUID[0];   // LOT 号 (低位)
uint32_t uid1 = pUID[1];   // LOT 号 (高位) + W 编号
uint32_t uid2 = pUID[2];   // X + Y 坐标

API 表格

FLASH

函数 说明
void FLASH_Unlock(void) 解锁 Flash 控制器
void FLASH_Lock(void) 锁定 Flash 控制器
FLASH_Status FLASH_ErasePage(uint32_t Page_Address) 擦除指定页 (1KB)
FLASH_Status FLASH_EraseAllPages(void) 擦除所有页
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data) 16 位编程
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data) 32 位编程
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data) 编程选项字节
FLASH_Status FLASH_EraseOptionBytes(void) 擦除选项字节
void FLASH_ITConfig(uint32_t FLASH_IT, FunctionalState NewState) 中断配置
void FLASH_SetLatency(uint32_t FLASH_Latency) 设置等待周期
FLASH_Status FLASH_GetStatus(void) 获取状态
FlagStatus FLASH_GetFlagStatus(uint32_t FLASH_FLAG) 获取标志
void FLASH_ClearFlag(uint32_t FLASH_FLAG) 清除标志

FLASH_Status 返回值

状态 说明
FLASH_BUSY 忙 (BSY=1)
FLASH_COMPLETE 操作完成
FLASH_ERROR_PG 编程错误
FLASH_ERROR_WRP 写保护错误
FLASH_TIMEOUT 超时

FLASH_FLAG

标志 说明
FLASH_FLAG_BSY 忙标志
FLASH_FLAG_PGERR 编程错误
FLASH_FLAG_WRPRTERR 写保护错误
FLASH_FLAG_EOP 操作结束

FLASH_Latency (Flash 等待周期):

系统时钟频率 等待周期
0~24MHz 0 (FLASH_Latency_0)
24~48MHz 1 (FLASH_Latency_1)
48~72MHz 2 (FLASH_Latency_2)

完整代码示例

基本 Flash 读写

/*------------------------------------------------
 * FLASH_Example.c
 * 保存/加载用户配置参数到 Flash 最后一页
 *------------------------------------------------*/

#include "stm32f10x.h"
#include <stdint.h>
#include <string.h>

/* 存储位置:Flash 最后一页 (64KB - 1KB = 63KB 偏移) */
#define CONFIG_FLASH_ADDR  0x0800FC00    // 第 63 页起始 (0x08000000 + 63*1024)

/* 用户配置结构体 (不超过 1KB) */
typedef struct {
    uint16_t magic;         // 校验魔数 0x55AA
    uint16_t baudRate;      // 波特率
    uint8_t  channel;       // 通道号
    uint8_t  reserved[21];  // 保留 (对齐)
} UserConfig;

static UserConfig g_config;

/* ---- 从 Flash 读取配置 ---- */
void Config_Load(void)
{
    uint16_t* pFlash = (uint16_t*)CONFIG_FLASH_ADDR;
    uint16_t* pDest  = (uint16_t*)&g_config;
    uint32_t  i;

    for (i = 0; i < sizeof(UserConfig) / 2; i++)
        pDest[i] = pFlash[i];

    if (g_config.magic != 0x55AA)
    {
        /* 无效配置,恢复默认 */
        g_config.magic    = 0x55AA;
        g_config.baudRate = 115200;
        g_config.channel  = 1;
    }
}

/* ---- 保存配置到 Flash ---- */
void Config_Save(void)
{
    uint16_t* pSrc = (uint16_t*)&g_config;
    uint32_t  i;

    g_config.magic = 0x55AA;

    FLASH_Unlock();

    /* 擦除整个页 */
    FLASH_ErasePage(CONFIG_FLASH_ADDR);

    /* 逐 16 位写入 */
    for (i = 0; i < sizeof(UserConfig) / 2; i++)
    {
        FLASH_ProgramHalfWord(CONFIG_FLASH_ADDR + i * 2, pSrc[i]);
    }

    FLASH_Lock();
}

/* ---- 写验证 ---- */
uint8_t Config_Verify(void)
{
    uint16_t* pFlash = (uint16_t*)CONFIG_FLASH_ADDR;
    uint16_t* pDest  = (uint16_t*)&g_config;
    uint32_t  i;

    for (i = 0; i < sizeof(UserConfig) / 2; i++)
    {
        if (pFlash[i] != pDest[i])
            return 0;   // 验证失败
    }
    return 1;           // 验证通过
}

int main(void)
{
    Config_Load();

    /* 修改配置 */
    g_config.baudRate = 9600;
    g_config.channel  = 2;

    Config_Save();

    if (Config_Verify())
    {
        /* 保存成功 */
    }

    while (1);
}

读取芯片唯一 ID

/*------------------------------------------------
 * UID_Example.c
 *------------------------------------------------*/

#include "stm32f10x.h"

typedef struct {
    uint32_t lotNumLow;     // LOT 号低 32 位
    uint32_t lotNumHigh;    // LOT 号高 16 位 + Wafer 编号
    uint32_t coordXY;       // X 坐标 + Y 坐标
} ChipUID;

void ReadChipUID(ChipUID* uid)
{
    uint32_t* pUID = (uint32_t*)0x1FFFF7E8;
    uid->lotNumLow  = pUID[0];
    uid->lotNumHigh = pUID[1];
    uid->coordXY    = pUID[2];
}

int main(void)
{
    ChipUID uid;
    ReadChipUID(&uid);

    /* uid 可用于软件加密、设备识别等 */

    while (1);
}

Flash 编程错误处理

/*------------------------------------------------
 * FLASH_ErrorHandling.c
 *------------------------------------------------*/

#include "stm32f10x.h"

FLASH_Status Flash_ProgramSafe(uint32_t addr, uint16_t data)
{
    FLASH_Status status;

    status = FLASH_ProgramHalfWord(addr, data);
    if (status != FLASH_COMPLETE)
    {
        /* 错误处理 */
        if (status == FLASH_ERROR_WRP)
        {
            /* 写保护错误:检查该页是否被保护 */
        }
        else if (status == FLASH_ERROR_PG)
        {
            /* 编程错误:地址未擦除或对齐问题 */
        }
    }
    return status;
}

int main(void)
{
    FLASH_Unlock();

    FLASH_ErasePage(0x0800FC00);

    if (Flash_ProgramSafe(0x0800FC00, 0x1234) == FLASH_COMPLETE)
    {
        /* 编程成功 */
    }

    FLASH_Lock();

    while (1);
}

常见坑点

  1. 擦除以页为单位 (1KB)

    • STM32F103C8T6 每页 1KB,不能单独擦除几个字节
    • 修改一个字节也要擦除整页,然后重写整页
    • 建议:用一整页存配置,不要和其他数据混用
  2. 编程必须 16 位对齐

    • FLASH_ProgramHalfWord 要求地址是半字对齐 (偶数地址)
    • FLASH_ProgramWord 要求地址是字对齐 (4 的倍数)
    • 不对齐会导致硬 fault 或写入错误
  3. 不要在 Flash 中执行程序时擦写同一页

    • 如果代码正在执行的地址位于要擦除的页中,擦除后 CPU 取指失败
    • 解决方法:擦写代码放到 RAM 中执行,或避开当前代码所在页
    • 用户配置存到最后一页(远离代码区)
  4. Flash 等待周期

    • 72MHz 时需设置 FLASH_SetLatency(FLASH_Latency_2) (2 个等待周期)
    • SystemInit() 中自动配置,手动改主频后也要重新设置
  5. 写保护

    • 默认没有写保护,可直接写入
    • 如果返回 FLASH_ERROR_WRP,需检查选项字节中的写保护位
    • 可用 FLASH_EraseOptionBytes() 清除保护
  6. Flash 擦写寿命

    • 约 1 万次擦写 (STM32F103 数据手册)
    • 频繁保存配置会损耗 Flash
    • 建议:只有配置变化时才保存,不要在循环中反复擦写
  7. 操作后锁定 Flash

    • 擦写完成后建议调 FLASH_Lock() 防止误操作
    • 否则意外代码跑飞可能破坏 Flash 内容
  8. 唯一 ID 只读

    • 0x1FFFF7E8 是只读区域,不能擦写
    • 可用于生成设备唯一序列号、软件加密绑定
  9. 读 Flash 直接用指针

    • 读 Flash 不需要解锁,直接用 *(uint16_t*)addr*(uint32_t*)addr
    • 不需要任何初始化,上电即可读
  10. 系统存储器与选项字节

    • 系统存储器 (0x1FFFF000) 是出厂 Bootloader,不可擦写
    • 选项字节 (0x1FFFF800) 可用 FLASH_ProgramOptionByteData 修改(需谨慎)