STM32读写内部FLASH

注: 本文是学习野火的指南针开发板过程的学习笔记,可能有误,详细请看B站野火官方配套视频教程(这个教程真的讲的很详细,请给官方三连吧)

01 STM32的内部FLASH简介

在STM32芯片内部有一个FLASH存储器,它主要用于存储代码,我们在电脑上编写好应用程序后,使用下载器把编译后的代码文件烧录到该内部FLASH中,由于FLASH存储器的内容在掉电后不会丢失,芯片重新上电复位后,内核可从内部FLASH中加载代码并运行:

除了使用外部的工具(如下载器)读写内部FLASH外,STM32芯片在运行的时候,也能对自身的内部FLASH进行读写,因此,若内部FLASH存储了应用程序后还有剩余的空间,我们可以把它像外部SPI-FLASH那样利用起来,存储一些程序运行时产生的需要掉电保存的数据。

由于访问内部FLASH的速度要比外部的SPI-FLASH快得多,所以在紧急状态下常常会使用内部FLASH存储关键记录;为了防止应用程序被抄袭,有的应用会禁止读写内部FLASH中的内容,或者在第一次运行时计算加密信息并记录到某些区域,然后删除自身的部分加密代码,这些应用都涉及到内部FLASH的操作。

STM32的内部FLASH包含主存储器、系统存储器以及选项字节区域,它们的地址分布及大小如下:

1. 内部FLASH的构成

FLASH的各个存储区域的说明如下:

  • 主存储器
    一般我们说STM32内部FLASH的时候,都是指这个主存储器区域,它是存储用户应用程序的空间,芯片型号说明中的256K FLASH、512K FLASH都是指这个区域的大小。
    主存储器分为256页,每页大小为2KB,共512KB。这个分页的概念,实质就是FLASH存储器的扇区,与其它FLASH一样,在写入数据前,要先按页(扇区)擦除。

注意上表中的主存储器是本实验板使用的STM32ZET6型号芯片的参数,即STM32F1大容量产品。若使用超大容量、中容量或小容量产品,它们主存储器的页数量、页大小均有不同,使用的时候要注意区分。

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

  • 系统存储区
    系统存储区是用户不能访问的区域,它在芯片出厂时已经固化了启动代码,它负责实现串口、USB以及CAN等ISP烧录功能。
  • 选项字节
    选项字节用于配置FLASH的读写保护、待机/停机复位、软件/硬件看门狗等功能,这部分共16字节。可以通过修改FLASH的选项控制寄存器修改。

02 对内部FLASH的写入过程

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 位被清零时,表示写入完成

03 查看工程的空间分布

打开map文件后,查看文件最后部分的区域,可以看到一段以"Memory Map of the image"开头的记录:

这一段是某工程的ROM存储器分布映像,在STM32芯片中,ROM区域的内容就是指存储到内部FLASH的代码。

1.程序ROM的加载与执行空间

上述说明中有两段分别以"Load Region LR_ROM1"及"Execution Region ER_IROM1"开头的内容,它们分别描述程序的加载及执行空间。在芯片刚上电运行时,会加载程序及数据,例如它会从程序的存储区域加载到程序的执行区域,还把一些已初始化的全局变量从ROM复制到RAM空间,以便程序运行时可以修改变量的内容。加载完成后,程序开始从执行区域开始执行

在上面map文件的描述中,我们了解到加载及执行空间的基地址(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)后的存储空间都可以作其它用途,使用这些存储空间时不会篡改应用程序空间的数据。

04 操作内部FLASH的库函数介绍

为简化编程,STM32标准库提供了一些库函数,它们封装了对内部FLASH写入数据操作寄存器的过程。

操作内部FLASH的库函数

1. FLASH解锁、上锁函数

