存储读写之FLASH篇2-本篇内容来自野火文档

STM32的内部FLASH简介

在STM32芯片内部,存在一个重要的FLASH存储器,其主要用途是存储应用程序代码。编写完应用程序后,通常需要使用下载工具将已编译的代码文件写入内部FLASH。不可忽视的是,内部FLASH具有非易失性存储的特性,这意味着在断电后存储的数据不会丢失。每次芯片重新上 电并复位后,内核可以从内部FLASH中加载并执行存储的代码。

image.png

除了使用外部工具(如下载器)来读写内部FLASH之外,STM32芯片在运行时也可以对其自身的内部FLASH进行读写操作。这意味着,若内部FLASH存储了应用程序后还有剩余的空间,我们可以可以利用内部FLASH的剩余空间,类似于外部SPI-FLASH,来存储一些在掉电情况下需要保留的关键数据。由于内部FLASH的访问速度明显快于外部SPI-FLASH,因此在紧急情况下,通常会使用内部FLASH来存储重要记录。
为了增强安全性和保护应用程序免受抄袭,一些应用采取了以下措施:

  • 禁止读写内部FLASH内容: 这可以通过STM32芯片的内部权限设置来实现,以确保只有授权的应用程序能够访问内部FLASH中的内容。
  • 数据加密和加密信息记录: 在首次运行时,应用程序可以计算并记录加密信息到特定区域,然后删除自身的部分加密代码。这有助于提高应用程序的安全性,同时确保数据在内部FLASH中受到保护。

FLASH各个存储区域说明

在STM32微控制器中,FLASH存储器的地址是分段的,不同地址段用于存储不同类型的数据。地址段的划分通常如下:
image.png

  • 主存储器:是用于存储用户程序代码的区域,通常包括FLASH的主要部分,用于存放应用程序。芯片型号中的256K FLASH或512K FLASH指的是这个区域的大小。分页,实质就是FLASH存储器的扇区,与其他FLASH存储器类似,写入数据之前需要先按照扇区进行擦除操作,以确保数据的正确存储和更新。
  • 信息块->启动程序代码:系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。
  • 信息块->用户选择字节:选项字节用于配置FLASH的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共16字节。可以通过修改FLASH的选项控制寄存器修改。
  • 闪存存储器接口寄存器:是用于控制和配置STM32微控制器内部闪存存储器的器件存器。这些寄存包括了一系列控制、状态和配置寄存器,用于操作和管理闪存存储器。

具体来说,闪存存储器用于以下目的:

  • 编程和擦除控制:这些寄存器允许您启动和停止闪存编程和擦除操作。通过配置这些寄存器,可以控制何时执行编程和擦除操作以及操作方式。
  • 错误检测和处理:这些寄存器包括错误标志,用于检测和处理闪存编程和擦除过程中的错误。可以读取这些标志以确定操作是否成功或是否出现了错误。
  • 读取和写入保护:这些寄存器允许配置对闪存存储器的读取和写入保护。可设置不同的保护级别,以防止非法访问或是否出现了错误。
  • 时序和时钟控制:这些寄存器用于配置闪存访问的时序和时钟。通过调整这些设置。可以优化闪存存储器的性能和稳定性。

STM32命名及对应的内容不容量

关于STM32内部FLASH的容量类型可根据它的型号名获知:

型号范例STM32F103ZET6
家族“STM32 “表示32bit的MCU
产品类型“F”表示基础型
具体特性“103”基础型
引脚数目“Z”表示144个引脚,
其他常用的为:
C表示48引脚,
R表示64引脚,
V表示100引脚,
Z表示144引脚,
B表示208引脚,
N表示216引脚
FLASH大小E表示512KB,
其他常用的为:
4表示16KB(小容量ld),
6表示32KB(小容量ld),
8表示64KB(中容量md),
B表示128KB(中容量md),
C表示256 KB(大容量hd),
E表示512 KB(大容量hd),
F表示768KB(超大容量xl),
G表示1024KB(超大容量xl),

内部FLASH的写入过程

STM32F10X内存编程参考手册
image.png

