STM32的内部FLASH简介
在STM32芯片内部,存在一个重要的FLASH存储器,其主要用途是存储应用程序代码。编写完应用程序后,通常需要使用下载工具将已编译的代码文件写入内部FLASH。不可忽视的是,内部FLASH具有非易失性存储的特性,这意味着在断电后存储的数据不会丢失。每次芯片重新上 电并复位后,内核可以从内部FLASH中加载并执行存储的代码。
除了使用外部工具(如下载器)来读写内部FLASH之外,STM32芯片在运行时也可以对其自身的内部FLASH进行读写操作。这意味着,若内部FLASH存储了应用程序后还有剩余的空间,我们可以可以利用内部FLASH的剩余空间,类似于外部SPI-FLASH,来存储一些在掉电情况下需要保留的关键数据。由于内部FLASH的访问速度明显快于外部SPI-FLASH,因此在紧急情况下,通常会使用内部FLASH来存储重要记录。
为了增强安全性和保护应用程序免受抄袭,一些应用采取了以下措施:
- 禁止读写内部FLASH内容: 这可以通过STM32芯片的内部权限设置来实现,以确保只有授权的应用程序能够访问内部FLASH中的内容。
- 数据加密和加密信息记录: 在首次运行时,应用程序可以计算并记录加密信息到特定区域,然后删除自身的部分加密代码。这有助于提高应用程序的安全性,同时确保数据在内部FLASH中受到保护。
FLASH各个存储区域说明
在STM32微控制器中,FLASH存储器的地址是分段的,不同地址段用于存储不同类型的数据。地址段的划分通常如下:
- 主存储器:是用于存储用户程序代码的区域,通常包括FLASH的主要部分,用于存放应用程序。芯片型号中的256K FLASH或512K FLASH指的是这个区域的大小。分页,实质就是FLASH存储器的扇区,与其他FLASH存储器类似,写入数据之前需要先按照扇区进行擦除操作,以确保数据的正确存储和更新。
- 信息块->启动程序代码:系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。
- 信息块->用户选择字节:选项字节用于配置FLASH的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共16字节。可以通过修改FLASH的选项控制寄存器修改。
- 闪存存储器接口寄存器:是用于控制和配置STM32微控制器内部闪存存储器的器件存器。这些寄存包括了一系列控制、状态和配置寄存器,用于操作和管理闪存存储器。
具体来说,闪存存储器用于以下目的:
- 编程和擦除控制:这些寄存器允许您启动和停止闪存编程和擦除操作。通过配置这些寄存器,可以控制何时执行编程和擦除操作以及操作方式。
- 错误检测和处理:这些寄存器包括错误标志,用于检测和处理闪存编程和擦除过程中的错误。可以读取这些标志以确定操作是否成功或是否出现了错误。
- 读取和写入保护:这些寄存器允许配置对闪存存储器的读取和写入保护。可设置不同的保护级别,以防止非法访问或是否出现了错误。
- 时序和时钟控制:这些寄存器用于配置闪存访问的时序和时钟。通过调整这些设置。可以优化闪存存储器的性能和稳定性。
STM32命名及对应的内容不容量
关于STM32内部FLASH的容量类型可根据它的型号名获知:
型号范例 | STM32 | F | 103 | Z | E | T | 6 |
---|---|---|---|---|---|---|---|
家族 | “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内存编程参考手册
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的
代码。
**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;
}
- 结构体指针 vs. 宏定义:
- 结构体指针:在C语言中,结构体指针是一个指向结构体对象的指针。通过结构体指针,您可以访问结构体对象的各个成员,这些成员在内存中是连续存储的。例如,**FLASH_TypeDef *** 是一个指向 FLASH_TypeDef 结构体的指针类型,可以通过它来访问结构体成员。
- 宏定义:宏定义是一种预处理指令,它在编译时被替换为代码。在这个上下文中,*#define FLASH ((FLASH_TypeDef ) FLASH_R_BASE) 是一个宏定义,它的目的是将 FLASH 视为一个指向 FLASH_TypeDef 结构体的指针,以便在代码中以一种更具可读性的方式访问结构体的成员。
- 类型强制转换:
- *(FLASH_TypeDef ) 部分是一种类型强制转换,它将 FLASH_R_BASE 的地址类型从整数类型转换为 FLASH_TypeDef 结构体指针类型。这使得编译器能够正确地理解 FLASH 是一个指针,从而允许您使用 -> 运算符来访问结构体成员。
- 连续性:
- 结构体成员在内存中通常是连续存储的,这意味着结构体的第一个成员的地址和结构体的起始地址是一样的,而后续成员的地址是连续递增的。
- 成员访问
- "."用于直接访问结构体变量的成员
- “->用于通过指针间接访问结构体成员”
#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存储器中的一个页面。
- typedef enum 是一个枚举类型的定义,它定义了一个名为 FLASH_Status 的新类型。这个枚举类型包含了不同的枚举值,表示了FLASH操作的不同状态。这些状态包括 FLASH_BUSY、FLASH_ERROR_PG、FLASH_ERROR_WRP、FLASH_COMPLETE 和 FLASH_TIMEOUT。
- FLASH_Status FLASH_ErasePage(uint32_t Page_Address) 是一个函数的定义,它用于擦除指定的FLASH页面。这个函数接受一个名为 Page_Address 的参数,表示要擦除的页面的地址。函数返回一个 FLASH_Status 枚举类型的值,表示擦除操作的状态。
- FLASH_Status status = FLASH_COMPLETE; 定义了一个名为 status 的局部变量,并将其初始化为 FLASH_COMPLETE,表示初始状态为擦除操作已完成。
- assert_param(IS_FLASH_ADDRESS(Page_Address)); 是一个断言语句,用于检查传入的 Page_Address 参数是否是有效的FLASH地址。如果地址无效,将触发断言错误。
- status = FLASH_WaitForLastOperation(EraseTimeout); 调用 FLASH_WaitForLastOperation 函数,等待上一个FLASH操作的完成,并将结果存储在 status 变量中。
- 接下来的条件语句检查 status 是否等于 FLASH_COMPLETE。如果上一个操作已完成,就会执行下面的操作,否则将跳过擦除步骤。
- FLASH->CR|= CR_PER_Set; 将 FLASH->CR 寄存器中的 CR_PER_Set 标志位置1,表示要擦除一个FLASH页面。
- FLASH->AR = Page_Address; 设置 FLASH->AR 寄存器的值为要擦除的页面地址。
- FLASH->CR|= CR_STRT_Set; 设置 FLASH->CR 寄存器的 CR_STRT_Set 标志位,触发擦除操作。
- 再次调用 FLASH_WaitForLastOperation 等待擦除操作完成。
- FLASH->CR &= CR_PER_Reset; 清除 FLASH->CR 寄存器中的 CR_PER_Set 标志位,表示擦除操作已完成。
- 最后,函数返回 status,它会表示擦除操作的状态,可以是 FLASH_BUSY、FLASH_ERROR_PG、FLASH_ERROR_WRP、FLASH_COMPLETE 或 FLASH_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 的枚举类型,它有三个可能的状态:OK、ERROR 和 INVALID_INPUT。然后,我们定义了一个名为 processInput 的函数,该函数接受一个整数输入,并根据输入的不同值返回不同的状态。
在 main 函数中,我们读取用户输入的整数,并将其传递给 processInput 函数,然后根据返回的状态值执行不同的操作,以清晰地表示输入的情况。这种方式可以更好地组织和传达函数的执行结果。
3.写入数据 对内部FLASH写入数据不像对SDRAM操作那样直接指针操作就完成了,还
要设置一系列的寄存器,利用FLASH_ProgramWord和FLASH_ProgramHalfWord函
数可按字、半字节单位写入数据:(见下一页PPT)
从函数代码可了解到,使用指针进行赋值操作前设置了PG寄存器位,在赋值操作后,调用了
FLASH_WaitForLastOperation函数等待写操作完毕。HalfWord和Byte操作宽度的函数执行过程类似。
FLASH_Status FLASH_
名词解释
自举程序(Bootloader)是嵌入式系统中的一种特殊程序,通常用于在启动过程中加载、升级或擦写设备的固件。
闪存编程/擦除控制器(FPEC):FPEC是内置在STM32F10X微控制器中的一个控制器,用于管理主存储器和信息块(通常是FLASH存储器)的写入和擦除操作。这是一个硬件模块,负责处理闪存的编程和擦除。
编程与擦除的高电压:在STM32F10X中,编程和擦除FLASH存储器需要较高的电压,这个高电压是由内部电路产生的。这是因为编程和擦除FLASH存储器需要改变存储单元的状态,而这通常需要较高的电压水平。
闪存存储器的保护方式:STM32F10X提供了两种方式来保护FLASH存储器免受非法访问:
页写入保护:这种保护方式允许您限制对FLASH存储器的特定页的写入操作。只有经过授权的代码才能对受保护的页进行写入操作。这有助于防止未经授权的修改。
读出保护:读出保护是另一种级别的保护,它可以防止从FLASH存储器中读取数据。当启用读出保护时,只有特定区域的代码才能访问FLASH存储器的内容,其他代码将无法读取其中的数据。这可以用于保护固件的安全性。
知识拓展
闪存编程和擦除操作通常需要高电压的原因如下:
- Floating Gate Transistor:在闪存存储单元中,通常使用一种特殊的晶体管结构,称为浮栅晶体管(Floating Gate Transistor)。这种晶体管具有两个栅极(Gate),一个用于控制数据的读写,另一个用于保持数据状态。数据状态存储在浮动栅极中。为了改变浮动栅极的状态,需要应用足够的电压以克服电子在栅极之间的障碍,这就需要高电压。
- Tunneling Effect:在编程和擦除时,电子需要通过两个栅极之间的氧化层,这涉及到电子的隧道效应。为了使电子能够足够多地穿越氧化层,需要高电压以增加电子的能量,从而使其能够穿越氧化层的能垒。
- 数据稳定性:闪存存储器设计的一个关键目标是数据的稳定性和长期保持。高电压编程和擦除确保了存储的数据能够长时间保持,不容易受到外部噪声或环境条件的干扰。
电路原理方面,闪存编程和擦除通常涉及一个特殊的电路,用于产生所需的高电压。这个电路通常称为电荷泵电路(Charge Pump Circuit),它可以从低电压源生成较高的电压。电荷泵电路基于电容器的原理,可以逐步将电荷积累,并将其释放为较高的电压。
电荷泵电路可以采用不同的设计和拓扑结构,以适应不同的芯片需求。一旦产生了所需的高电压,它会被应用到浮动栅极上,以执行编程或擦除操作。
总的来说,高电压编程和擦除是确保闪存稳定性和性能的关键因素,因为它们确保了在闪存存储单元内进行可靠的数据状态更改。电荷泵电路是用于生成这些高电压的关键电路之一。