cs 复制代码
1 #define FLASH_KEY1 ((uint32_t)0x45670123)
2 #define FLASH_KEY2 ((uint32_t)0xCDEF89AB)
3 /**
4 * @brief 对 FLASH 控制寄存器解锁,使能访问
5 * @param None
6 * @retval None
7 */
8 void FLASH_Unlock(void)
9 {
10 if ((FLASH->CR & FLASH_CR_LOCK) != RESET) {
11 /* 写入确认验证码 */
12 FLASH->KEYR = FLASH_KEY1;
13 FLASH->KEYR = FLASH_KEY2;
14 }
15 }
16
17 /**
18 * @brief 对 FLASH 控制寄存器上锁,禁止访问
19 * @param None
20 * @retval None
21 */
22 void FLASH_Lock(void)
23 {
24 /* 设置 FLASH 寄存器的 LOCK 位 */
25 FLASH->CR |= FLASH_CR_LOCK;
26 }

解锁的时候,它对 FLASH_KEYR 寄存器写入两个解锁参数,上锁的时候,对 FLASH_CR 寄存器的 FLASH_CR_LOCK 位置 1 。

2.设置操作位数及擦除扇区

cs 复制代码
1 /**
2 * @brief 擦除指定的页
3 * @param Page_Address: 要擦除的页地址.
4 * @retval FLASH Status:
5 可能的返回值: FLASH_BUSY, FLASH_ERROR_PG,
6 * FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
7 */
8 FLASH_Status FLASH_ErasePage(uint32_t Page_Address)
9 {
10 FLASH_Status status = FLASH_COMPLETE;
11 /* 检查参数 */
12 assert_param(IS_FLASH_ADDRESS(Page_Address));
13 /*... 此处省略 XL 超大容量芯片的控制部分 */
14 /* 等待上一次操作完成 */
15 status = FLASH_WaitForLastOperation(EraseTimeout);
16
17 if (status == FLASH_COMPLETE) {
18 /* 若上次操作完成,则开始页擦除 */
19 FLASH->CR|= CR_PER_Set;
20 FLASH->AR = Page_Address;
21 FLASH->CR|= CR_STRT_Set;
22
23 /* 等待操作完成 */
24 status = FLASH_WaitForLastOperation(EraseTimeout);
25
26 /* 复位 PER 位 */
27 FLASH->CR &= CR_PER_Reset;
28 }
29
30 /* 返回擦除结果 */
31 return status;
32 }

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

3.写入数据

对内部FLASH写入数据不像对SDRAM操作那样直接指针操作就完成了,还要设置一系列的寄存器,利用FLASH_ProgramWord和FLASH_ProgramHalfWord函数可按字、半字节单位写入数据:

cs 复制代码
1 /**
2 * @brief 向指定的地址写入一个字的数据(32 位)
3 * @param Address: 要写入的地址
4 * @param Data: 要写入的数据
5 * @retval FLASH Status:
6 可能的返回值: FLASH_ERROR_PG,
7 * FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
8 */
9 FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
10 {
11 FLASH_Status status = FLASH_COMPLETE;
12 __IO uint32_t tmp = 0;
13
14 /* 检查参数 */
15 assert_param(IS_FLASH_ADDRESS(Address));
16 /*... 此处省略 XL 超大容量芯片的控制部分 */
17 /* Wait for last operation to be completed */
18 status = FLASH_WaitForLastOperation(ProgramTimeout);
19
20 if (status == FLASH_COMPLETE) {
21 /* 若上次操作完成,则开始页入低 16 位的数据(输入参数的第 1 部分) */
22 FLASH->CR |= CR_PG_Set;
23
24 *(__IO uint16_t*)Address = (uint16_t)Data;
25 /* 等待上一次操作完成 */
26 status = FLASH_WaitForLastOperation(ProgramTimeout);
27
28 if (status == FLASH_COMPLETE) {
29 /* 若上次操作完成,则开始页入高 16 位的数据(输入参数的第 2 部分) */
30 tmp = Address + 2;
31
32 *(__IO uint16_t*) tmp = Data >> 16;
33
34 /* 等待操作完成 */
35 status = FLASH_WaitForLastOperation(ProgramTimeout);
36
37 /* 复位 PG 位 */
38 FLASH->CR &= CR_PG_Reset;
39 } else {
40 /* 复位 PG 位 */
41 FLASH->CR &= CR_PG_Reset;
42 }
43 }
44
45 /* 返回写入结果 */
46 return status;
47 }