1.** 解锁,** 由于内部FLASH空间主要存储的是应用程序,是非常关键的数据,为了 防止误操作修改了这些内容,芯片复位后默认会给FLASH上锁,此时,不允许设置FLASH的控制寄存器,并且不能对修改FLASH中的内容。所以对FLASH写入数据前,需要先给它解锁。解锁的操作步骤如下:
• 往Flash 密钥寄存器 FLASH_KEYR中写入 KEY1 = 0x45670123
• 再往Flash 密钥寄存器 FLASH_KEYR中写入 KEY2 = 0xCDEF89AB
**2.擦除扇区,**在写入新的数据前,需要先擦除存储区域,STM32提供了扇区擦除指令和整个FLASH擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。 扇区擦除的过程如下:
• 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
• 在 FLASH_CR 寄存器中,将“激活页擦除寄存器位PER ”置 1,
• 用FLASH_AR寄存器选择要擦除的页;
• 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
• 等待 BSY 位被清零时,表示擦除完成
3.写入数据 ,擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋 值,赋值前还还需要配置一系列的寄存器,步骤如下:
• 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
• 将 FLASH_CR 寄存器中的 “激活编程寄存器位PG” 置 1;
• 向指定的FLASH存储器地址执行数据写入操作,每次只能以16位的方式写入;
• 等待 BSY 位被清零时,表示写入完成

查看工程的空间分布

由于内部FLASH本身存储有程序数据,若不是有意删除某段程序代码, 一般不应修改程序空间的内容,所以在使用内部FLASH存储其它数据前需要了解 哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生的“*.map”后缀文件,可以了解程序存储到了哪些区域。
打开map文件后,查看文件最后部分的区域,可以看到一段以“Memory Map of the image”开头的记录:
这一段是某工 程的ROM存 储器分布映像, 在STM32芯 片中,ROM 区域的内容就 是指存储到内部FLASH的
代码。
image.png
**1.程序ROM的加载与执行空间 ,**在上面map文件的描述中“Load Region LR_ROM1”及“Execution Region ER_IROM1”开头的内容,它们分别描述程序的加载及执行空间。在芯片 刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从ROM复制到RAM空间,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。我们了解到加载及执行空间的基地址(Base)都是0x08000000,它正好是STM32内部FLASH的首地址,即STM32的程序存储空间就直接是执行空间;它们的大小(Size)分别为0x000017a8及0x0000177c,执行空间的ROM比较小的原因就是因为部分RW-data类型的变量被拷贝到RAM空间了; 它们的最大空间(Max)均为0x00080000,即512K字节,它指的是内部FLASH的最大空间。
计算程序占用的空间时,需要使用加载区域的大小进行计算,本例子中应 用程序使用的内部FLASH是从0x08000000至(0x08000000+0x000017a8)地址的空间区域。
2. ROM空间分布表 ,在加载及执行空间总体描述之后,紧接着一个ROM详细地址分布表,它列出了工程中的各个段(如函数、常量数据)所在的地址Base Addr及占用的空间 Size,列表中的Type说明了该段的类型,CODE表示代码,DATA表示数据,而 PAD表示段之间的填充区域,它是无效的内容,PAD区域往往是为了解决地址对齐 的问题。
观察表中的最后一项,它的基地址是0x0800175c,大小为0x00000020,可知它占用的最高的地址空间为0x0800177c,跟执行区域的最高地址0x0000177c 一样,但它们比加载区域说明中的最高地址0x80017a8要小,所以我们以加载区域的大小为准。对比内部FLASH页地址分布表,可知仅使用页0至页2就可以完全存 储本应用程序,所以从页3(地址0x08001800)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。

Memory Map of the image //存储分布映像

  Image Entry point : 0x08000131 

  Load Region LR_IROM1 (Base: 0x08000000, Size: 0x00006c5c, Max: 0x00080000, ABSOLUTE, COMPRESSED[0x00006784])

    Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00006758, Max: 0x00080000, ABSOLUTE)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x08000000   0x00000130   Data   RO          368    RESET               startup_stm32f10x_hd.o
    0x08000130   0x00000000   Code   RO          618  * .ARM.Collect$$$$00000000  mc_w.l(entry.o)
    0x08000130   0x00000004   Code   RO          944    .ARM.Collect$$$$00000001  mc_w.l(entry2.o)
    0x08000134   0x00000004   Code   RO          947    .ARM.Collect$$$$00000004  mc_w.l(entry5.o)
    0x08000138   0x00000000   Code   RO          949    .ARM.Collect$$$$00000008  mc_w.l(entry7b.o)
    0x08000138   0x00000000   Code   RO          951    .ARM.Collect$$$$0000000A  mc_w.l(entry8b.o)
    省略....
    0x08006618   0x00000030   Data   RO          917    .constdata          m_ws.l(cos_i.o)
    0x08006648   0x000000c8   Data   RO          935    .constdata          m_ws.l(rred.o)
    0x08006710   0x00000028   Data   RO          939    .constdata          m_ws.l(sin_i.o)
    0x08006738   0x00000020   Data   RO          999    Region$$Table       anon$$obj.o


    Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x0000bfd0, Max: 0x00010000, ABSOLUTE, COMPRESSED[0x0000002c])

    

