一、FLASH

1.FLASH简介

        STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程

读写FLASH的用途:

        利用程序存储器的剩余空间来保存掉电不丢失的用户数据

        通过在程序中编程(IAP),实现程序的自我更新

        在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序

        在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序

2.闪存模块组织

        主存储器地址只要以000、400、800、C00结尾的,都一定是页的起始地址。

        系统存储器起始地址是0x1FFFF000,容量2K。

        选项字节起始地址是0x1FFFF800,容量是16个字节

3.FLASH基本结构

4.FLASH解锁

        FPEC(闪存编程和擦除控制器)共有三个键值:

                RDPRT键 = 0x000000A5(解除读保护)

                KEY1 = 0x45670123

                KEY2 = 0xCDEF89AB

        解锁:

                复位后,FPEC被保护,不能写入FLASH_CR

                在FLASH_KEYR先写入KEY1,再写入KEY2,解锁

                错误的操作序列会在下次复位前锁死FPEC和FLASH_CR

        加锁:

                设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR

5.使用指针访问存储器

        使用指针读指定地址下的存储器:

                uint16_t Data = *((__IO uint16_t *)(0x08000000));

        使用指针写指定地址下的存储器:

                *((__IO uint16_t *)(0x08000000)) = 0x1234;

        其中:

                #define    __IO    volatile

6.程序存储器编程

1. 读 FLASH_CR 的 LOCK 位

  • 作用:先检查闪存控制器(FPEC)是否 “加锁”。FLASH_CR 是闪存控制寄存器LOCK 位是其中的 “锁标志位”。
  • 意义:决定后续是否需要解锁,保证操作权限合法。

2. 判断 LOCK 位是否为 1

  • LOCK=1:表示闪存控制器被 “锁住”,需先执行解锁序列(对应你之前提到的 KEY1+KEY2 流程 ),解锁后才能操作 FLASH_CR
  • LOCK=0:控制器已解锁,可直接进入下一步。

3. 置 FLASH_CR 的 PG 位 = 1

  • PG 位:是 FLASH_CR 里的 “编程使能位”。置 1 后,硬件允许向闪存写入数据(半字、字等)。
  • 本质:开启 “编程模式”,为实际写入数据做准备。

4. 在指定地址写入半字(16 位)

  • “半字”:指 16 位数据(如 0x1234 ),是很多 MCU 闪存的基本写入单元。
  • “指定地址”:要编程的闪存地址(需在可写入的闪存区间内,且地址需合法、未被保护 )。

5. 判断 FLASH_SR 的 BSY 位是否为 1

  • FLASH_SR:是闪存状态寄存器BSY 位是 “忙标志位”。
  • BSY=1:表示闪存正在执行编程操作(内部硬件在擦写存储单元),需等待;
  • BSY=0:操作完成,可进入下一步校验。

6. 读编程地址并检查写入的数据

  • 目的:验证写入是否成功(防止硬件错误、电压异常等导致写入失败 )。
  • 操作:重新读取 “第 4 步写入地址” 的数据,和原始要写入的值对比,确认是否一致

7.程序存储器页擦除

1. 读 FLASH_CR 的 LOCK 位

  • 作用:检查闪存控制器(FPEC)是否 “加锁”。FLASH_CR 是闪存控制寄存器LOCK 位是其中的 “锁标志位”。
  • 意义:决定后续是否需要解锁,保证操作权限合法(防止误操作擦除闪存 )。

2. 判断 LOCK 位是否为 1

  • LOCK=1:表示闪存控制器被 “锁住”,需先执行解锁序列(如你之前提到的 KEY1+KEY2 流程 ),解锁后才能操作 FLASH_CR
  • LOCK=0:控制器已解锁,可直接进入下一步。

3. 置 FLASH_CR 的 PER=1 + 配置 FLASH_AR + 置 STRT=1

这一步是 **“擦除准备 + 启动擦除”** 的核心操作:

  • PER=1PER 是 FLASH_CR 里的 “页擦除使能位”,置 1 后,硬件进入 “页擦除模式”。
  • FLASH_AR:是闪存地址寄存器,需在其中写入 “要擦除的页地址”(指定擦除哪个闪存页 )。
  • STRT=1STRT 是 FLASH_CR 里的 “擦除启动位”,置 1 后,硬件开始执行页擦除操作(内部会将指定页的内容擦除为全 1,如 0xFFFF )。

