一、内部FLASH简介

    在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行。

读写内部FLASH

    除了使用外部工具(如下载器)读写内部FLASH外,STM32芯片在运行的时候,也能对自身的内部FLASH进行读写,因此,若内部FLASH存储了应用程序后还有剩余空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。

    由于访问内部FLASH的速度要比访问外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。

    (1)内部FLASH的构成

          STM32的内部FLASH包括主存储器、系统存储器、OTP区域以及选项字节区域,它们的地址分布大小如下:

读写内部FLASH

          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位改变,配置表如下:

读写内部FLASH

              最大操作位数会影响擦除和写入的速度,其中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”开头的记录:

读写内部FLASH

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

相关文章:

  • 2022-12-23
  • 2021-08-14
  • 2021-09-10
  • 2021-10-14
  • 2021-11-05
  • 2022-12-23
  • 2022-01-20
猜你喜欢
  • 2021-08-16
  • 2021-12-12
  • 2018-05-16
  • 2022-02-08
  • 2022-12-23
相关资源
相似解决方案