操作内部FLASH的库函数

为简化编程,STM32标准库提供了一些库函数,它们封装了对内部 FLASH写入数据操作寄存器的过程。 解锁的时候,它对 FLASH_KEYR寄存器写入两个解锁参数,上锁的时候,对FLASH_CR寄存器的FLASH_CR_LOCK
位置1.

#define FLASH_R_BASE      (AHBPERIPH_BASE + 0x2000) 
#define FLASH             ((FLASH_TypeDef *) FLASH_R_BASE)
#define	FLASH_CR_LOCK     ((uint16_t)0x0080)
#define FLASH_KEY1	      ((uint32_t)0x45670123)
#define FLASH_KEY2        ((uint32_t)0xCDEF89AB)

typedef struct
{
  __IO uint32_t ACR;
  __IO uint32_t KEYR;
  __IO uint32_t OPTKEYR;
  __IO uint32_t SR;
  __IO uint32_t CR;
  __IO uint32_t AR;
  __IO uint32_t RESERVED;
  __IO uint32_t OBR;
  __IO uint32_t WRPR;
#ifdef STM32F10X_XL
  uint32_t RESERVED1[8]; 
  __IO uint32_t KEYR2;
  uint32_t RESERVED2;   
  __IO uint32_t SR2;
  __IO uint32_t CR2;
  __IO uint32_t AR2; 
#endif /* STM32F10X_XL */  
} FLASH_TypeDef;


typedef	struct
{
	_IO uint32_t ACR;
	_IO	uint32_t KEYR;
    _IO	uint32_t OPTKEYR;
    _IO uint32_t SR;
    _IO uint32_t CR;
    
}

/*相关寄存器请查看STM32F10XXX闪存编程参考手册*/


void FLASH Unlock(void)
{
    if((FLASH->CR & FLASH_CR_LOCK)!=RESET) 
    {
        FLASH->KEYR = FLASH_KEY1;
        FLASH->KEYR = FLASH_KEY2;
    }
}

void FLSH_Lock(void)
{
    FLASH->CR | = FLASH_CR_LOK;
}

  1. 结构体指针 vs. 宏定义:
  • 结构体指针:在C语言中,结构体指针是一个指向结构体对象的指针。通过结构体指针,您可以访问结构体对象的各个成员,这些成员在内存中是连续存储的。例如,**FLASH_TypeDef *** 是一个指向 FLASH_TypeDef 结构体的指针类型,可以通过它来访问结构体成员。
  • 宏定义:宏定义是一种预处理指令,它在编译时被替换为代码。在这个上下文中,*#define FLASH ((FLASH_TypeDef ) FLASH_R_BASE) 是一个宏定义,它的目的是将 FLASH 视为一个指向 FLASH_TypeDef 结构体的指针,以便在代码中以一种更具可读性的方式访问结构体的成员。
  1. 类型强制转换:
  • *(FLASH_TypeDef ) 部分是一种类型强制转换,它将 FLASH_R_BASE 的地址类型从整数类型转换为 FLASH_TypeDef 结构体指针类型。这使得编译器能够正确地理解 FLASH 是一个指针,从而允许您使用 -> 运算符来访问结构体成员。
  1. 连续性:
  • 结构体成员在内存中通常是连续存储的,这意味着结构体的第一个成员的地址和结构体的起始地址是一样的,而后续成员的地址是连续递增的。
  1. 成员访问
  • "."用于直接访问结构体变量的成员
  • “->用于通过指针间接访问结构体成员”
#include<stdio.h>

struct person
{
    int age;
    char name[32];
}; // 逗号容易忘记

int main()
{
    struct perseon p;
    p.age =20 ; //通过结构体变量访问成员

    stru person *ptr =*p;
    ptr->age =25 ;//通过指针访问结构体成员
        
    return 0;
}

2.设置操作位数及擦除扇区,解锁后擦除扇区时可调用FLASH_EraseSector完成:该函数包含以Page_Address
输入参数获得要擦除的地址。 内部根据该参数配置 FLASH_AR地址,然后擦除扇区,擦除扇区的时候需要等
待一段时间,它使用 FLASH_WaitForLastOperation等待,擦除完成的时候才会 退出FLASH_EraseSector函
数。

