STM32专题之内部FLASH详解

目录

一、小叙内部Flash

二、庖丁解牛之STM32内部Flash的构成

三、寻觅将应用程序写入到内部Flash的底层逻辑

四、熟稔于心工程的空间分布

五、实践出真知之代码实战

[5.1 库函数是工具](#5.1 库函数是工具)

[5.2 如何使用内部Flash存储数据实验](#5.2 如何使用内部Flash存储数据实验)


接下来进入正题:

一、小叙内部Flash

STM32芯片内部有一个Flash,可以通过下载器烧录进去程序,并且由于Flash本身的特性是掉电不丢失,所以芯片复位的话,内核就从该内部Flash加载代码并运行。

这里需要注意:除了使用下载器读取Flash内部数据,在运行期间,芯片也能对内部Flash进行读写,所以,除了本身的应用程序,如果还有空余空间,此空间还可以用来保存一些需要掉电不丢失的数据(类似于SPI Flash保存的数据)。访问内部的Flash速度要比访问SPI Flash要快的多,所以紧急情况下还可以保存一些关键记录。有时为了防止程序被更改还会禁止读写Flash的内容,可能是第一次运行时计算加密的信息存储到某些区域,然后再删除自身的加密代码,不留痕迹,这将会涉及到对Flash的操作,那么这就会引出下面的内容:

二、庖丁解牛之STM32内部Flash的构成

四个区域即主存储区、系统存储区、OTP区域以及选项字节区域。

主存储区:我们所说的内部Flash大小1MB就是指这个区域。是用来存储应用程序/代码的。由图可知,主存储区分为2部分,块1和块2,共2MB,我们的STM32F407ZGT6主存储区大小1MB,所以只有块1。( 双块存储格式(由于我们的芯片不支持此功能,目前这里只提一下,需要详细了解可参考教程)

系统存储区:用户不能访问此区域,是一段芯片出厂固化的启动代码 ,用来实现串口、USB、以及CAN等ISP烧录功能

OTP区域:One Time Program,一次写入的存储区域,512字节,写入后不能更改,常用于存储应用程序的加密密钥。

选项字节:配置Flash的读写保护、电源管理中的BOR级别、软件/硬件看门狗等功能,共32字节,

通过修改FLASH的选项控制寄存器修改。

三、寻觅将应用程序写入到内部Flash的底层逻辑

1.写入前先对Flash解锁,2.在擦除扇区和写入数据时,还要先设置最大操作位数(通过配置FLASH_CR寄存器的PSIZE位,这是由于电源电压的影响),3.写入数据前先擦除存储区域,也就是擦除扇区,4.擦除完毕就可以写入数据,这里需要注意的是写入数据时不仅是使用指针对地址赋值,赋值前还需要配置一系列的寄存器(这里卖个关子,具体配置哪些见下文)。

为什么要解锁?被上锁了,**为什么上锁?**这是由于FLASH里面存储的是应用程序,非常重要,防止误操作被修改,在芯片复位后默认会给FLASH上锁,上锁后是不允许设置FLASH的控制寄存器也不能修改FLASH内的内容。

但是我们需要写入内容到FLASH中就要先解锁,怎么解锁呢 ?其实很简单的,只需要在FLASH的秘钥寄存器FLASH_KEYR中依次写入KEY1=0x45670123,KEY2 = 0xCDEF89AB即可。

为什么要设置最大操作位数? 通过配置FLASH_CR寄存器的PSIZE位来设置电源电压,由于电源电压会影响最大操作位数,进而直接会影响到擦除和写入的速度大部分使用32位的宽度 (量产时可能就是64位的宽度,此外还需要vpp引脚外加8-9v的电压源,且通电时间<1h,否则FLASH将损坏)。

接下来就开始扇区擦除,扇区擦除指令是STM32提供好的,其中批量擦除指令仅针对主存储区。扇区擦除分3步

1.检测忙不?即查看FLASH_SR的BSY位为0,则当前未执行任何FLASH操作。

2.激活扇区擦除功能、选择是哪个扇区并且执行开始擦除。即将FLASH_CR中的SER位置1,并设置SNB位为扇区编号是哪个(也就是哪个扇区要擦除),最后START位置1,将开始擦除。

3、检测忙不?即查看FLASH_SR的BSY位为0,则表示擦除完成。

擦除完毕,就可以写入数据了。写入数据也分3步走:

1.检测忙不?即查看FLASH_SR的BSY位为0,则当前未执行任何FLASH操作。

2.激活FLASH编程后对存储地址执行写入操作。即将FLASH_CR的PG位置1表示激活FLASH编程,然后对对应的存储器地址(主存储区或OTP区)进行写入数据操作。

3.检测忙不?即查看FLASH_SR的BSY位为0,则表示写入完成。

四、熟稔于心工程的空间分布

在使用内部Flash存储其它数据之前一定要了解空间分布,即内部Flash哪段空间写入了程序代码。

怎么查看?两个方法:1.双击工程的主文件夹 2.Listing文件夹中找Template.map文件

我们要找的内容在.map文件的后面部分(是一段以Mermory Map of the image开头的记录):

复制代码
==============================================================================

Memory Map of the image

  Image Entry point : 0x08000189

  Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00000e28, Max: 0x00100000, ABSOLUTE)

    Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00000e14, Max: 0x00100000, ABSOLUTE)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x08000000   0x00000188   Data   RO            3    RESET               startup_stm32f40xx.o
    0x08000188   0x00000000   Code   RO         4960  * .ARM.Collect$$$$00000000  mc_w.l(entry.o)
    0x08000188   0x00000004   Code   RO         5224    .ARM.Collect$$$$00000001  mc_w.l(entry2.o)
    0x0800018c   0x00000004   Code   RO         5227    .ARM.Collect$$$$00000004  mc_w.l(entry5.o)
    0x08000190   0x00000000   Code   RO         5229    .ARM.Collect$$$$00000008  mc_w.l(entry7b.o)
    0x08000190   0x00000000   Code   RO         5231    .ARM.Collect$$$$0000000A  mc_w.l(entry8b.o)
    0x08000190   0x00000008   Code   RO         5232    .ARM.Collect$$$$0000000B  mc_w.l(entry9a.o)
    0x08000198   0x00000000   Code   RO         5234    .ARM.Collect$$$$0000000D  mc_w.l(entry10a.o)
    0x08000198   0x00000000   Code   RO         5236    .ARM.Collect$$$$0000000F  mc_w.l(entry11a.o)
    0x08000198   0x00000004   Code   RO         5225    .ARM.Collect$$$$00002712  mc_w.l(entry2.o)
    0x0800019c   0x00000024   Code   RO            4    .text               startup_stm32f40xx.o
    0x080001c0   0x00000062   Code   RO         5241    .text               mc_w.l(uldiv.o)
    0x08000222   0x00000002   PAD
    0x08000224   0x00000024   Code   RO         5254    .text               mc_w.l(init.o)
    0x08000248   0x0000001e   Code   RO         5256    .text               mc_w.l(llshl.o)
    0x08000266   0x00000020   Code   RO         5258    .text               mc_w.l(llushr.o)
    0x08000286   0x00000002   Code   RO         4796    i.BusFault_Handler  stm32f4xx_it.o
    0x08000288   0x00000002   Code   RO         4797    i.DebugMon_Handler  stm32f4xx_it.o
    0x0800028a   0x00000002   PAD
    0x0800028c   0x000000a8   Code   RO         4877    i.Debug_USART_Config  bsp_debug_usart.o
    0x08000334   0x00000070   Code   RO         1415    i.FLASH_EraseSector  stm32f4xx_flash.o
    0x080003a4   0x0000003c   Code   RO         1417    i.FLASH_GetStatus   stm32f4xx_flash.o
    0x080003e0   0x00000010   Code   RO         1421    i.FLASH_Lock        stm32f4xx_flash.o
    0x080003f0   0x00000038   Code   RO         1442    i.FLASH_ProgramByte  stm32f4xx_flash.o
    0x08000428   0x0000003c   Code   RO         1444    i.FLASH_ProgramHalfWord  stm32f4xx_flash.o
    0x08000464   0x00000024   Code   RO         1447    i.FLASH_Unlock      stm32f4xx_flash.o
    0x08000488   0x00000022   Code   RO         1448    i.FLASH_WaitForLastOperation  stm32f4xx_flash.o
    0x080004aa   0x0000007c   Code   RO         1857    i.GPIO_Init         stm32f4xx_gpio.o
    0x08000526   0x00000020   Code   RO         1858    i.GPIO_PinAFConfig  stm32f4xx_gpio.o
    0x08000546   0x00000002   Code   RO         4798    i.HardFault_Handler  stm32f4xx_it.o
    0x08000548   0x00000002   Code   RO         4799    i.MemManage_Handler  stm32f4xx_it.o
    0x0800054a   0x00000002   Code   RO         4800    i.NMI_Handler       stm32f4xx_it.o
    0x0800054c   0x00000026   Code   RO         4878    i.NVIC_Configuration  bsp_debug_usart.o
    0x08000572   0x00000002   PAD
    0x08000574   0x00000068   Code   RO          155    i.NVIC_Init         misc.o
    0x080005dc   0x00000014   Code   RO          156    i.NVIC_PriorityGroupConfig  misc.o
    0x080005f0   0x00000002   Code   RO         4801    i.PendSV_Handler    stm32f4xx_it.o
    0x080005f2   0x00000002   PAD
    0x080005f4   0x0000001c   Code   RO         2561    i.RCC_AHB1PeriphClockCmd  stm32f4xx_rcc.o
    0x08000610   0x0000001c   Code   RO         2573    i.RCC_APB2PeriphClockCmd  stm32f4xx_rcc.o
    0x0800062c   0x000000a4   Code   RO         2582    i.RCC_GetClocksFreq  stm32f4xx_rcc.o
    0x080006d0   0x00000002   Code   RO         4802    i.SVC_Handler       stm32f4xx_it.o
    0x080006d2   0x00000002   PAD
    0x080006d4   0x000000b8   Code   RO           13    i.SetSysClock       system_stm32f4xx.o
    0x0800078c   0x00000002   Code   RO         4803    i.SysTick_Handler   stm32f4xx_it.o
    0x0800078e   0x00000002   PAD
    0x08000790   0x0000005c   Code   RO           15    i.SystemInit        system_stm32f4xx.o
    0x080007ec   0x0000002c   Code   RO         4804    i.USART1_IRQHandler  stm32f4xx_it.o
    0x08000818   0x00000018   Code   RO         4511    i.USART_Cmd         stm32f4xx_usart.o
    0x08000830   0x0000000e   Code   RO         4514    i.USART_GetFlagStatus  stm32f4xx_usart.o
    0x0800083e   0x00000040   Code   RO         4515    i.USART_GetITStatus  stm32f4xx_usart.o
    0x0800087e   0x00000036   Code   RO         4517    i.USART_ITConfig    stm32f4xx_usart.o
    0x080008b4   0x000000bc   Code   RO         4518    i.USART_Init        stm32f4xx_usart.o
    0x08000970   0x00000008   Code   RO         4525    i.USART_ReceiveData  stm32f4xx_usart.o
    0x08000978   0x00000008   Code   RO         4528    i.USART_SendData    stm32f4xx_usart.o
    0x08000980   0x00000002   Code   RO         4805    i.UsageFault_Handler  stm32f4xx_it.o
    0x08000982   0x00000002   PAD
    0x08000984   0x00000020   Code   RO         5096    i.__0printf$5       mc_w.l(printf5.o)
    0x080009a4   0x0000000e   Code   RO         5266    i.__scatterload_copy  mc_w.l(handlers.o)
    0x080009b2   0x00000002   Code   RO         5267    i.__scatterload_null  mc_w.l(handlers.o)
    0x080009b4   0x0000000e   Code   RO         5268    i.__scatterload_zeroinit  mc_w.l(handlers.o)
    0x080009c2   0x00000002   PAD
    0x080009c4   0x000002c0   Code   RO         5103    i._printf_core      mc_w.l(printf5.o)
    0x08000c84   0x00000024   Code   RO         4883    i.fputc             bsp_debug_usart.o
    0x08000ca8   0x0000011c   Code   RO         4936    i.inter_flash_test  bsp_inter_flash.o
    0x08000dc4   0x00000030   Code   RO         4765    i.main              main.o
    0x08000df4   0x00000020   Data   RO         5264    Region$$Table       anon$$obj.o

这一段就是该工程的ROM存储器分布映像,在STM32中,ROM区域的内容就是存储到内部Flash的代码。

其中Load Region LR_IROM1指的的加载空间,Execution Region ER_IROM1指的是执行空间。

**代码(包含程序和数据)在芯片中时如何运行的?**在芯片刚上电时,会加载程序和数据,例如他会从程序的存储区域加载到执行区域,还把一些已初始化的全局变量从ROM复制到RAM中,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。

从上面的代码可以看出加载空间和执行空间的基地址都是0x0800 0000,对应的正是内部Flash的起始地址,也就是说STM32的内部Flash就直接是执行空间,他们大小分别是0x00000e28和0x00000e14,执行空间ROM比较小的原因是因为部分RW-Data类型的变量被COPY到RAM空间了。他们的最大空间都是0x00100000,等于1MB,也就是内部Flash的最大空间。

计算程序占用的空间时,需要使用加载区域的大小计算 。这里例子使用的内部Flash区域就是0x0800 0000到(0x0800 0000 + 0x0000 0e28)地址的空间区域。我们经过对比STM32内部Flash的构成那个表可以看出仅使用扇区0就可以满足本程序,那么扇区1后面的空间就可以应用其它用途了。

加载空间和执行空间描述之后是一个ROM详细地址分布表,它列出了各段的基址、大小,Type是该段的类型CODE表示代码,DATA表示数据,PAD表示段之间的填充区域(为了地址对齐)。

五、实践出真知之代码实战

5.1 库函数是工具

1、FLASH解锁、上锁函数:

cpp 复制代码
//stm32f4xx_flash.c
void FLASH_Unlock(void)
{
  if((FLASH->CR & FLASH_CR_LOCK) != RESET)
  {
    /* Authorize the FLASH Registers access */
    FLASH->KEYR = FLASH_KEY1;
    FLASH->KEYR = FLASH_KEY2;
  }  
}
cpp 复制代码
//stm32f4xx_flash.c
void FLASH_Lock(void)
{
  /* Set the LOCK Bit to lock the FLASH Registers access */
  FLASH->CR |= FLASH_CR_LOCK;
}

这里复习下&和|的用法:

任何数 & 1 = 任何数;任何数 & 0 = 0。任何数 | 1 = 1,任何数 | 0 = 任何数。

2、设置操作位数和擦除扇区

cpp 复制代码
//stm32f4xx_flash.c
FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange)
{
  uint32_t tmp_psize = 0x0;
  FLASH_Status status = FLASH_COMPLETE;

  /* Check the parameters */
  assert_param(IS_FLASH_SECTOR(FLASH_Sector));
  assert_param(IS_VOLTAGERANGE(VoltageRange));
  
  if(VoltageRange == VoltageRange_1)
  {
     tmp_psize = FLASH_PSIZE_BYTE;
  }
  else if(VoltageRange == VoltageRange_2)
  {
    tmp_psize = FLASH_PSIZE_HALF_WORD;
  }
  else if(VoltageRange == VoltageRange_3)
  {
    tmp_psize = FLASH_PSIZE_WORD;
  }
  else
  {
    tmp_psize = FLASH_PSIZE_DOUBLE_WORD;
  }
  /* Wait for last operation to be completed 即等待上一步操作完成*/
  status = FLASH_WaitForLastOperation();
  
  if(status == FLASH_COMPLETE)
  { 
    /* if the previous operation is completed, proceed to erase the sector */
    FLASH->CR &= CR_PSIZE_MASK;//CR的第9位和第10位清0,即PSIZE位清0
    FLASH->CR |= tmp_psize;
    FLASH->CR &= SECTOR_MASK;
    FLASH->CR |= FLASH_CR_SER | FLASH_Sector;//激活扇区并选中某个扇区
    FLASH->CR |= FLASH_CR_STRT;
    
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation();
    
    /* if the erase operation is completed, disable the SER Bit */
    FLASH->CR &= (~FLASH_CR_SER);
    FLASH->CR &= SECTOR_MASK; 
  }
  /* Return the Erase Status */
  return status;
}

