STM32F103学习笔记(3)——读写内部Flash
一、简介
在STM32芯片内部有一个 FLASH 存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部 FLASH 中,由于 FLASH 存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部 FLASH 中加载代码并运行。
STM32 的内部 FLASH 包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小见下表
- 主存储器
一般我们说 STM32 内部 FLASH 的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的 256K FLASH、512K FLASH 都是指这个区域的大小。
主存储器分为 256 页,每页大小为 2KB,共 512KB。这个分页的概念,实质就是 FLASH 存储器的扇区,与其它 FLASH 一样,在写入数据前,要先按页(扇区)擦除。
注意上表中的主存储器是本实验板使用的 STM32VET6 型号芯片的参数,即 STM32F1 大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。
主存储器是以页为单位划分的。stm32根据FLASH主存储块容量、页面的不同,系统存储器的不同,分为小容量、中容量、大容量、互联型,共四类产品。
- 小容量产品:主存储块1-32KB, 每页1KB。系统存储器2KB
- 中容量产品:主存储块64-128KB, 每页1KB。系统存储器2KB
- 大容量产品:主存储块256KB以上, 每页2KB。系统存储器2KB
- 互联型产品:主存储块256KB以上, 每页2KB。系统存储器18KB
- 系统存储区
系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB 以及 CAN 等 ISP 烧录功能。
- 选项字节
选项字节用于配置 FLASH 的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共 16 字节。可以通过修改 FLASH 的选项控制寄存器修改。
二、查看工程的空间分布
由于内部 FLASH 本身存储有程序数据,若不是有意删除某段程序代码,一般不应修改程序空间的内容,所以在使用内部 FLASH 存储其它数据前需要了解哪一些空间已经写入了程序代码,存储了程序代码的扇区都不应作任何修改。通过查询应用程序编译时产生
的“*.map”后缀文件,可以了解程序存储到了哪些区域。
打开 map 文件后,查看文件最后部分的区域,可以看到一段以 “Memory Map of the image” 开头的记录(若找不到可用查找功能定位)
观察表中的最后一项,它的基地址是 0x0800175c,大小为 0x00000020,可知它占用的
最高的地址空间为 0x0800177c,跟执行区域的最高地址 0x0000177c 一样,但它们比加载
区域说明中的最高地址 0x80017a8 要小,所以我们以加载区域的大小为准。对比表 45-1 的
内部 FLASH 页地址分布表,可知仅使用页 0 至页 2 就可以完全存储本应用程序,所以从页
3**(地址 0x08001800)**后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程
序空间的数据。
三、写入Flash
3.1 写入过程
3.1.1 解锁
由于内部 FLASH 空间主要存储的是应用程序,是非常关键的数据,为了防止误操作修改了这些内容,芯片复位后默认会给控制寄存器 FLASH_CR 上锁,这个时候不允许设置 FLASH 的控制寄存器,从而不能修改 FLASH 中的内容。
所以对 FLASH 写入数据前,需要先给它解锁。解锁的操作步骤如下:
- 往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY1 = 0x45670123
- 再往 FPEC 键寄存器 FLASH_KEYR 中写入 KEY2 = 0xCDEF89AB
3.1.2 页擦除
在写入新的数据前,需要先擦除存储区域,STM32 提供了页(扇区)擦除指令和整个 FLASH 擦除(批量擦除)的指令,批量擦除指令仅针对主存储区。
页擦除的过程如下:
- 检查 FLASH_SR 寄存器中的“忙碌寄存器位 BSY”,以确认当前未执行任何 Flash 操作;
- 在 FLASH_CR 寄存器中,将“**页擦除寄存器位 PER ”置 1;
- 用 FLASH_AR 寄存器选择要擦除的页;
- 将 FLASH_CR 寄存器中的“开始擦除寄存器位 STRT ”置 1,开始擦除;
- 等待 BSY 位被清零时,表示擦除完成。
3.1.3 写入数据
擦除完毕后即可写入数据,写入数据的过程并不是仅仅使用指针向地址赋值,赋值前还需要配置一系列的寄存器,步骤如下:
- 检查 FLASH_SR 中的 BSY 位,以确认当前未执行任何其它的内部 Flash 操作;
- 将 FLASH_CR 寄存器中的 “**编程寄存器位 PG” 置 1;
- 向指定的 FLASH 存储器地址执行数据写入操作,每次只能以 16 位的方式写入;
- 等待 BSY 位被清零时,表示写入完成。
3.2 写入函数
/* STM32大容量产品每页大小2KByte,中、小容量产品每页大小1KByte */
#if defined (STM32F10X_HD) || defined (STM32F10X_HD_VL) || defined (STM32F10X_CL) || defined (STM32F10X_XL)
#define FLASH_PAGE_SIZE ((uint16_t)0x800) // 2048
#else
#define FLASH_PAGE_SIZE ((uint16_t)0x400) // 1024
#endif
#define WRITE_START_ADDR ((uint32_t)0x08008000)
#define WRITE_END_ADDR ((uint32_t)0x0800C000)
/**
@brief 内部Flash写入
@param address -[in] 写入的地址
@param pData -[in&out] 指向需要操作的数据
@param dataLen -[in] 数据长度
@return true - 成功;false - 失败
*/
bool Internal_WriteFlash(uint32_t addrStart, uint32_t *pData, uint32_t dataLen)
{
uint32_t i = 0;
uint32_t eraseCounter = 0x00; // 记录要擦除多少页
uint32_t address = 0x00; // 记录写入的地址
uint32_t numberOfPage = 0x00; // 记录写入多少页
FLASH_Status flashStatus = FLASH_COMPLETE; // 记录每次擦除的结果
address = addrStart;
FLASH_Unlock(); // 解锁
numberOfPage = (WRITE_END_ADDR - address) / FLASH_PAGE_SIZE; // 计算要擦除多少页
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 清除所有标志
// 按页擦除
for(eraseCounter = 0; (eraseCounter < numberOfPage) && (flashStatus == FLASH_COMPLETE); eraseCounter++)
{
flashStatus = FLASH_ErasePage(address + (FLASH_PAGE_SIZE * eraseCounter));
}
for(i = 0; (i < dataLen)&&(flashStatus == FLASH_COMPLETE); i++)
{
flashStatus = FLASH_ProgramWord(address, pData[i]); // 写入一个字(32位)的数据入指定地址
address = address + 4; // 地址偏移4个字节
}
FLASH_Lock(); // 重新上锁
if(flashStatus == FLASH_COMPLETE)
{
return true;
}
return false;
}
四、读取Flash
4.1 读取函数
/**
@brief 内部Flash读取
@param address -[in] 读取的地址
@param pData -[in&out] 指向需要操作的数据
@param dataLen -[in] 数据长度
@return true - 成功;false - 失败
*/
bool Internal_ReadFlash(uint32_t addrStart, uint32_t *pData, uint32_t dataLen)
{
uint32_t i = 0;
uint32_t address = 0x00;
address = addrStart;
for(i = 0; i < dataLen; i++)
{
pData[i] = (*(__IO uint32_t*) address); // 读指定地址的一个字的数据
address += 4; // 地址偏移4个字节
}
return true;
}
五、举例
int main(void)
{
u32 in_data[5]={11,22,33,44,55};//要写入的数据
u32 out_data[5];//读存放
int i;
u8 STATUS=0;
USART1_Config();//串口1配置
GPIO_Configuration();//GPIO配置,用于点亮led
STATUS=Internal_WriteFlash(0x08001800,in_data,5);
Delay(0x02FFFF);
if(STATUS)
{
GPIO_SetBits(GPIOD, GPIO_Pin_13);//点亮led1
Internal_ReadFlash(0x08001800,out_data,5);
printf("\r\n The Five Data Is : \r\n");
for(i=0;i<5;i++)
{
printf("\r %d \r",out_data[i]);
}
}
while(1);
}
• 由 Leung 写于 2020 年 7 月 13 日
• 参考:[零死角玩转STM32——基于野火F103【指南者】开发板]
stm32——Flash读写