typedef enum
{ 
  FLASH_BUSY = 1,
  FLASH_ERROR_PG,
  FLASH_ERROR_WRP,
  FLASH_COMPLETE,
  FLASH_TIMEOUT
}FLASH_Status;

/**
  * @brief  Erases a specified FLASH page.
  * @note   This function can be used for all STM32F10x devices.
  * @param  Page_Address: The page address to be erased.
  * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
  *         FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
  */

FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
{
  FLASH_Status status = FLASH_COMPLETE;
  /* Check the parameters */
  assert_param(IS_FLASH_ADDRESS(Page_Address));
  status = FLASH_WaitForLastOperation(EraseTimeout);
  
  if(status == FLASH_COMPLETE)
  { 
    /* if the previous operation is completed, proceed to erase the page */
    FLASH->CR|= CR_PER_Set;
    FLASH->AR = Page_Address; 
    FLASH->CR|= CR_STRT_Set;
    
    /* Wait for last operation to be completed */
    status = FLASH_WaitForLastOperation(EraseTimeout);
    
    /* Disable the PER Bit */
    FLASH->CR &= CR_PER_Reset;
  }
#endif /* STM32F10X_XL */

  /* Return the Erase Status */
  return status;
}

这段代码是使用C语言编写的,用于擦除STM32F10x系列微控制器的FLASH存储器中的一个页面。

  1. typedef enum 是一个枚举类型的定义,它定义了一个名为 FLASH_Status 的新类型。这个枚举类型包含了不同的枚举值,表示了FLASH操作的不同状态。这些状态包括 FLASH_BUSYFLASH_ERROR_PGFLASH_ERROR_WRPFLASH_COMPLETEFLASH_TIMEOUT
  2. FLASH_Status FLASH_ErasePage(uint32_t Page_Address) 是一个函数的定义,它用于擦除指定的FLASH页面。这个函数接受一个名为 Page_Address 的参数,表示要擦除的页面的地址。函数返回一个 FLASH_Status 枚举类型的值,表示擦除操作的状态。
  3. FLASH_Status status = FLASH_COMPLETE; 定义了一个名为 status 的局部变量,并将其初始化为 FLASH_COMPLETE,表示初始状态为擦除操作已完成。
  4. assert_param(IS_FLASH_ADDRESS(Page_Address)); 是一个断言语句,用于检查传入的 Page_Address 参数是否是有效的FLASH地址。如果地址无效,将触发断言错误。
  5. status = FLASH_WaitForLastOperation(EraseTimeout); 调用 FLASH_WaitForLastOperation 函数,等待上一个FLASH操作的完成,并将结果存储在 status 变量中。
  6. 接下来的条件语句检查 status 是否等于 FLASH_COMPLETE。如果上一个操作已完成,就会执行下面的操作,否则将跳过擦除步骤。
  7. FLASH->CR|= CR_PER_Set;FLASH->CR 寄存器中的 CR_PER_Set 标志位置1,表示要擦除一个FLASH页面。
  8. FLASH->AR = Page_Address; 设置 FLASH->AR 寄存器的值为要擦除的页面地址。
  9. FLASH->CR|= CR_STRT_Set; 设置 FLASH->CR 寄存器的 CR_STRT_Set 标志位,触发擦除操作。
  10. 再次调用 FLASH_WaitForLastOperation 等待擦除操作完成。
  11. FLASH->CR &= CR_PER_Reset; 清除 FLASH->CR 寄存器中的 CR_PER_Set 标志位,表示擦除操作已完成。
  12. 最后,函数返回 status,它会表示擦除操作的状态,可以是 FLASH_BUSYFLASH_ERROR_PGFLASH_ERROR_WRPFLASH_COMPLETEFLASH_TIMEOUT 中的一个。

当函数需要返回多个可能状态时,使用枚举类型可以提供更清晰的结果。下面是一个简单的C语言示例,演示如何定义一个函数,该函数返回一个枚举类型表示不同的状态:

#include <stdio.h>

// 定义枚举类型表示不同的状态
typedef enum {
    OK,
    ERROR,
    INVALID_INPUT
} Status;

// 示例函数,根据输入返回不同的状态
Status processInput(int input) {
    if (input > 0) {
        return OK;
    } else if (input < 0) {
        return ERROR;
    } else {
        return INVALID_INPUT;
    }
}