3、写入数据

cpp 复制代码
//stm32f4xx_flash.c
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
  FLASH_Status status = FLASH_COMPLETE;

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

  /* Wait for last operation to be completed */
  status = FLASH_WaitForLastOperation();
  
  if(status == FLASH_COMPLETE)
  {
    /* if the previous operation is completed, proceed to program the new data */
    FLASH->CR &= CR_PSIZE_MASK;
    FLASH->CR |= FLASH_PSIZE_WORD;
    FLASH->CR |= FLASH_CR_PG;
  
    *(__IO uint32_t*)Address = Data;
        
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation();

    /* if the program operation is completed, disable the PG Bit */
    FLASH->CR &= (~FLASH_CR_PG);
  } 
  /* Return the Program Status */
  return status;
}

5.2 如何使用内部Flash存储数据实验

编程思路:

1、解锁 2、找出空闲区,擦除目标区 3、进行读写测试

为了方便的向FLASH_CR的SNB位写入要擦除的扇区号,我们自定义一个GetSector函数,用来实现根据内部的FLASH地址找出所在的扇区,并返回该扇区对应的SNB位寄存器的值。

cpp 复制代码
static uint32_t GetSector(uint32_t Address)
{
	uint32_t sector = 0;
	if((Address < ADDR_FLASH_SECTOR_1) && (Address > ADDR_FLASH_SECTOR_0)){
		sector = FLASH_Sector_0;
	} else if((Address < ADDR_FLASH_SECTOR_2) && (Address > ADDR_FLASH_SECTOR_1)){
		sector = FLASH_Sector_1;
	} else if((Address < ADDR_FLASH_SECTOR_3) && (Address > ADDR_FLASH_SECTOR_2)){
		sector = FLASH_Sector_2;
	} else if((Address < ADDR_FLASH_SECTOR_4) && (Address > ADDR_FLASH_SECTOR_3)){
		sector = FLASH_Sector_3;
	} else if((Address < ADDR_FLASH_SECTOR_5) && (Address > ADDR_FLASH_SECTOR_4)){
		sector = FLASH_Sector_4;
	} else if((Address < ADDR_FLASH_SECTOR_6) && (Address > ADDR_FLASH_SECTOR_5)){
		sector = FLASH_Sector_5;
	} else if((Address < ADDR_FLASH_SECTOR_7) && (Address > ADDR_FLASH_SECTOR_6)){
		sector = FLASH_Sector_6;
	} else if((Address < ADDR_FLASH_SECTOR_8) && (Address > ADDR_FLASH_SECTOR_7)){
		sector = FLASH_Sector_7;
	} else if((Address < ADDR_FLASH_SECTOR_9) && (Address > ADDR_FLASH_SECTOR_8)){
		sector = FLASH_Sector_8;
	} else if((Address < ADDR_FLASH_SECTOR_10) && (Address > ADDR_FLASH_SECTOR_9)){
		sector = FLASH_Sector_9;
	} else if((Address < ADDR_FLASH_SECTOR_11) && (Address > ADDR_FLASH_SECTOR_10)){
		sector = FLASH_Sector_10;
	} else {
		sector = FLASH_Sector_11;
	} 
	return sector;
}

