一、内部FLASH简介
在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行。
除了使用外部工具(如下载器)读写内部FLASH外,STM32芯片在运行的时候,也能对自身的内部FLASH进行读写,因此,若内部FLASH存储了应用程序后还有剩余空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。
由于访问内部FLASH的速度要比访问外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。
(1)内部FLASH的构成
STM32的内部FLASH包括主存储器、系统存储器、OTP区域以及选项字节区域,它们的地址分布大小如下:
1》主存储器
一般我们说STM32内部FLASH的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的1M FLASH、2M FLASH都是指这个区域的大小。主存储器分为两块,共2MB,每块内分12个扇区,其中包含4个16KB扇区、1个64KB扇区和7个128KB扇区。如我们实验板中使用的STMF407型号芯片,它的主存储区域大小为1MB,所以它只包含有表中的扇区0-扇区11。
2》系统存储器
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。
3》OTP区域
OTP,指的是只能写入一次的存储区域,容量为512字节,写入后数据就无法再更改,OTP常用于存储应用程序的加***。
4》选项字节区域
选项字节用于配置FLASH的读写保护、电源管理中的BOR级别、软件/硬件看门狗等功能,这部分共32字节。可以通过修改FLASH的选项控制寄存器修改。
二、对内部FLASH的写入过程
(1)解锁
由于内部FLASH空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会结FLASH上锁,这个时候不允许设置FLASH的控制寄存器,并且不能对修改FLASH中的内容。
所以对FLASH写入数据前,需要先给它解锁。解锁的操作步骤如下:
1》往Flash **寄存器 FLASH_KEYR中写入 KEY1 = 0x45670123 ;
2》再往Flash **寄存器 FLASH_KEYR中写入 KEY2 = 0xCDEF89AB。
(2)数据操作位数
在内部FLASH进行擦除及写入操作时,电源电压会影响数据的最大操作位数,该电源电压可通过配置FLASH_CR 寄存器中的 PSIZE位改变,配置表如下:
最大操作位数会影响擦除和写入的速度,其中64位宽度的操作除了配置寄存器位外,还需要在Vpp引脚外加一个8-9V的电压源,且其供电时间不得超过一小时,否则FLASH可能损坏,所以64位宽度的操作一般是在量产时对FLASH写入应用程序时才使用,大部分应用场合都是用32位的宽度。
(3)擦除扇区
在写入新的数据前,需要先擦除存储区域,STM32提供了扇区擦除指令和整个FLASH擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。扇区擦除的过程如下:
1》检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
2》在 FLASH_CR 寄存器中,将“**扇区擦除寄存器位SER ”置 1,并设置“扇区编号寄存器位SNB”,选择要擦除的扇区;
3》 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
4》等待 BSY 位被清零时,表示擦除完成。
(4)写入数据
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下:
1》检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
2》将 FLASH_CR 寄存器中的 “**编程寄存器位PG” 置 1;
3》针对所需存储器地址(主存储器块或 OTP 区域内)执行数据写入操作;
4》等待 BSY 位被清零时,表示写入完成。
三、查看工程的空间分布
由于内部FLASH本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部FLASH存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的“*.map”后缀文件,可以了解程序存储到了哪些区域。
打开map文件后,查看文件最后部分的区域,可以看到一段以“Memory Map of the image”开头的记录:
(1)程序ROM的加载与执行空间
上述说明中有两段分别以“Load Region LR_ROM1”及“Execution Region ER_IROM1”开头的内容,它们分别描述程序的加载及执行空间。在芯片刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从ROM复制到RAM空间,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。
在上面map文件的描述中,可了解到加载及执行空间的基地址(Base)都是0x08000000,它正好是STM32内部FLASH的首地址,即STM32的程序存储空间就直接是执行空间;它们的大小(Size)分别为0x00000b50及0x00000b3c,执行空间的ROM比较小的原因就是因为部分RW-data类型的变量被拷贝到RAM空间了;它们的最大空间(Max)均为0x00100000,即1M字节,它指的是内部FLASH的最大空间。
计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应用程序使用的内部FLASH是从0x08000000至(0x08000000+0x00000b50)地址的空间区域。
(2)ROM空间分布表
在加载及执行空间总体描述之后,紧接着一个ROM详细地址分布表,它列出了工程中的各个段(如函数、常量数据)所在的地址Base Addr及占用的空间Size,列表中的Type说明了该段的类型,CODE表示代码,DATA表示数据,而PAD表示段之间的填充区域,它是无效的内容,PAD区域往往是为了解决地址对齐的问题。
观察表中的最后一项,它的基地址是0x08000b1c,大小为0x00000020,可知它占用的最高的地址空间为0x08000b3c,跟执行区域的最高地址0x00000b3c一样,但它们比加载区域说明中的最高地址0x8000b50要小,所以我们以加载区域的大小为准。对比内部FLASH扇区地址分布表,可知仅使用扇区0就可以完全存储本应用程序,所以从扇区1(地址0x08004000)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。
四、操作内部FLASH的库函数
(1)FLASH解锁、上锁函数
void FLASH_Unlock(void); //解锁
void FLASH_Lock(void); //上锁
(2)设置操作位数及擦除扇区
FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange);
功能:擦除指定的扇区。
参数:
参数1:要擦除的扇区号,可选FLASH_Sector_0 ~ FLASH_Sector_11
参数2:工作电压范围,可选VoltageRange_1 ~ VoltageRange_4
注意:
在该函数内部会调用等待扇区擦除完成的函数,所以无须外部等待擦除完成。
(3)写入数据
1》按字为单位写入数据
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);
参数:
参数1:指定要写入数据的地址
参数2:指定要写入的数据
注意:
在该函数内部会调用等待数据写入完成的函数,所以无须外部等待写入完成。
2》按半字为单位写入数据
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);
参数:
参数1:指定要写入数据的地址
参数2:指定要写入的数据
注意:
在该函数内部会调用等待数据写入完成的函数,所以无须外部等待写入完成。
3》按字节为单位写入数据
FLASH_Status FLASH_ProgramByte(uint32_t Address, uint8_t Data);
参数:
参数1:指定要写入数据的地址
参数2:指定要写入的数据
注意:
在该函数内部会调用等待数据写入完成的函数,所以无须外部等待写入完成。
五、程序
bsp_inter_flash.h文件
#ifndef __BSP_INTER_FLASH_H__
#define __BSP_INTER_FLASH_H__
#include "stm32f4xx_conf.h"
#define TEST_ADDR 0x08008000
#define TEST_DATA 0x12
extern void Inter_Flash_Test(void);
#endif
bsp_inter_flash.c文件
#include "./flash/bsp_inter_flash.h"
#include "stdio.h"
void Inter_Flash_Test(void)
{
FLASH_Status status;
char *p = (char *)TEST_ADDR;
/*1、解锁*/
FLASH_Unlock();
/*2、擦除(第二个扇区)*/
status = FLASH_EraseSector(FLASH_Sector_2,VoltageRange_3);
if(status == FLASH_COMPLETE)
{
printf("\r\n 扇区擦除完成");
}
else
{
printf("\r\n 扇区擦除失败");
}
/*3、写入一个字节的数据*/
status = FLASH_ProgramByte(TEST_ADDR,TEST_DATA);
if(status == FLASH_COMPLETE)
{
printf("\r\n 写入完成");
}
else
{
printf("\r\n 写入失败");
}
/*4、读取校验(校验写入的数据是否正确)*/
printf("\r\n 读取地址0x%x的内容是:0x%x",TEST_ADDR,*p);
/*5、上锁*/
FLASH_Lock();
/*6、再次读取校验(校验上锁后是否可以正常的读取数据)*/
printf("\r\n 读取地址0x%x的内容是:0x%x",TEST_ADDR,*p);
}
main.c文件
#include "./usart/bsp_usart.h"
#include "./flash/bsp_inter_flash.h"
#include "stdio.h"
int main(void)
{
USART_Config();
printf("\r\n 这是一个读写内部FLASH实验");
Inter_Flash_Test();
while(1)
{
}
}