STM32G474单片机开发入门(八)内部FLASH详解及读写实战

文章目录

一.概要

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主要特色

  1. ‌双BANK架构与灵活映射‌
    采用独立的BANK1和BANK2设计,各256KB(共512KB),通过SYSCFG_MEMRMP寄存器的FB_MODE位可动态映射地址空间(0x08000000或0x08040000)‌。
    默认BANK1映射至0x08000000,用户通常无需修改此配置‌。
  2. ‌高性能与可靠性‌
    每页2KB,每个BANK含256页,支持至少1万次擦写,且在85℃环境下可保证数据保存30年,优于同类产品。集成FPEC(Flash编程与擦除控制器),支持ISP、ICP、IAP等多种编程方式。
  3. ‌安全与保护机制‌
    选项字节存储芯片配置及主FLASH保护信息,系统存储器固化BootLoader(不可修改)‌。
    支持存储块保护功能,通过小信息模块(8字节)定义复位特性和保护策略。
  4. ‌扩展功能支持‌
    支持存储器重映射(如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,能降低整体产品成本。

相关推荐
BreezeJuvenile3 小时前
通用定时器_输入捕获介绍及案例实操
stm32·单片机·嵌入式硬件·输入捕获·通用定时器
时空自由民.4 小时前
无人机系统耗电,低功耗管理问题解决方法(chatgpt)
单片机·嵌入式硬件·无人机
时空自由民.4 小时前
无人机系统耗电,低功耗管理问题解决方法(腾讯元宝)
单片机·嵌入式硬件·无人机
清风6666665 小时前
基于单片机的双档输出数字直流电压源设计
单片机·mongodb·毕业设计·nosql·课程设计
牛马大师兄6 小时前
STM32独立看门狗IWDG与窗口看门狗WWDG知识梳理笔记
笔记·stm32·单片机·嵌入式硬件·嵌入式·看门狗
夜月yeyue6 小时前
STM32 Flash 访问加速器详解(ART Accelerator)
linux·单片机·嵌入式硬件·uboot·bootloard
A9better6 小时前
嵌入式开发学习日志37——stm32之USART
stm32·嵌入式硬件·学习
国科安芯10 小时前
ASP4644芯片低功耗设计思路解析
网络·单片机·嵌入式硬件·安全
充哥单片机设计10 小时前
【STM32项目开源】基于STM32的智能厨房火灾燃气监控
stm32·单片机·嵌入式硬件