编写测试代码--思路如下:

1、解锁

2、清除标志位

3、擦除扇区

4、写值(以字为单位)

5、上锁

6、校验

cpp 复制代码
#define FLASH_USER_START_ADDR ADDR_FLASH_SECTOR_8
#define FLASH_USER_END_ADDR   ADDR_FLASH_SECTOR_11
#define DATA_32				  ((uint32_t) 0x00000000)

int InternalFlash_Test(void)
{
	uint32_t uwStartSector = 0;//起始扇区
	uint32_t uwEndSector = 0;//结束扇区
	
	uint32_t uwAddress = 0;
	uint32_t uwSectorCounter = 0;
	
	__IO uint32_t uwData32 = 0;
	__IO uint32_t uwMemoryProgramStatus = 0;
	
	/* FLASH 解锁 */
	FLASH_Unlock();
	
	/* 清除各种FLASH的标志位 */
	FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | 
		FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);
	
	uwStartSector = GetSector(FLASH_USER_START_ADDR);
	uwEndSector = GetSector(FLASH_USER_END_ADDR);
	
	/* 开始擦除操作 */
	uwSectorCounter = uwStartSector;
	while(uwSectorCounter <= uwEndSector){
		if(FLASH_EraseSector(uwSectorCounter, VoltageRange_3) != FLASH_COMPLETE){
			return -1;
		}
		/* 计数器指向下一个扇区 */
		uwSectorCounter += 8;		
	}
	
	/* 以字为单位写入数据 */
	uwAddress = FLASH_USER_START_ADDR;
	while(uwAddress < FLASH_USER_END_ADDR){
		if(FLASH_ProgramWord(uwAddress, DATA_32) = FLASH_COMPLETE){
			uwAddress += 4; //表示向后偏移四个字节
		} else {
			return -1;
		}
	}
	
	/* FLASH 上锁 */
	FLASH_Lock();
	
	/* 校验 */
	uwAddress = FLASH_USER_START_ADDR;
	uwMemoryProgramStatus = 0;	
	while(uwAddress < FLASH_USER_END_ADDR) {
		uwData32 = *(__IO uint32_t *)uwAddress;
		if(uwData32 != DATA_32) {
			uwMemoryProgramStatus++;
		}
		uwAddress += 4;
	}
	/* 数据校验 */
	if(uwMemoryProgramStatus) {
		return -1;
	} else {
		return 0;
	}
}

