目录
1. FLASH简介
1.1 存储器映像
STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程。
类型 | 起始地址 | 存储器 | 用途 |
ROM | 0x0800 0000 | 程序存储器Flash | 存储C语言编译后的程序代码 |
0x1FFF F000 | 系统存储器 | 存储C语言编译后的程序代码 | |
0x1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 | |
RAM | 0x2000 0000 | 运行内存SRAM | 存储运行过程中的临时变量 |
0x4000 0000 | 运行内存SRAM | 存储各个外设的配置参数 | |
0xE000 0000 | 内核外设寄存器 | 存储内核各个外设的配置参数 |
1.2 读写FLASH的用途
(1)利用程序存储器的剩余空间来保存掉电不丢失的用户数据
(2)通过在程序中编程(IAP),实现程序的自我更新
在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序。
在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序。
1.3 闪存容量
1.3.1 命名规则
我们先来了解一下STM32系列的命名规则:
对于STM32F1系列闪存存储容量:
小容量:16K和32K字节的闪存
中容量:64K和128K字节的闪存
大容量:256K、348K和512K字节的闪存
1.3.2 闪存模块组织
(1)小容量
(2)中容量
(3)大容量
其中,对于信息块分为两部分:
系统存储器(启动程序代码):是用于存放在系统存储器自举模式下的启动程序,这个区域只保留给ST使用,启动程序使用USART1串行接口实现对闪存存储器的编程;ST在生产线上对这个区域编程并锁定以防止用户擦写。
用户选择字节:选项字节。
注意:
- 对于主存储器,擦除和写入是以页为单位的;
- 写入前必须擦除;
- 擦除必须以最小单位进行
- 擦除后数据位全变为1
- 数据只能1写0,不能0写1
- 擦除和写入都需要等待忙
1.4 FLASH基本结构
以C8T6为例:
1.5 FLASH解锁
FPEC共有三个键值:
RDPRT键 = 0x000000A5
KEY1 = 0x45670123
KEY2 = 0xCDEF89AB
解锁:
复位后,FPEC被保护,不能写入FLASH_CR
在FLASH_KEYR先写入KEY1,再写入KEY2,解锁
错误的操作序列会在下次复位前锁死FPEC和FLASH_CR
加锁:
设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR
1.6 使用指针访问存储器
1.6.1 使用指针读指定地址下的存储器
uint16_t Data = *((__IO uint16_t *)(0x08000000));
(0x08000000):这是一个十六进制的地址值。上面我们也说了,STM32F1系列的起始地址为0x08000000,如果想要别的地址下的数据则可以对其进行更改。
(__IO uint16_t *):这是一个类型转换,将前面的地址值(0x08000000)强制转换为一个指向__IO uint16_t类型的指针。如果想要转变其他类型的数据可以将uint16_t更改为及其他的如:uint8_t、uint32_t、float或者double等。
#define __IO volatile
__IO:在这里是一个宏定义,用于表示该指针所指向的内存区域具有特定的访问属性,与 volatile 关键字相关,意味着该内存位置的值可能会被硬件或其他异步操作修改,编译器不会对其进行优化,每次访问都从内存中读取实际值。
1.6.2 使用指针写指定地址下的存储器
*((__IO uint16_t *)(0x08000000)) = 0x1234;
1.7 闪存全擦除过程
首先判断有没有被锁住,锁住执行解锁过程,没解锁继续运行,不过对于STM32给的库函数并没有这层判断,库函数直接执行解锁过程,管你锁没锁都解一次:
解锁完成后置,控制寄存器的MER位为1,在置STRT为1,STRT位是开始位,当开始运行,发现MER位为1,则开始执行全擦除:
然后擦除等待:
1.8 闪存页擦除过程
该过程与全擦除类似:
1.9 程序存储器编程
解锁:
置控制寄存器的PG位为1,表示即将写入数据:
以半字写入数据(只能以半字写入):
字:32位数据
半字:16位数据
字节:8位数据
等待忙:
1.10 选项字节
- RDP:写入RDPRT键(0x000000A5)后解除读保护
- USER:配置硬件看门狗和进入停机/待机模式是否产生复位
- Data0/1:用户可自定义使用
- WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)
2. STM32代码编写
2.1 FLASH读取一个32位的字
/**
* 函 数:FLASH读取一个32位的字
* 参 数:Address 要读取数据的字地址
* 返 回 值:指定地址下的数据
*/
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
return *((__IO uint32_t *)(Address)); //使用指针访问指定地址下的数据并返回
}
2.2 FLASH读取一个16位的半字
/**
* 函 数:FLASH读取一个16位的半字
* 参 数:Address 要读取数据的半字地址
* 返 回 值:指定地址下的数据
*/
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
return *((__IO uint16_t *)(Address)); //使用指针访问指定地址下的数据并返回
}
2.3 FLASH读取一个8位的字节
/**
* 函 数:FLASH读取一个8位的字节
* 参 数:Address 要读取数据的字节地址
* 返 回 值:指定地址下的数据
*/
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
return *((__IO uint8_t *)(Address)); //使用指针访问指定地址下的数据并返回
}
2.4 FLASH全擦除
大多都是STM32封装好的函数,按照上面演示的流程直接调用即可:
/**
* 函 数:FLASH全擦除
* 参 数:无
* 返 回 值:无
* 说 明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在
*/
void MyFLASH_EraseAllPages(void)
{
FLASH_Unlock(); //解锁
FLASH_EraseAllPages(); //全擦除
FLASH_Lock(); //加锁
}
2.5 FLASH页擦除
/**
* 函 数:FLASH页擦除
* 参 数:PageAddress 要擦除页的页地址
* 返 回 值:无
*/
void MyFLASH_ErasePage(uint32_t PageAddress)
{
FLASH_Unlock(); //解锁
FLASH_ErasePage(PageAddress); //页擦除
FLASH_Lock(); //加锁
}
2.6 FLASH编程字
/**
* 函 数:FLASH编程字
* 参 数:Address 要写入数据的字地址
* 参 数:Data 要写入的32位数据
* 返 回 值:无
*/
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
FLASH_Unlock(); //解锁
FLASH_ProgramWord(Address, Data); //编程字
FLASH_Lock(); //加锁
}
2.7 FLASH编程半字
/**
* 函 数:FLASH编程半字
* 参 数:Address 要写入数据的半字地址
* 参 数:Data 要写入的16位数据
* 返 回 值:无
*/
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
FLASH_Unlock(); //解锁
FLASH_ProgramHalfWord(Address, Data); //编程半字
FLASH_Lock(); //加锁
}