从函数代码可了解到,使用指针进行赋值操作前设置了PG寄存器位,在赋值操作后,调用了FLASH_WaitForLastOperation函数等待写操作完毕。HalfWord和Byte操作宽度的函数执行过程类似。

05 实验:读写内部****FLASH

bsp_internal_Flash.h

cs 复制代码
#ifndef __INTERNAL_FLASH_H
#define	__INTERNAL_FLASH_H

#include "stm32f10x.h"

/* STM32大容量产品每页大小2KByte,中、小容量产品每页大小1KByte */
//先根据芯片类型定义了宏 FLASH_PAGE_SIZE
#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

//写入的起始地址与结束地址
/*WRITE_START_ADDR 和 WRITE_END_ADDR 定义了后面本工程测试读写内部 FLASH 的
起始地址与结束地址,这部分区域与 map 文件指示的程序本身占用的空间不重合,所以在后面
修改这些地址的内容时,它不会把自身的程序修改掉*/
#define WRITE_START_ADDR  ((uint32_t)0x08008000)
#define WRITE_END_ADDR    ((uint32_t)0x0800C000)



typedef enum 
{
	FAILED = 0, 
  PASSED = !FAILED
} TestStatus;


int InternalFlash_Test(void);



#endif /* __INTERNAL_FLASH_H */

一切准备就绪,可以开始对内部 FLASH 进行擦写,这个过程不需要初始化任何外设,只要按解
锁、擦除及写入的流程走就可以

(bsp_internal_Flash.c

cs 复制代码
/**
  ******************************************************************************
  * @file    bsp_internalFlash.c
  * @author  fire
  * @version V1.0
  * @date    2015-xx-xx
  * @brief   内部FLASH读写测试范例
  ******************************************************************************
  * @attention
  *
  * 实验平台:野火  STM32 霸道 开发板  
  * 论坛    :http://www.firebbs.cn
  * 淘宝    :https://fire-stm32.taobao.com
  *
  ******************************************************************************
  */
  
#include "./internal_flash/bsp_internal_flash.h"   
#include "./usart/bsp_usart.h"

/**
  * @brief  InternalFlash_Test,对内部FLASH进行读写测试
  * @param  None
  * @retval None
  */
int InternalFlash_Test(void)
{
	uint32_t EraseCounter = 0x00; 	//记录要擦除多少页
	uint32_t Address = 0x00;				//记录写入的地址
	uint32_t Data = 0x3210ABCD;			//记录写入的数据
	uint32_t NbrOfPage = 0x00;			//记录写入多少页
	
	FLASH_Status FLASHStatus = FLASH_COMPLETE; //记录每次擦除的结果	
	TestStatus MemoryProgramStatus = PASSED;//记录整个测试结果
	

  /* 解锁 */
  FLASH_Unlock();

  /* 计算要擦除多少页 */
  NbrOfPage = (WRITE_END_ADDR - WRITE_START_ADDR) / FLASH_PAGE_SIZE;

  /* 清空所有标志位 */
  FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);	

  /* 按页擦除*/
  for(EraseCounter = 0; (EraseCounter < NbrOfPage) && (FLASHStatus == FLASH_COMPLETE); EraseCounter++)
  {
    FLASHStatus = FLASH_ErasePage(WRITE_START_ADDR + (FLASH_PAGE_SIZE * EraseCounter));
  
	}
  
  /* 向内部FLASH写入数据 */
  Address = WRITE_START_ADDR;

  while((Address < WRITE_END_ADDR) && (FLASHStatus == FLASH_COMPLETE))
  {
    FLASHStatus = FLASH_ProgramWord(Address, Data);
    Address = Address + 4;
  }

  FLASH_Lock();
  
  /* 检查写入的数据是否正确 */
  Address = WRITE_START_ADDR;

  while((Address < WRITE_END_ADDR) && (MemoryProgramStatus != FAILED))
  {
    if((*(__IO uint32_t*) Address) != Data)
    {
      MemoryProgramStatus = FAILED;
    }
    Address += 4;
  }
	return MemoryProgramStatus;
}

