文章目录
一.概要
STM32G474RET6是一款强大而灵活的微控制器,它的片内Flash存储器可以用来存储有关代码和数据,在实际应用中,我们也需要对这个存储器进行读写操作。
STM32的FLASH主存储块按页组织,有的产品每页1KB,有的能到2KB,页面典型的用途就是用于按页擦除FLASH,STM32G474RET6总共有512 KB空间,每个FLASH页是16K。
本文介绍了STM32G474RET6的FLASH存储空间,FLASH数据的读写,以及读写保护等。
二.内部FLASH地址空间排布
根据用途,STM32片内的FLASH分成两部分:主存储块、信息块。
主存储块:用于存储程序,我们写的程序一般存储在这里,用户还可以存储数据。
信息块又分成两部分:系统存储器、OTP、选项字节。
系统存储器存储用于存放在系统存储器自举模式下的启动程序(BootLoader),当使用ISP方式加载程序时,就是由这个程序执行。这个区域由芯片厂写入BootLoader,然后锁死,用户是无法改变这个区域的。
OTP(One Time Program)区域,指的是只能写入一次的存储区域,容量为1KB字节,写入后数据就无法再更改,OTP常用于存储应用程序的加密密钥。
选项字节存储芯片的配置信息及对主存储块的保护信息,主要有写保护字节,读保护字节等。
STM32G474RET6产品主存储块512KB,分成两个Bank, 每个Bank为256K,每个Bank有128页,每页大小为2K,一共有7个扇区。系统存储器56KB(ISP的启动代码),存储空间如下图:
三.内部FLASH主要特色
- 双BANK架构与灵活映射
采用独立的BANK1和BANK2设计,各256KB(共512KB),通过SYSCFG_MEMRMP寄存器的FB_MODE位可动态映射地址空间(0x08000000或0x08040000)。
默认BANK1映射至0x08000000,用户通常无需修改此配置。 - 高性能与可靠性
每页2KB,每个BANK含256页,支持至少1万次擦写,且在85℃环境下可保证数据保存30年,优于同类产品。集成FPEC(Flash编程与擦除控制器),支持ISP、ICP、IAP等多种编程方式。 - 安全与保护机制
选项字节存储芯片配置及主FLASH保护信息,系统存储器固化BootLoader(不可修改)。
支持存储块保护功能,通过小信息模块(8字节)定义复位特性和保护策略。 - 扩展功能支持
支持存储器重映射(如SRAM、FSMC、QUADSPI等),通过SYSCFG_MEMRMP寄存器的MEM_MODE位配置,优化外设访问性能。
该芯片的FLASH设计兼顾灵活性、耐久性与安全性,适合需要复杂算法或实时数据处理的场景。
四.内部FLASH读写操作
1.FLASH数据读取
内置闪存模块可以在通用地址空间直接寻址,任何32位数据的读操作都能访问闪存模块的内容并得到相应的数据。
以下是一个示例代码,展示了如何在C语言中读取STM32G474RET6的内部Flash数据
// 假设我们要读取的数据在Flash地址0x8000000的位置
#define FLASH_ADDRESS 0x8000000
uint32_t data = (uint32_t)FLASH_ADDRESS;
2.FLASH数据擦除
STM32G474RET6的FLASH在写入数据前确实需要先进行擦除操作。这是因为FLASH存储器的特性决定的,与其它FLASH存储器一样,STM32G474RET6的FLASH也是按页进行管理的,FLASH可以按扇区擦除,也可以整片擦除。
1.解锁Flash控制寄存器
通过调用HAL_FLASH_Unlock()函数解除写保护,允许后续擦除操作。
2.清除挂起标志
使用__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS)清除可能存在的错误标志,避免干扰擦除过程。
3.配置擦除参数
初始化FLASH_EraseInitTypeDef结构体,设置擦除类型为页擦除(FLASH_TYPEERASE_PAGES),并指定目标页地址(Page)和擦除页数(NbPages)。STM32G474的Flash每页大小为2KB,需根据数据手册确认具体页号范围。
4.执行擦除操作
调用HAL_FLASHEx_Erase(&eraseConfig, &SectorError)函数,传入配置参数和错误状态变量。需检查返回值和SectorError以确认操作是否成功。
5.重新锁定Flash
操作完成后通过HAL_FLASH_Lock()重新启用写保护,防止意外修改。
3.FLASH数据写入
1.Flash初始化与解锁
调用HAL_FLASH_Unlock()解除写保护,使能编程操作。需注意操作前需关闭全局中断以避免干扰。
通过__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS)清除可能的错误标志。
2.写入参数配置
数据需按64位(8字节)对齐写入,最小写入长度为8字节,不足部分需填充。
目标地址需位于有效Flash区域(如BANK1的0x08000000~ 0x0803FFFF或BANK2的0x08040000~0x0807FFFF)。
4.数据写入执行
使用HAL_FLASH_Program()函数,指定编程类型(如FLASH_TYPEPROGRAM_DOUBLEWORD)和地址数据。示例代码片段:
c
uint64_t data = 0x123456789ABCDEF0; // 64位数据
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, targetAddress, data);
5.写入后处理
校验数据是否成功写入,可通过读取目标地址并与原始数据对比。调用HAL_FLASH_Lock()重新启用写保护,确保Flash安全性。
五.内部FLASH的各种保护
1.写保护
STM32G474RET6单片机的FLASH写保护机制主要通过选项字节(Option Bytes)和硬件寄存器实现,具体可分为以下核心要点:
1.写保护原理
保护单位:以4KB页为最小单位进行写保护,通过WRP0~WRP3共4个选项字节控制。每个选项字节的每一位对应一个4KB区域的保护状态(0为保护,1为解除)。保护效果:被保护的页禁止任何擦除和写入操作,但读操作不受影响(读权限由读保护单独控制)。
2.选项字节配置
关键寄存器:通过FLASH_CR寄存器操作选项字节,需先解锁(写入特定序列0x45670123和0xCDEF89AB)。
2.读保护
将Flash设置为读保护的目的,是为了防止其他人通过STLINK等仿真器,将Flash中的程序读取出来(设想一下,你辛辛苦苦研发的产品,别人通过仿真器将程序读取出来,再copy一下产品的硬件,就可以生产),所以可以通过将Flash设置为读保护来保护自己的程序。
可对 Flash 中的用户区域实施读保护,以防不受信任的代码读取其中的数据。读保护分三个级别,具体定义如下:
级别 0:无读保护,将 0xAA 写入读保护选项字节 (RDP) 时,读保护级别即设为 0。
级别 1:存储器读保护,将任意值(分别用于设置级别 0 和级别 2 的 0xAA
和 0xCC 除外)写入 RDP 选项字节时,即激活读保护级别 1。
级别 2:禁止调试/芯片读保护,将 0xCC 写入 RDP 选项字节时,即激活读保护级别 2。
六.FLASH读写例程
1.硬件准备
STLINK接STM32G474RET6开发板,STLINK接电脑USB口。
2.创建工程
如下图所示,打开STM32CubeMX软件,新建工程。
如下图所示,Part Number处输入STM32G474RE,再双击就创建新的工程。
如下图所示,配置下载口引脚,PA13为SWD的SWDIO脚,PA14为SWD的SWCLK脚。
如下图所示,配置系统主频170Mhz,使用内部16MHZ晶振。
如下图所示,配置工程文件名,保存路径,KEIL5工程输出方式。
如下图所示,生成工程。
如下图所示,用Keil5打开工程。
添加代码,FLASH解锁,FLASH写入,FLASH数据读取。
主要代码:
c
#define FLASH_USER_START_ADDR ADDR_FLASH_PAGE_4 /* 从0x08002000地址开始存数据#define ADDR_FLASH_PAGE_4 ((uint32_t)0x08002000)*/
#define FLASH_USER_END_ADDR (ADDR_FLASH_PAGE_5 - 1) /*地址存放结束地址为0x080027FF */
#define DATA_32 ((uint32_t)0x12345678)//定义32bit数据内容
#define DATA_64 ((uint64_t)0x1234567812345678)//定义64bit数据内容
uint32_t FirstPage = 0, NbOfPages = 0, BankNumber = 0;//FLASH读写操作变量
uint32_t Address = 0, PageError = 0;
__IO uint32_t MemoryProgramStatus = 0;
__IO uint32_t data32 = 0;
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();//SysTick配置成1ms中断
/* USER CODE BEGIN Init */
/* Configure the system clock to 170 MHz */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();//内部16MHZ晶振,170MHZ系统主频
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
HAL_FLASH_Unlock();//FLASH解锁
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);//清除FLASH所有错误标志
FirstPage = GetPage(FLASH_USER_START_ADDR);//获取需要写FLASH地址的起始地址的页号,页号是4
NbOfPages = GetPage(FLASH_USER_END_ADDR) - FirstPage + 1;//获取页数,这边是1页
BankNumber = GetBank(FLASH_USER_START_ADDR);//获取Bank,这边是BANK1
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.Banks = BankNumber;
EraseInitStruct.Page = FirstPage;
EraseInitStruct.NbPages = NbOfPages;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)//擦除根据定义的FLASH起始地址到结束地址的空间,空间的内容都变成0xff
{
}
Address = FLASH_USER_START_ADDR;
while (Address < FLASH_USER_END_ADDR)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, DATA_64) == HAL_OK)//根据定义的FLASH起始地址到结束地址,双字写入,一次写入8字节,内容是0x1234567812345678
{
Address = Address + 8;//地址加8
}
else
{
}
}
HAL_FLASH_Lock();//FLASH上锁
Address = FLASH_USER_START_ADDR;
MemoryProgramStatus = 0x0;
while (Address < FLASH_USER_END_ADDR)
{
data32 = *(__IO uint32_t *)Address;//4字节读取从定义的FLASH起始地址到结束地址的数据
if (data32 != DATA_32)//读出的内容与写入的内容比较
{
MemoryProgramStatus++;//内容不一致,变量加1
}
Address = Address + 4;//地址加4
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.实验结果
在Keil5下进入调试模式,全速运行,查看Memory内容,输入0x08002000地址,可以看到内容已经变成我们写入的数值。
七.小结
在STM32的开发过程中,保存用户数据、实现程序的自我更新等应用场景都离不开对FLASH读写操作,用好内部FLASH,能降低整体产品成本。