最后main函数执行测试函数:

cpp 复制代码
#include "stm32f4xx.h"
#include "./usart/bsp_debug_usart.h"
#include "./led/bsp_led.h"
#include "./interFLASH/bsp_inter_flash.h"

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{	
	/*初始化USART 配置模式为 115200 8-N-1 */
	Debug_USART_Config();
	LED_GPIO_Config();
	LED_BLUE;
	
	printf("下面将开始进行读写内部FLASH实验\r\n");
	if(InternalFlash_Test() == 0) {
		LED_GREEN;
		printf("读写内部FLASH实验测试成功!\r\n");
	} else {
		printf("读写内部FLASH实验测试失败!\r\nd");
		LED_RED;
	}
	
	while(1)
	{
		
	}	
}

详细的代码已共享到附件。

相关推荐
xiaoxiangsiyan3 小时前
全网IPv6规模化改造实战指南
运维·网络·笔记·云原生·自动化
Wang's Blog4 小时前
PostgreSQL笔记59:行列级权限机制与数据脱敏实践
数据库·笔记·postgresql
疯狂打码的少年4 小时前
【数据结构】选择类排序:简单选择与堆排序
java·数据结构·笔记·算法
Be for thing4 小时前
【嵌入式成长10】STC89C51蜂鸣器|有源/无源蜂鸣器区分、三极管驱动、定时器播放音乐实战
单片机·嵌入式硬件·学习
那年窗外下的雪.4 小时前
Linux 学习笔记
linux·笔记·学习
爱奥尼欧5 小时前
【daily practice】最长数字串怎么找?岛屿数量如何沉岛秒数?六棍拼两三角形有几种验法?(5.字符串中找出连续最长的数字串、岛屿数量、拼三角)
笔记·学习
郎哥编程课堂5 小时前
单片机零基础训练营第2天:嵌入式开发常用的三种进制
单片机·嵌入式硬件
LCG元5 小时前
STM32 DAC+DMA+定时器触发生成任意波形:数据对齐陷阱、输出缓冲器选型与频率精度实测
stm32·单片机·嵌入式硬件
疯狂打码的少年5 小时前
【数据结构】排序算法:归并排序与基数排序
数据结构·笔记·算法·排序算法