main.c

cs 复制代码
int main(void)
{ 	
	/*初始化USART,配置模式为 115200 8-N-1*/
    USART_Config();
	LED_GPIO_Config();
 
	LED_BLUE;
	printf("\r\n 欢迎使用野火  STM32  开发板。\r\n");	
	printf("正在进行读写内部FLASH实验,请耐心等待\r\n");
	
	if(InternalFlash_Test()== PASSED)
	{
		LED_GREEN;
		printf("读写内部FLASH测试成功\r\n");

	}
	else
	{
		printf("读写内部FLASH测试失败\r\n");
		LED_RED;
	}
	
	
  while(1)
	{	} 
}

06 实际应用

MyFLASH.c

cs 复制代码
#include "stm32f10x.h"                  // Device header

/**
  * 函    数:FLASH读取一个32位的字
  * 参    数:Address 要读取数据的字地址
  * 返 回 值:指定地址下的数据
  */
uint32_t MyFLASH_ReadWord(uint32_t Address)
{
	return *((__IO uint32_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个16位的半字
  * 参    数:Address 要读取数据的半字地址
  * 返 回 值:指定地址下的数据
  */
uint16_t MyFLASH_ReadHalfWord(uint32_t Address)
{
	return *((__IO uint16_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH读取一个8位的字节
  * 参    数:Address 要读取数据的字节地址
  * 返 回 值:指定地址下的数据
  */
uint8_t MyFLASH_ReadByte(uint32_t Address)
{
	return *((__IO uint8_t *)(Address));	//使用指针访问指定地址下的数据并返回
}

/**
  * 函    数:FLASH全擦除
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,FLASH的所有页都会被擦除,包括程序文件本身,擦除后,程序将不复存在
  */
void MyFLASH_EraseAllPages(void)
{
	FLASH_Unlock();					//解锁
	FLASH_EraseAllPages();			//全擦除
	FLASH_Lock();					//加锁
}

/**
  * 函    数:FLASH页擦除
  * 参    数:PageAddress 要擦除页的页地址
  * 返 回 值:无
  */
void MyFLASH_ErasePage(uint32_t PageAddress)
{
	FLASH_Unlock();					//解锁
	FLASH_ErasePage(PageAddress);	//页擦除
	FLASH_Lock();					//加锁
}

/**
  * 函    数:FLASH编程字
  * 参    数:Address 要写入数据的字地址
  * 参    数:Data 要写入的32位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)
{
	FLASH_Unlock();							//解锁
	FLASH_ProgramWord(Address, Data);		//编程字
	FLASH_Lock();							//加锁
}

/**
  * 函    数:FLASH编程半字
  * 参    数:Address 要写入数据的半字地址
  * 参    数:Data 要写入的16位数据
  * 返 回 值:无
  */
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
{
	FLASH_Unlock();							//解锁
	FLASH_ProgramHalfWord(Address, Data);	//编程半字
	FLASH_Lock();							//加锁
}

MyFLASH.h

cs 复制代码
#ifndef __MYFLASH_H
#define __MYFLASH_H

uint32_t MyFLASH_ReadWord(uint32_t Address);
uint16_t MyFLASH_ReadHalfWord(uint32_t Address);
uint8_t MyFLASH_ReadByte(uint32_t Address);

void MyFLASH_EraseAllPages(void);
void MyFLASH_ErasePage(uint32_t PageAddress);

void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

#endif
cs 复制代码
#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"

#define STORE_START_ADDRESS		0x0800FC00		//存储的起始地址
#define STORE_COUNT				512				//存储数据的个数

uint16_t Store_Data[STORE_COUNT];				//定义SRAM数组

/**
  * 函    数:参数存储模块初始化
  * 参    数:无
  * 返 回 值:无
  */
void Store_Init(void)
{
	/*判断是不是第一次使用*/
	if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)	//读取第一个半字的标志位,if成立,则执行第一次使用的初始化
	{
		MyFLASH_ErasePage(STORE_START_ADDRESS);					//擦除指定页
		MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);	//在第一个半字写入自己规定的标志位,用于判断是不是第一次使用
		for (uint16_t i = 1; i < STORE_COUNT; i ++)				//循环STORE_COUNT次,除了第一个标志位
		{
			MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);		//除了标志位的有效数据全部清0
		}
	}
	
	/*上电时,将闪存数据加载回SRAM数组,实现SRAM数组的掉电不丢失*/
	for (uint16_t i = 0; i < STORE_COUNT; i ++)					//循环STORE_COUNT次,包括第一个标志位
	{
		Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);		//将闪存的数据加载回SRAM数组
	}
}

/**
  * 函    数:参数存储模块保存数据到闪存
  * 参    数:无
  * 返 回 值:无
  */
void Store_Save(void)
{
	MyFLASH_ErasePage(STORE_START_ADDRESS);				//擦除指定页
	for (uint16_t i = 0; i < STORE_COUNT; i ++)			//循环STORE_COUNT次,包括第一个标志位
	{
		MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);	//将SRAM数组的数据备份保存到闪存
	}
}

/**
  * 函    数:参数存储模块将所有有效数据清0
  * 参    数:无
  * 返 回 值:无
  */
void Store_Clear(void)
{
	for (uint16_t i = 1; i < STORE_COUNT; i ++)			//循环STORE_COUNT次,除了第一个标志位
	{
		Store_Data[i] = 0x0000;							//SRAM数组有效数据清0
	}
	Store_Save();										//保存数据到闪存
}

Store.c

cs 复制代码
#ifndef __STORE_H
#define __STORE_H

extern uint16_t Store_Data[];

void Store_Init(void);
void Store_Save(void);
void Store_Clear(void);

#endif

Store.h


cs 复制代码
#include "Store.h"//C8T6自带闪存
int main(void)
{
    Store_Init( );				//参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失
/*读取*/
   	KP_R=Store_Data[1]  ;
	KI_R=Store_Data[2]  ;
	KD_R=Store_Data[3]  ;

}
/*存入*/
Store_Data[1] =KP_R ;
Store_Data[2] =KI_R ;
Store_Data[3] =KD_R ;
Store_Save();			//将Store_Data的数据备份保存到闪存,实现掉电不丢失

main.c

相关推荐
沐欣工作室_lvyiyi15 分钟前
基于单片机的智能家居窗帘控制系统设计(论文+源码)
单片机·嵌入式硬件·毕业设计·智能家居·智能窗帘
嵌入式分享32 分钟前
嵌入式分享#41:RK3576改UART波特率【精简版】
linux·嵌入式硬件·ubuntu·嵌入式
典则1 小时前
STM32FreeRtos入门(四)——任务状态和调度
stm32·单片机·嵌入式硬件
充哥单片机设计1 小时前
【STM32项目开源】基于STM32的智能天然气火灾监控
stm32·单片机·嵌入式硬件
充哥单片机设计1 小时前
【STM32项目开源】基于STM32的智能仓库火灾检测系统
stm32·单片机·嵌入式硬件
就叫飞六吧3 小时前
普中stm32大Dap烧录流程
stm32
A9better4 小时前
嵌入式开发学习日志38——stm32之看门狗
stm32·嵌入式硬件·学习
小莞尔5 小时前
【51单片机】【protues仿真】基于51单片机智能路灯控制系统
c语言·stm32·单片机·嵌入式硬件·51单片机
辰哥单片机设计14 小时前
TT直流减速电机(STM32)
stm32
A9better14 小时前
嵌入式开发学习日志36——stm32之USART串口通信前述
stm32·单片机·嵌入式硬件·学习