int main() {
    int userInput;
    printf("Enter an integer: ");
    scanf("%d", &userInput);

    Status result = processInput(userInput);

    // 根据函数返回的状态进行不同的操作
    switch (result) {
        case OK:
            printf("Input is positive.\n");
            break;
        case ERROR:
            printf("Input is negative.\n");
            break;
        case INVALID_INPUT:
            printf("Input is invalid.\n");
            break;
        default:
            printf("Unknown status.\n");
    }

    return 0;
}

在这个示例中,我们定义了一个名为 Status 的枚举类型,它有三个可能的状态:OKERRORINVALID_INPUT。然后,我们定义了一个名为 processInput 的函数,该函数接受一个整数输入,并根据输入的不同值返回不同的状态。
main 函数中,我们读取用户输入的整数,并将其传递给 processInput 函数,然后根据返回的状态值执行不同的操作,以清晰地表示输入的情况。这种方式可以更好地组织和传达函数的执行结果。

3.写入数据 对内部FLASH写入数据不像对SDRAM操作那样直接指针操作就完成了,还
要设置一系列的寄存器,利用FLASH_ProgramWord和FLASH_ProgramHalfWord函
数可按字、半字节单位写入数据:(见下一页PPT)
从函数代码可了解到,使用指针进行赋值操作前设置了PG寄存器位,在赋值操作后,调用了
FLASH_WaitForLastOperation函数等待写操作完毕。HalfWord和Byte操作宽度的函数执行过程类似。
image.png

FLASH_Status FLASH_

名词解释

自举程序(Bootloader)是嵌入式系统中的一种特殊程序,通常用于在启动过程中加载、升级或擦写设备的固件。
闪存编程/擦除控制器(FPEC):FPEC是内置在STM32F10X微控制器中的一个控制器,用于管理主存储器和信息块(通常是FLASH存储器)的写入和擦除操作。这是一个硬件模块,负责处理闪存的编程和擦除。
编程与擦除的高电压:在STM32F10X中,编程和擦除FLASH存储器需要较高的电压,这个高电压是由内部电路产生的。这是因为编程和擦除FLASH存储器需要改变存储单元的状态,而这通常需要较高的电压水平。
闪存存储器的保护方式:STM32F10X提供了两种方式来保护FLASH存储器免受非法访问:
页写入保护:这种保护方式允许您限制对FLASH存储器的特定页的写入操作。只有经过授权的代码才能对受保护的页进行写入操作。这有助于防止未经授权的修改。
读出保护:读出保护是另一种级别的保护,它可以防止从FLASH存储器中读取数据。当启用读出保护时,只有特定区域的代码才能访问FLASH存储器的内容,其他代码将无法读取其中的数据。这可以用于保护固件的安全性。
image.png

知识拓展

闪存编程和擦除操作通常需要高电压的原因如下:

  1. Floating Gate Transistor:在闪存存储单元中,通常使用一种特殊的晶体管结构,称为浮栅晶体管(Floating Gate Transistor)。这种晶体管具有两个栅极(Gate),一个用于控制数据的读写,另一个用于保持数据状态。数据状态存储在浮动栅极中。为了改变浮动栅极的状态,需要应用足够的电压以克服电子在栅极之间的障碍,这就需要高电压。
  2. Tunneling Effect:在编程和擦除时,电子需要通过两个栅极之间的氧化层,这涉及到电子的隧道效应。为了使电子能够足够多地穿越氧化层,需要高电压以增加电子的能量,从而使其能够穿越氧化层的能垒。
  3. 数据稳定性:闪存存储器设计的一个关键目标是数据的稳定性和长期保持。高电压编程和擦除确保了存储的数据能够长时间保持,不容易受到外部噪声或环境条件的干扰。

电路原理方面,闪存编程和擦除通常涉及一个特殊的电路,用于产生所需的高电压。这个电路通常称为电荷泵电路(Charge Pump Circuit),它可以从低电压源生成较高的电压。电荷泵电路基于电容器的原理,可以逐步将电荷积累,并将其释放为较高的电压。
电荷泵电路可以采用不同的设计和拓扑结构,以适应不同的芯片需求。一旦产生了所需的高电压,它会被应用到浮动栅极上,以执行编程或擦除操作。
总的来说,高电压编程和擦除是确保闪存稳定性和性能的关键因素,因为它们确保了在闪存存储单元内进行可靠的数据状态更改。电荷泵电路是用于生成这些高电压的关键电路之一。

  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值