当前位置: 首页 > news >正文

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

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

http://www.cnnetsun.cn/news/4212384.html

相关文章:

  • php生成html代码 PHP一键生成HTML,服务器CPU狂降90%,这招太狠了
  • Google能索引,AI却抓不到
  • SDD 规范驱动实战:我用 Vibe Coding 开发了一个 AI 网页翻译 Chrome 插件
  • 2026年7月黄石市新房价格深度分析报告
  • 面向具身智能的TVA-VLA长程任务记忆与技能复用
  • php json schema 你的PHP JSON Schema验证,真能像健身库一样,一个git clone就搞定吗?
  • [通信与计算]通信原理03:信道容量与信道编码:原理与工程实践
  • js实践小案例
  • 无偏教师 v2:适用于无锚框和基于锚框检测器的半监督目标检测
  • Agent 敢开写权限吗?—— 深入探讨 AI 代理的自主操作风险与安全边界
  • C++初阶——类和对象(下)
  • AI 编程不得不知道的二三事
  • 隐匿中的生长:当代青年 “偷感” 现象解读
  • 2021CSP-J初赛真题解析(适合复习、巩固、备考)
  • 麒麟(Kylin)服务器系统网卡地址设置
  • 女孩子可以考什么证书比较简单
  • Ansible实战:LNMP一键部署指南
  • 汉森制药(002412)深度研究报告
  • 理解 JWT:三段分别是什么
  • 计算机毕业设计之企业社交网络平台的设计与实现
  • HarmonyOS 性能优化工具链:从「手工排查」到「工程化治理」的全栈实战指南
  • 01-具身机器人硬件全景拆解
  • ABAP 里有没有 AI Agent 的 Progressive Disclosure,一套从封装、Released API 到 RAP 暴露层的完整对照
  • 从奈奎斯特判据到无源性:为什么只需保证逆变器导纳无源就能稳定并网?
  • 【python】条件语句
  • 【2015-03-02】《RealView编译工具汇编器指南》摘录:内置变量和常数
  • 手机玩鸣潮 3.6 版本,随时随地开荒新地图不卡顿
  • AIGC 到底是什么:从传统软件到生成式人工智能
  • 规模化养殖冰爽强甘多少钱
  • Go单例模式:sync.Once与双重检查