news 2026/8/25 14:53:15

STM32专题之内部FLASH详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32专题之内部FLASH详解

目录

一、小叙内部Flash

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

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

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

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

5.1 库函数是工具

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解锁、上锁函数:

//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; } }
//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、设置操作位数和擦除扇区

//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、写入数据

//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位寄存器的值。

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、校验

#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函数执行测试函数:

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 14:45:43

php生成html代码 PHP一键生成HTML,服务器CPU狂降90%,这招太狠了

知识获取的路径存在着许多种, 于课课家语言编程那儿的学习, 更是有着无穷无尽的乐趣, 此刻由小编来为大伙讲述有关语言编程的文章, 但愿对大伙能有所助益。最近研究了一些开发技术, 就其中的php实例教程而言, 发现PHP具备诸多独特的优秀功能, 这些功能是其他一些技术所没有的, …

作者头像 李华
网站建设 2026/8/25 14:44:45

Google能索引,AI却抓不到

很多技术团队有个错觉&#xff1a;只要Google能正常收录&#xff0c;内容就没问题。但现实是&#xff0c;你的页面在Google搜索结果里好好的&#xff0c;在ChatGPT、Perplexity、Claude的答案里却像从未存在过一样。问题往往出在JavaScript渲染上——Googlebot能执行JS&#xf…

作者头像 李华
网站建设 2026/8/25 14:37:19

2026年7月黄石市新房价格深度分析报告

一、报告背景与数据说明本报告基于2026年7月黄石市新房实际成交案例&#xff0c;结合区域分布、楼盘定位、户型结构与成交价格等多维数据&#xff0c;对当前黄石市新房市场进行深度分析。报告旨在为购房者、投资者及行业从业者提供客观、真实的市场参考。数据来源说明&#xff…

作者头像 李华
网站建设 2026/8/25 14:28:55

面向具身智能的TVA-VLA长程任务记忆与技能复用

前沿技术探索&#xff1a;TVA智能体&#xff08;简称TVA&#xff09;TVA智能体&#xff08;亦称“AI智能体视觉”或“TVA视觉智能体”&#xff09;是依托Transformer架构与“因式智能体”理论构建的系统级视觉技术框架。它融合深度强化学习&#xff08;DRL&#xff09;、卷积神…

作者头像 李华