[应用相关] STM32内部读写FLASH

[复制链接]
578|0
Haizangwang 发表于 2025-8-11 13:31 | 显示全部楼层 |阅读模式
很多情况下,在STM32中写入一些数据,在某些不可控因素下其数据无法保存。因此,解决此问题就要用到FLASH.

什么是内部 Flash?
Flash 是一种非易失性存储器,STM32 的程序和常量数据就存在 Flash 中。它的关键特点是:




Flash 是如何读数据的?
STM32 的 Flash 是memory-mapped(内存映射)的,即:

Flash 被挂在总线上,和 SRAM 一样的访问方式,读取数据只需要访问地址。

底层硬件会自动完成数据译码、电荷检测、字线控制等,所以我们可以像访问 RAM 一样访问 Flash。

STM32 读取和写入内部 Flash 的本质区




为什么不需要解锁就能读取?
因为 读取 Flash 是只读操作,不会修改 Flash 结构,不存在写保护一说,也不需要擦除。Flash 读取只需要:

uint16_t data = *(volatile uint16_t *)0x0800FC00;


STM32 会自动从 Flash 控制器中读取该地址的数据。

读取代码详解
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
        return *((__IO uint32_t *)(Address));
}

uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
        return *((__IO uint16_t *)(Address));
}

uint8_t MyFLASH_ReadByte(uint32_t Address)
{
        return *((__IO uint8_t *)(Address));
}



通用结构分析
*((__IO 类型 *)(Address)) 是什么意思?





举个例子:

*((__IO uint16_t *)0x0800FC00) → 读取地址0x0800FC00的2字节数据


注意事项
不要读取未对齐地址(比如读取 uint16_t 不能从奇数地址读取)

不要在 Flash 正在写入时读取 Flash

写入 Flash 的完整流程
写入 Flash 是一个有顺序、受保护的敏感操作,必须严格遵守 STM32 的流程!






  ┌────────────┐
  │ Flash_Unlock│ ← 解锁 Flash 写保护
  └─────┬──────┘
        ↓
  ┌──────────────┐
  │Flash_ErasePage│ ← 擦除所在页(将所有位置1)
  └─────┬────────┘
        ↓
  ┌──────────────────────┐
  │Flash_ProgramHalfWord │ ← 写入一个16位值(地址必须对齐)
  └────────────┬─────────┘
               ↓
       ┌────────────┐
       │ Flash_Lock │ ← 锁回 Flash,防止误写
       └────────────┘



void MyFLASH_EraseAllPages(void)
{
        FLASH_Unlock();
        FLASH_EraseAllPages();
        FLASH_Lock();
}

void MyFLASH_ErasePage(uint32_t PageAddress)
{
        FLASH_Unlock();
        FLASH_ErasePage(PageAddress);
        FLASH_Lock();
}

void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
        FLASH_Unlock();
        MyFLASH_ErasePage(Address);  
        FLASH_ProgramWord(Address, Data);
        FLASH_Lock();
}

void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
        FLASH_Unlock();
        MyFLASH_ErasePage(Address);  
        FLASH_ProgramHalfWord(Address, Data);
        FLASH_Lock();
}



把 STM32 的内部 Flash 最后一页 0x0800FC00 用作数据存储区




Store_Init() 函数解释


#define STORE_START_ADDRESS                0x0800FC00
#define STORE_COUNT                                512

uint16_t Store_Data[STORE_COUNT];

void Store_Init(void)
{
        if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)
        {
                MyFLASH_ErasePage(STORE_START_ADDRESS);
                MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);
                for (uint16_t i = 1; i < STORE_COUNT; i ++)
                {
                        MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);
                }
        }
       
        for (uint16_t i = 0; i < STORE_COUNT; i ++)
        {
                Store_Data = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);
        }
}





空间计算(为什么是 512?)
每个 uint16_t 是 2 字节(16 位),所以:

512 × 2 字节 = 1024 字节 = 1KB


正好使用 Flash 的最后一页(1KB 大小)来存储 512 个 uint16_t。

这是一种 对齐页大小的设计,防止写入跨页、浪费空间或出错。




为什么用魔数 0xA5A5?

Flash 出厂是全 0xFFFF,你写入 0xA5A5 后,可以作为初始化标志,下次重启时避免重复擦除。

Store_Save() 函数解释(将 RAM 数据写回 Flash)
void Store_Save(void)
{
        MyFLASH_ErasePage(STORE_START_ADDRESS);
        for (uint16_t i = 0; i < STORE_COUNT; i ++)
        {
                MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data);
        }
}




为什么每个数据偏移 i * 2?
因为你每个数据是 uint16_t,占用 2 字节,要避免覆盖前一个数据。

Store_Clear() 函数解释(清除并保存)

void Store_Clear(void)
{
        for (uint16_t i = 1; i < STORE_COUNT; i ++)
        {
                Store_Data = 0x0000;
        }
        Store_Save();
}


功能分析:
从第 1 个数据开始清 0(保留 Store_Data[0] 为魔数)

调用 Store_Save() 整页重写

main函数
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Store.h"
#include "Key.h"

uint8_t KeyNum;

int main(void)
{
        OLED_Init();
        Key_Init();
        Store_Init();
       
        OLED_ShowString(1, 1, "Flag:");
        OLED_ShowString(2, 1, "Data:");
       
        while (1)
        {
                KeyNum = Key_GetNum();
               
                if (KeyNum == 1)
                {
                        Store_Data[1] ++;
                        Store_Data[2] += 2;
                        Store_Data[3] += 3;
                        Store_Data[4] += 4;
                        Store_Save();
                }
               
                if (KeyNum == 2)
                {
                        Store_Clear();
                }
               
                OLED_ShowHexNum(1, 6, Store_Data[0], 4);
                OLED_ShowHexNum(3, 1, Store_Data[1], 4);
                OLED_ShowHexNum(3, 6, Store_Data[2], 4);
                OLED_ShowHexNum(4, 1, Store_Data[3], 4);
                OLED_ShowHexNum(4, 6, Store_Data[4], 4);
        }
}



FLASH读取芯片ID
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"

int main(void)
{
        OLED_Init();
       
        OLED_ShowString(1, 1, "F_SIZE:");
        OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);
       
        OLED_ShowString(2, 1, "U_ID:");
        OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);
        OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
        OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
        OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
       
        while (1)
        {
               
        }
}




应用场景
防伪 / 唯一性绑定:

使用 UID 作为设备编号上传服务器或产品注册

Flash 容量检测:

某些型号可能 Flash 容量不一致,可动态检测

License 系统:

生成授权码绑定到 UID,防止盗版

设备信息显示:

工程生产中可直接通过 OLED 显示设备 ID 进行追溯
————————————————
版权声明:本文为CSDN博主「JasmineX-1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/2504_92863595/article/details/149942294

您需要登录后才可以回帖 登录 | 注册

本版积分规则

95

主题

300

帖子

0

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