4. 判断 FLASH_SR 的 BSY 位是否为 1

  • FLASH_SR:是闪存状态寄存器BSY 位是 “忙标志位”。
  • BSY=1:表示闪存正在执行擦除操作(内部硬件在擦除存储单元 ),需等待;
  • BSY=0:擦除完成,可进入下一步校验。

5. 读出并验证被擦除页的数据

  • 目的:确认擦除是否成功(防止硬件异常、地址错误导致擦除失败 )。
  • 操作:重新读取 “第 3 步指定的页地址”,检查数据是否为擦除后的值(通常是全 1,如 0xFFFF ,需结合芯片手册确认 )。

8.程序存储器全擦除

1. 读 FLASH_CR 的 LOCK 位

  • 作用:检查闪存控制器(FPEC)是否 “加锁”。FLASH_CR 是闪存控制寄存器LOCK 位是其中的 “锁标志位”。
  • 意义:决定后续是否需要解锁,保证操作权限合法(防止误操作全擦除闪存 )。

2. 判断 LOCK 位是否为 1

  • LOCK=1:表示闪存控制器被 “锁住”,需先执行解锁序列(如你之前提到的 KEY1+KEY2 流程 ),解锁后才能操作 FLASH_CR
  • LOCK=0:控制器已解锁,可直接进入下一步。

3. 置 FLASH_CR 的 MER=1 + 置 STRT=1

这一步是 **“全擦除准备 + 启动全擦除”** 的核心操作:

  • MER=1MER 是 FLASH_CR 里的 “全擦除使能位”(全称可能是 Mass Erase 或 Main Erase ),置 1 后,硬件进入 “全擦除模式”(区别于 “页擦除”,会擦除整个闪存所有可擦除区域 )。
  • STRT=1STRT 是 FLASH_CR 里的 “擦除启动位”,置 1 后,硬件开始执行全擦除操作(内部会将整个闪存的内容擦除为全 1,如 0xFFFF ,具体需看芯片手册 )。

4. 判断 FLASH_SR 的 BSY 位是否为 1

  • FLASH_SR:是闪存状态寄存器BSY 位是 “忙标志位”。
  • BSY=1:表示闪存正在执行全擦除操作(内部硬件在批量擦除存储单元 ),需等待;
  • BSY=0:全擦除完成,可进入下一步校验。

5. 读出并验证所有页的数据

  • 目的:确认全擦除是否成功(防止硬件异常、地址范围错误导致擦除不彻底 )。
  • 操作:读取整个闪存所有页的地址(或关键页、全范围 ),检查数据是否为擦除后的值(通常是全 1,如 0xFFFF ,需结合芯片手册确认 )

9.选项字节

        RDP:写入RDPRT键(0x000000A5)后解除读保护

        nRDP:写入RDP数据的反码

        USER:配置硬件看门狗和进入停机/待机模式是否产生复位

        Data0/1:用户可自定义使用

        WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)

10.选项字节编程

        检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作

        解锁FLASH_CR的OPTWRE位——选择字节解锁

        设置FLASH_CR的OPTPG位为1——即将写入选项字节

        写入要编程的半字到指定的地址

        等待BSY位变为0——写入完成

        读出写入的地址并验证数据

11.选项字节擦除

        检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作

        解锁FLASH_CR的OPTWRE位——选择字节解锁

        设置FLASH_CR的OPTER位为1——即将擦除选项字节

        设置FLASH_CR的STRT位为1——触发芯片,开始干活

        等待BSY位变为0——擦除完成

        读出被擦除的选择字节并做验证

12.器件电子签名

        电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名

        闪存容量寄存器:

                基地址:0x1FFF F7E0

                大小:16位

        产品唯一身份标识寄存器:

                基地址: 0x1FFF F7E8

                大小:96位

二、FLASH库函数

void FLASH_Unlock(void);

作用:解锁

void FLASH_Lock(void);

作用:加锁

FLASH_Status FLASH_ErasePage(uint32_t Page_Address);

作用:闪存擦除某一页

返回值:FLASH_BUSY = 1,——芯片当期忙

   FLASH_ERROR_PG,——编程错误

   FLASH_ERROR_WRP,——写保护错误

  FLASH_COMPLETE,——完成

  FLASH_TIMEOUT——等待超时

FLASH_Status FLASH_EraseAllPages(void);

作用:全擦除

FLASH_Status FLASH_EraseOptionBytes(void);

作用:擦除选项字节

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);

