基于stm32f103,作为个人学习记录使用
STM32 芯片内部有一个 FLASH 存储器,它主要用于存储代码,在紧急状态下常常会使用内部 FLASH 存储关键记录;
内部 FLASH 的构成
STM32 的内部 FLASH 包含主存储器
、系统存储器
以及选项字节区域
大容量产品内部 FLASH 的构成(摘自《STM32F10x 闪存编程参考手册》
主存储器
一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、512K FLASH 都是指这个区域的大小。
主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除
不同容量的芯片,Flash的主存储器的页数量、页大小均有不同
系统存储区
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB 以及 CAN 等 ISP 烧录功能。
选项字节
选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16 字节。可以通过修改 FLASH 的选项控制寄存器修改。
查看keil工程的空间分布,确定空余空间
内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容。所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。
通过.map
文件,可以了解程序存储到了哪些区域。
打开 map 文件后,查看文件最后部分的区域,可以看到一段以“Memory Map of the image”开头的记录。
这一段是某工程的 ROM 存储器分布映像,在 STM32芯片中,ROM 区域的内容就是指存储到内部 FLASH 的代码
1. 计算程序 ROM 的加载与执行空间
例子中
Load Region LR_ROM1
:程序的加载空间 :Base 0x800 0000,Size:0x0000 17a8
Execution Region ER_IROM1
:程序的执行空间: Base 0x0800 0000,Size:0x0000 177c
在芯片刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从 ROM 复制到 RAM 空间,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行。
在上面 map 文件的描述中
加载及执行空间的基地址 (Base)都是0x08000000,它正好是 STM32 内部 FLASH 的首地址,也是 STM32 的程序存储空间就直接是执行空间;
它们的大小(Size)分别为 0x000017a8 及 0x0000177c,执行空间的 ROM 比较小的原因就是因为部分 RW-data 类型的变量被拷贝到 RAM 空间了;
拷贝RW到RAM中
最大空间(Max)
:0x00080000,即 512K 字节,此款STM32内部 FLASH 的最大空间。
用大的那个
计算程序占用的空间时,需要使用加载区域的大小
进行计算,本例子中应用程序使用的内部 FLASH 是从 0x08000000 至(0x08000000+0x000017a8)地址的空间区域。
2. ROM 空间分布表
在加载及执行空间总体描述之后,紧接着一个ROM详细地址分布表,它列出了工程中的各个段(如函数、常量数据)所在的地址 Base Addr 及占用的空间 Size。
Type
说明了该段的类型;
CODE
表示代码;
DATA
表示数据;
PAD
表示段之间的填充区域,它是无效的内容,PAD 区域往往是为了解决地址对齐的问题。
观察表中的最后一项,它的基地址是 0x0800175c,大小为 0x00000020,可知它占用的最高的地址空间为 0x0800177c,跟执行区域的最高地址 0x0000177c 一样,但它们比加载区域说明中的最高地址 0x80017a8 要小,所以我们以加载区域的大小为准。
所以这边一共使用了4k不到的空间,那么从第三页开始就可以作为其他功用了。
对内部 FLASH 的写入的一般过程
1.解锁
由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置FLASH 的控制寄存器,不能修改 FLASH 中的内容。
所以对 FLASH 写入数据前,需要先给它解锁。解锁的操作步骤如下:
(1) 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
(2) 再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB
#define FLASH_KEY1 ((uint32_t)0x45670123)
#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)
//对 FLASH 控制寄存器解锁,使能访问
void FLASH_Unlock(void)
{
if ((FLASH->CR & FLASH_CR_LOCK) != RESET) {
/* 写入确认验证码 */
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
}}
顺带一提,给flash上锁的方法
void FLASH_Lock(void)
{
FLASH->CR |= FLASH_CR_LOCK;/* 设置 FLASH 寄存器的 LOCK 位 */
}
2. 页擦除
在写入新的数据前,需要先擦除存储区域,STM32 提供了页(扇区)擦除指令和整个FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
页擦除的过程:
(1) 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何Flash 操作;
(2) 在 FLASH_CR 寄存器中,将“激活页擦除寄存器位 PER ”置 1;
(3) 用 FLASH_AR 寄存器选择要擦除的页;
(4) 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
(5) 等待 BSY 位被清零时,表示擦除完成。
/**
* @brief 擦除指定的页
* @param Page_Address: 要擦除的页地址.
* @retval FLASH Status:
可能的返回值: 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;
/* 检查参数 */
assert_param(IS_FLASH_ADDRESS(Page_Address));
/*...此处省略 XL 超大容量芯片的控制部分*/
/* 等待上一次操作完成 */
status = FLASH_WaitForLastOperation(EraseTimeout);
if (status == FLASH_COMPLETE) {
/* 若上次操作完成,则开始页擦除 */
FLASH->CR|= CR_PER_Set;
FLASH->AR = Page_Address;
FLASH->CR|= CR_STRT_Set;
/* 等待操作完成 */
status = FLASH_WaitForLastOperation(EraseTimeout);
/* 复位 PER 位 */
FLASH->CR &= CR_PER_Reset;
}
return status; /* 返回擦除结果 */
}
3. 写入数据
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还还需要配置一系列的寄存器,步骤如下:
(1) 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
(2) 将 FLASH_CR 寄存器中的 “激活编程寄存器位 PG” 置 1;
(3) 向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;
(4) 等待 BSY 位被清零时,表示写入完成。
/**
* @brief 向指定的地址写入一个字的数据(32 位)
* @param Address: 要写入的地址
* @param Data: 要写入的数据
* @retval FLASH Status:
可能的返回值: FLASH_ERROR_PG,
* FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
*/
FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
FLASH_Status status = FLASH_COMPLETE;
__IO uint32_t tmp = 0;
/* 检查参数 */
assert_param(IS_FLASH_ADDRESS(Address));
/*...此处省略 XL 超大容量芯片的控制部分*/
/* Wait for last operation to be completed */
status = FLASH_WaitForLastOperation(ProgramTimeout);
if (status == FLASH_COMPLETE) {
/* 若上次操作完成,则开始页入低 16 位的数据(输入参数的第 1 部分) */
FLASH->CR |= CR_PG_Set;
*(__IO uint16_t*)Address = (uint16_t)Data;
/* 等待上一次操作完成 */
status = FLASH_WaitForLastOperation(ProgramTimeout);
if (status == FLASH_COMPLETE) {
/* 若上次操作完成,则开始页入高 16 位的数据(输入参数的第 2 部分) */
tmp = Address + 2;
*(__IO uint16_t*) tmp = Data >> 16;
/* 等待操作完成 */
status = FLASH_WaitForLastOperation(ProgramTimeout);
/* 复位 PG 位 */
FLASH->CR &= CR_PG_Reset;
} else {
/* 复位 PG 位 */
FLASH->CR &= CR_PG_Reset;
}
}
return status; /* 返回写入结果 */
}