目录
[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)
{
}
}
详细的代码已共享到附件。