作用:指定地址写入字

FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

作用:指定地址写入半字

FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);

作用:选项字节的写入Data0和Data1

FLASH_Status FLASH_EnableWriteProtection(uint32_t FLASH_Pages);

作用:选项字节的写保护

FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState);

作用:选项字节的读保护

FLASH_Status FLASH_UserOptionByteConfig(uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY);

作用:用户选项字节的三个配置位

uint32_t FLASH_GetUserOptionByte(void);

作用:获取用户选项的三个配置位

uint32_t FLASH_GetWriteProtectionOptionByte(void);

作用:获取选项字节的写保护状态

FlagStatus FLASH_GetReadOutProtectionStatus(void);

作用:获取选项字节的读保护状态

FlagStatus FLASH_GetPrefetchBufferStatus(void);

作用:获取预取缓冲区状态

void FLASH_ITConfig(uint32_t FLASH_IT, FunctionalState NewState);

作用:中断使能

FlagStatus FLASH_GetFlagStatus(uint32_t FLASH_FLAG);

作用:获取标志位

void FLASH_ClearFlag(uint32_t FLASH_FLAG);

作用:清除标志位

FLASH_Status FLASH_GetStatus(void);

作用:获取状态

FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);

作用:等待上一次操作(等待Busy)

三、代码

1.代码1——读写内部FLASH

        利用内部FLASH程序存储器的剩余空间,来存储掉电不丢失的参数

        工程结构:两个底层模块

                ①MyFLASH:完成闪存的读取、擦除和编程

                ②Store:参数数据的读写和存储管理

        使用SARM缓存数组管理FLASH的最后一页,实现参数的任意读写和保存

MyFLASH.c

#include "stm32f10x.h"                  // Device header


// 读取32位的字
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
	return *((__IO uint32_t *)(Address));
}

// 读取16位的字
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
	return *((__IO uint16_t *)(Address));
}

// 读取8位的字——读取字节
uint8_t MyFLASH_ReadByte(uint32_t Address)
{ 
	return *((__IO uint8_t *)(Address));
}

// 全擦除
void MyFLASH_EraseAllPages(void)
{
	FLASH_Unlock();
	FLASH_EraseAllPages();
	FLASH_Lock();
}

// 页擦除
void MyFLASH_ErasePage(uint32_t Page_Address)
{
	FLASH_Unlock();
	FLASH_ErasePage(Page_Address);
	FLASH_Lock();
}

// 字编程
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
	FLASH_Unlock();
	FLASH_ProgramWord(Address, Data);
	FLASH_Lock();
}

// 半字编程
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
	FLASH_Unlock();
	FLASH_ProgramHalfWord(Address, Data);
	FLASH_Lock();
}

store.c

#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"

#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)				//	说明是不是第一次使用 判断最后一页的第一个半字节是不是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++)						//	把闪存的数据全都转到SARM数组中
		{
			Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);
		}
	
}

//	备份保存
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]);
		}
}

void Store_Clear(void)
{
	for(uint16_t i = 1; i < STORE_COUNT; i++)						//	清空数组
		{
			Store_Data[i] = 0x0000;
		}
	Store_Save();
}

main.c

#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);
	}	
}

        当按下按键1时,数据依次加1,并将数组的数据转入到闪存中,当按下按键2时,闪存中除了第一位标志位不清除,其余为都清除为0;

2.代码2——读取芯片ID

main.c

#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)
	{
		
	}	
}

        使用指针访问寄存器即可。F_SIZE为存储容量大小,下面为ID号。

四、总结

        STM32的FLASH(闪存)作为程序和数据的非易失性存储介质,其操作受FPEC(闪存编程和擦除控制器)管控,复位后默认处于锁定状态,需通过向FLASH_KEYR依次写入KEY1(0x45670123)和KEY2(0xCDEF89AB)解锁才能操作;编程时需使能FLASH_CR的PG位,按半字(16位)为单位在对齐地址写入,并通过FLASH_SR的BSY位判断操作完成;擦除分为页擦除(使能PER位、指定页地址到FLASH_AR并置STRT位)和全擦除(使能MER位置STRT位),均需等待BSY位清零后验证擦除结果(通常为全0xFFFF),操作完成后可通过设置FLASH_CR的LOCK位重新加锁,同时RDPRT键(0x000000A5)用于解除读保护,整体机制通过严格的寄存器控制和时序校验确保操作安全可靠。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