一、FLASH介绍

  • 1、概念:非易失性存储:内部FLASH用于存储程序代码和常量数据,断电后数据不丢失。

  • 2、结构与容量

    • 型号相关:不同STM32系列(如F1/F4/H7)容量差异大,从16KB到2MB不等。

    • 分块管理:

      • F1系列:分页结构(1KB或2KB每页)。

      • F4系列:扇区结构(16KB、64KB、128KB等)。

      • H7系列:双Bank设计,支持并行操作。

  • 3、访问特性

    • 读写速度:零等待周期时可达CPU主频速度,高频需插入等待周期(如F1@72MHz需2周期)。

    • 操作单位:

      • 擦除:按页/扇区或整片(时间较长,需数毫秒到秒级)

      • 写入:按半字(16位)或字(32位),不支持按字节写入。

    • 解锁机制:通过FLASH_KEYR写入密钥解锁写保护。

  • 4、功能特性

    • 启动配置:通过BOOT引脚选择启动源(如FLASH、系统存储器)。

    • 保护机制:

      • 读保护(RDP):防止调试接口读取,触发后需全片擦除解除。

      • 写保护:可选扇区保护,避免误擦写。

    • 选项字节(Option Bytes):配置保护、复位策略等,需单独操作。

  • 5、编程方式

    • ICP(在线编程):通过SWD/JTAG接口烧录,使用ST-Link等工具。

    • IAP(应用内编程):

      • 自更新:程序运行时修改自身FLASH,需注意中断管理和代码重定位。

      • 双Bank应用:H7系列可在双Bank间切换,实现无中断升级。

对于存储器的其他介绍,可以详见这一篇。常见存储器介绍

二、STM32的内部FLASH简介

在这里插入图片描述

  • 存储程序代码:

在 STM32 芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中。

由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。

  • 存储关键信息

除了通过外部工具(如下载器)进行读写操作外,STM32芯片在运行时也能直接对内部FLASH进行读写操作。

如果内部FLASH在存储应用程序后仍有剩余空间,我们可以将其当作一个类似于外部SPI-FLASH的存储设备,用于保存程序运行时产生的数据。

这些数据在掉电后依然可以被保留,因此非常适合用于存储需要长期保存的关键信息。

  • 访问速度

由于内部FLASH的访问速度远高于外部SPI-FLASH,它在紧急状态下可以作为存储关键记录的首选区域。例如,在系统出现异常或需要快速保存重要数据时,内部FLASH能够快速响应并完成数据存储。

  • 安全性

为了保护程序代码和存储数据的安全性,STM32还提供了一系列内部FLASH的保护措施。例如,可以通过设置选项字节或使用加密算法来禁止读写内部FLASH中的内容。一些应用会在首次运行时计算加密信息并将其存储在特定区域,随后删除部分加密代码,从而防止程序被抄袭。

2.1 内部FLASH的构成

STM32 的内部 FLASH 包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小如下:
在这里插入图片描述

2.1.1 主存储器

一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、 512K FLASH 都是指这个区域的大小。

主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除

注意上表中的主存储器是本实验板使用的 STM32VET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。

关于 STM32 内部 FLASH 的容量类型可根据它的型号名获知,见下表:
在这里插入图片描述

2.1.2 系统存储区

系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、 USB以及 CAN 等 ISP 烧录功能。

STM32出厂前就已经使用了的空间。

2.1.3 选项字节

选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16字节。可以通过修改 FLASH 的选项控制寄存器修改。

相当于(FLASH类型的)寄存器。

三、对内部FLASH的写入过程

3.1 解锁

由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。

所以对 FLASH 写入数据前,需要先给它解锁。

解锁步骤:

  • 1、 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123;

  • 2、再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB;

在这里插入图片描述

/* FLASH Keys */
#define RDP_Key                  ((uint16_t)0x00A5)
#define FLASH_KEY1               ((uint32_t)0x45670123)
#define FLASH_KEY2               ((uint32_t)0xCDEF89AB)

/**
  * @brief  Unlocks the FLASH Program Erase Controller.
  * @note   This function can be used for all STM32F10x devices.
  *         - For STM32F10X_XL devices this function unlocks Bank1 and Bank2.
  *         - For all other devices it unlocks Bank1 and it is equivalent 
  *           to FLASH_UnlockBank1 function.. 
  * @param  None
  * @retval None
  */
void FLASH_Unlock(void)
{
  /* Authorize the FPEC of Bank1 Access */
  FLASH->KEYR = FLASH_KEY1;
  FLASH->KEYR = FLASH_KEY2;
}

3.2 擦除扇区

在写入新的数据前,需要先擦除存储区域, STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除 (批量擦除) 的指令,批量擦除指令仅针对主存储区。

擦除步骤:

  • 1、检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;

    BSY 0:不忙碌 1:忙碌;

  • 2、在 FLASH_CR 寄存器中,将“激活页擦除寄存器位 PER ”置 1;

  • 3、用 FLASH_AR 寄存器选择要擦除的页;

  • 4、将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;

  • 5、 等待 BSY 位被清零时,表示擦除完成;

  • 6、将“激活页擦除寄存器位 PER ”清 0;

/**
  * @brief  Erases a specified FLASH page.
  * @note   This function can be used for all STM32F10x devices.
  * @param  Page_Address: The page address to be erased.
  * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
  *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
  */
FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
{
  FLASH_Status status = FLASH_COMPLETE;
  /* Check the parameters */
  assert_param(IS_FLASH_ADDRESS(Page_Address));

  /* Wait for last operation to be completed */
  status = FLASH_WaitForLastOperation(EraseTimeout);	// 1、检测BSY位
  
  if(status == FLASH_COMPLETE)
  { 
    /* if the previous operation is completed, proceed to erase the page */
    FLASH->CR|= CR_PER_Set;		// 2、PER置1
    FLASH->AR = Page_Address; 	// 3、选择要擦除的页
    FLASH->CR|= CR_STRT_Set;	// 4、STRT置1
    
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(EraseTimeout);		// 5、等待清除完成
    
    /* Disable the PER Bit */
    FLASH->CR &= CR_PER_Reset;	// 6、PER清0
  }

  /* Return the Erase Status */
  return status;
}

在这里插入图片描述

3.3 写入数据

擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器。

写入步骤:

  • 1、检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;

  • 2、将 FLASH_CR 寄存器中的“激活编程寄存器位 PG”置 1;

  • 3、向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;

  • 4、等待 BSY 位被清零时,表示写入完成;

  • 5、将 FLASH_CR 寄存器中的“激活编程寄存器位 PG”清0;

/**
  * @brief  Programs a word at a specified address.
  * @note   This function can be used for all STM32F10x devices.
  * @param  Address: specifies the address to be programmed.
  * @param  Data: specifies the data to be programmed.
  * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
  *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT. 
  */
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
  FLASH_Status status = FLASH_COMPLETE;
  __IO uint32_t tmp = 0;

  /* Check the parameters */
  assert_param(IS_FLASH_ADDRESS(Address));

  /* Wait for last operation to be completed */
  status = FLASH_WaitForLastOperation(ProgramTimeout);		// 1、检测BSY位
  
  if(status == FLASH_COMPLETE)
  {
    /* if the previous operation is completed, proceed to program the new first 
    half word */
    FLASH->CR |= CR_PG_Set;		// 2、PG置1
  
    *(__IO uint16_t*)Address = (uint16_t)Data;		// 3、写入数据
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(ProgramTimeout);	// 4、等待写入完成
 
    if(status == FLASH_COMPLETE)
    {
      /* if the previous operation is completed, proceed to program the new second 
      half word */
      tmp = Address + 2;

      *(__IO uint16_t*) tmp = Data >> 16;
    
      /* Wait for last operation to be completed */
      status = FLASH_WaitForLastOperation(ProgramTimeout);
        
      /* Disable the PG Bit */
      FLASH->CR &= CR_PG_Reset;			// 5.PG清0
    }
    else
    {
      /* Disable the PG Bit */
      FLASH->CR &= CR_PG_Reset;
    }
  }         
   
  /* Return the Program Status */
  return status;
}

在这里插入图片描述

四、查看工程的空间分布

由于内部FLASH本身存储了程序代码,若非有意删除某段代码,一般不应修改程序空间的内容。因此,在使用内部FLASH存储其他数据之前,需先了解哪些空间已写入程序代码。已存储程序代码的扇区不应进行任何修改。

通过查询应用程序编译时产生的“*.map”后缀文件,可以了解程序存储到了哪些区域

在这里插入图片描述

用Total ROM Size(十进制转十六进制+Base)算出整个程序占用空间。

五、实验:读写内部FLASH

// flash.c文件
#include "flash.h"

#define FLASH_BASE_ADDR		0x08000000
#define FLASH_SIZE			(1024*512)

// 1、解锁
// 2、擦除指定的页(查看工程分部,以防擦除程序本身)
// 3、写入数据
// 4、读出数据校验

void Flash_Write(uint32_t Addr, uint32_t data)
{
	FLASH_Unlock();
	
	FLASH_ErasePage(Addr);		// FLASH_ErasePage(0x80000000 + 2*1024*page);	擦除第page页
	
	FLASH_ProgramWord(Addr, data);
	
	FLASH_Lock();
}

uint32_t Flash_Read(uint32_t Addr)
{
	if(Addr < FLASH_BASE_ADDR || Addr >= FLASH_BASE_ADDR + FLASH_SIZE)
	{
			// 地址超出范围,返回错误值
			return 0x5aa5a5a5;
	}
	
	uint32_t *p = (uint32_t *)Addr;
	
	return *p;
}
// flash.h文件
#ifndef __FLASH_H
#define __FLASH_H

#include "stm32f10x.h"

void Flash_Write(uint32_t Addr, uint32_t data);
uint32_t Flash_Read(uint32_t Addr);
	
#endif /* __FLASH_H */								
// main.c文件
#include "stm32f10x.h"
#include "usart.h"
#include "flash.h"

int main(void)
{
	uint32_t temp = 0;
	
	USART_Config();
	
	Flash_Write(0x08000000+2*1024*5, 0x12345678);
	
	printf("\r\n 写入完成\r\n");
	
                                
	temp = Flash_Read(0x08000000+2*1024*5);
	
	printf("\r\n 写入的值为 0x%x :\r\n" , temp);
	
	while(1)
	{

	}
	
}

在这里插入图片描述

  • 在 Flash_Read 函数中,对地址进行检测是十分必要的。如果地址传错,而函数中没有这种检测机制,那么就需要花费大量精力去排查这种失误。
Logo

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

更多推荐