STM32入门教程(FLASH闪存篇)

参考教程:[15-1] FLASH闪存_哔哩哔哩_bilibili

1、STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程

(1)读写FLASH的用途:

①利用程序存储器的剩余空间来保存掉电不丢失的用户数据。

②通过在程序中编程(IAP),实现程序的自我更新。

(2)在线编程(In-Circuit Programming – ICP)用于更新程序存储器的全部内容,它通过JTAG、SWD协议或系统加载程序(Bootloader)下载程序。

(3)在程序中编程(In-Application Programming – IAP)可以使用微控制器支持的任一种通信接口下载程序。

2、闪存模块组织:

3、FLASH基本结构:

4、FLASH解锁:

(1)FPEC共有三个键值:RDPRT键 = 0x000000A5;KEY1 = 0x45670123;KEY2 = 0xCDEF89AB

(2)解锁:

①复位后,FPEC被保护,默认不能写入FLASH_CR(控制寄存器)。

在FLASH_KEYR先写入KEY1,再写入KEY2,即可解锁

③错误的操作序列会在下次复位前锁死FPEC和FLASH_CR。

(3)加锁:

设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR。

5、使用指针访问存储器:

(1)使用指针读指定地址下的存储器:

	uint16_t Data = *((__IO uint16_t *)(0x08000000));  //读地址0x08000000下的2个字节(16位,半字)

(2)使用指针写指定地址下的存储器:

*((__IO uint16_t *)(0x08000000)) = 0x1234;  //写2个字节(16位,半字)的数据0x1234到地址0x08000000

其中:

#define    __IO    volatile  //volatile在逻辑上没有什么作用,只是为了防止编译器优化

6、程序存储器编程:

(1)首先读取控制寄存器的LOCK位,如果LOCK=1,那就需要解锁(在KEYR寄存器先写入KEY1再写入KEY2)才能继续操作,如果LOCK=0,可以往下进行编程(写入)操作。

(2)置CR寄存器的PG位为1,开始编程操作,不过STM32的闪存会在写入数据之前会检查指定地址有没有被擦除,如果未被擦除,STM32不执行写入操作。(另外每次写入都必须一次性写半字,也就是16位

(3)编程过程需要一定时间,在这段时间里程序等待CR寄存器的BSY位被置为0,也就是等待编程操作完成。

(4)“读出并验证所有页的数据”这一步是测试程序的步骤,一般程序不执行这一步。

7、程序存储器页擦除:(被擦除部分全部置为1

(1)首先读取控制寄存器的LOCK位,如果LOCK=1,那就需要解锁(在KEYR寄存器先写入KEY1再写入KEY2)才能继续操作,如果LOCK=0,可以往下进行擦除操作。

(2)首先置CR寄存器的PER位为1,在AR寄存器中写入要擦除的页,再置STRT位为1,其中置STRT为1是触发条件,触发芯片开始工作,而PER=1则决定芯片需要进行页擦除操作。

(3)擦除过程需要一定时间,在这段时间里程序等待CR寄存器的BSY位被置为0,也就是等待擦除操作完成。

(4)“读出并验证所有页的数据”这一步是测试程序的步骤,一般程序不执行这一步。

8、程序存储器全擦除:

(1)首先读取控制寄存器的LOCK位,如果LOCK=1,那就需要解锁(在KEYR寄存器先写入KEY1再写入KEY2)才能继续操作,如果LOCK=0,可以往下进行擦除操作。

(2)首先置CR寄存器的MER位为1,置STRT位为1,其中置STRT为1是触发条件,触发芯片开始工作,而MER=1则决定芯片需要进行全擦除操作。

(3)擦除过程需要一定时间,在这段时间里程序等待CR寄存器的BSY位被置为0,也就是等待擦除操作完成。

(4)“读出并验证所有页的数据”这一步是测试程序的步骤,一般程序不执行这一步。

9、选项字节:(nXXX代表XXX的反码)

(1)信息块的组织结构:

①RDP:写入RDPRT键(0x000000A5)后解除读保护。

②USER:配置硬件看门狗和进入停机/待机模式是否产生复位。

③Data0/1:用户可自定义使用。

④WRP0/1/2/3:配置写保护,每一个位对应保护4个存储页(中容量)。

(2)选项字节编程:

①解锁闪存(解锁LOCK位),检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作。

②解锁FLASH_CR的OPTWRE位。

③设置FLASH_CR的OPTPG位为1。

④写入要编程的半字到指定的地址。

⑤等待BSY位变为0。

⑥读出写入的地址并验证数据。

(3)选项字节擦除:

①解锁闪存(解锁LOCK位),检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作。

②解锁FLASH_CR的OPTWRE位。

③设置FLASH_CR的OPTER位为1。

④设置FLASH_CR的STRT位为1。

⑤等待BSY位变为0。

⑥读出被擦除的选择字节并做验证。

10、器件电子签名:电子签名存放在闪存存储器模块的系统存储区域,包含的芯片识别信息在出厂时编写,不可更改,使用指针读指定地址下的存储器可获取电子签名。

(1)闪存容量寄存器:基地址为0x1FFF F7E0,大小为16位。

(2)产品唯一身份标识寄存器:基地址为0x1FFF F7E8,大小为96位。

11、读写内部FLASH:

(1)按照下图所示接好电路,并将OLED显示屏的项目文件夹复制一份作为模板使用。

(2)在stm32f10x_flash.h文件中有闪存模块相关的函数。

[1]FLASH_Unlock函数:解锁LOCK位。

[2]FLASH_Lock函数:加锁LOCK位。

[3]FLASH_ErasePage函数:闪存擦除某一页。

[4]FLASH_EraseAllPages函数:闪存擦除全部页。

[5]FLASH_EraseOptionBytes函数:擦除选项字节。

[6]FLASH_ProgramWord函数:往闪存指定地址写一个字(32位)。

[7]FLASH_ProgramHalfWord函数:往闪存指定地址写半个字(16位)。

[8]FLASH_ITConfig函数:开启中断。

[9]FLASH_GetFlagStatus函数:获取状态标志位。

[10]FLASH_ClearFlag函数:清除状态标志位。

[11]FLASH_GetStatus函数:获取当前状态。

[12]FLASH_WaitForLastOperation函数:等待上一次操作完成(等待BSY=0)。

(3)在项目的System组中添加MyFLASH.h文件和MyFLASH.c文件用于封装闪存模块的代码。

①MyFLASH.h文件:

#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_ErasePages(uint32_t PageAddress);
void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data);
void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);

#endif

②MyFLASH.c文件:

#include "stm32f10x.h"                  // Device header

uint32_t MyFLASH_ReadWord(uint32_t Address)  //读取Address地址下的一个字
{
	return *((__IO uint32_t *)(Address));
}

uint16_t MyFLASH_ReadHalfWord(uint32_t Address)  //读取Address地址下的半个字
{
	return *((__IO uint16_t *)(Address));
}

uint8_t MyFLASH_ReadByte(uint32_t Address)  //读取Address地址下的一个字节
{
	return *((__IO uint8_t *)(Address));
}

void MyFLASH_EraseAllPages(void)  //擦除全部页
{
	FLASH_Unlock();  //解锁
	FLASH_EraseAllPages(); //擦除全部页
	FLASH_Lock();    //加锁
}

void MyFLASH_ErasePages(uint32_t PageAddress)  //擦除页起始地址PageAddress下的页
{
	FLASH_Unlock();  //解锁
	FLASH_ErasePage(PageAddress); //擦除指定页
	FLASH_Lock();    //加锁
}

void MyFLASH_ProgramWord(uint32_t Address, uint32_t Data)  //往Address地址写一个字Data
{
	FLASH_Unlock();  //解锁
	FLASH_ProgramWord(Address, Data); //往指定地址写一个字
	FLASH_Lock();    //加锁
}

void MyFLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)  //往Address地址写半个字Data
{
	FLASH_Unlock();  //解锁
	FLASH_ProgramHalfWord(Address, Data); //往指定地址写半个字
	FLASH_Lock();    //加锁
}

(4)在项目的System组中添加Store.h文件和Store.c文件用于封装业务层的代码。

①Store.h文件:

#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.c文件:

#include "stm32f10x.h"                  // Device header
#include "MyFLASH.h"

#define STORE_START_ADDRESS  0x800FC00  //使用闪存0x800FC00的一页进行调试(只要程序占用空间不大,该页一般空闲)
#define STORE_COUNT          512        //一页有512个半字

uint16_t Store_Data[STORE_COUNT];       //存储在SRAM中的数组,用于暂存FLASH中的数据

void Store_Init(void)
{
	if(MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)  //闪存的0x800FC00页只初始化一次即可
	{
		MyFLASH_ErasePages(STORE_START_ADDRESS);  //擦除0x800FC00页
		MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);
		//0x800FC00页的第一个半字用来做标志,如果初始化过一次,数据掉电不丢失,重新上电后该半字仍为0xA5A5
		for(uint16_t i = 1; i < STORE_COUNT; i++)  //该页剩下的空间全部初始化为0
		{
			MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);
		}
	}
	for(uint16_t i = 0; i < STORE_COUNT; i++)  //将0x800FC00页的数据拷贝到SARM的数组中,便于程序修改
	{
		Store_Data[i] = MyFLASH_ReadHalfWord(STORE_START_ADDRESS + i * 2);
	}
}

void Store_Save(void)  //将SARM中的数据保存到闪存的0x800FC00页
{
	MyFLASH_ErasePages(STORE_START_ADDRESS);  //先擦除原来保存的数据
	for(uint16_t i = 0; i < STORE_COUNT; i++) //把SRAM中数组的数据逐半字写入FLASH
	{
		 MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, Store_Data[i]);
	}
}

void Store_Clear(void)  //清空0x800FC00页保存的数据(不是擦除)
{
	for(uint16_t i = 1; i < STORE_COUNT; i++)  //先把SARM中暂存的数据请空
	{
		Store_Data[i] = 0;
	}
	Store_Save();  //将SARM中的数据保存到闪存的0x800FC00页
}

(5)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中进行调试。

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "Key.h"
#include "Store.h"

uint8_t KeyNum;

int main()
{
	OLED_Init();
	Key_Init();
	Store_Init();
	
	OLED_ShowString(1,1,"Flag:");
	OLED_ShowString(2,1,"Data:");
	
	while(1)
	{
		KeyNum = Key_GetNum();
		
		if(KeyNum == 1)  //按下按键1,修改闪存数据
		{
			Store_Data[1] += 1;
			Store_Data[2] += 2;
			Store_Data[3] += 3;
			Store_Data[4] += 4;
			Store_Save();       //保存数据
		}
		if(KeyNum == 2)  //按下按键2,清空指定页STORE_START_ADDRESS的数据
		{
			Store_Clear();      //清空数据
		}
		
		OLED_ShowHexNum(1,6,Store_Data[0],4);  //0xA5A5(标志)
		OLED_ShowHexNum(3,1,Store_Data[1],4);
		OLED_ShowHexNum(3,6,Store_Data[2],4);
		OLED_ShowHexNum(4,1,Store_Data[3],4);
		OLED_ShowHexNum(4,6,Store_Data[4],4);
	}
}

(6)使用STM32 ST-LINK Utility可以查看闪存中的数据存储情况,在程序下载到开发板中后,点击红框所在的按钮,然后把Address修改为0x0800FC00,就可以查看0x0800FC00这一页的存储内容。(如果想下载新程序,需要点击蓝框所在按钮,否则ST-LINK会被占用)

(7)程序最大占用空间可以设置,当程序所占空间超过设定值时程序将无法编译。

12、读取芯片ID:

(1)按照下图所示接好电路,并将OLED显示屏的项目文件夹复制一份作为模板使用。

(2)产品唯一身份标识寄存器的地址:

(3)在main.c文件中粘贴以下代码,然后编译,将程序下载到开发板中进行调试。

#include "stm32f10x.h"                  // Device header
#include "OLED.h"

int main(void)
{
	OLED_Init();						//OLED初始化
	
	OLED_ShowString(1, 1, "F_SIZE:");	//显示静态字符串
	OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);		//使用指针读取指定地址下的闪存容量寄存器
	
	OLED_ShowString(2, 1, "U_ID:");		//显示静态字符串
	OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);		//使用指针读取指定地址下的产品唯一身份标识寄存器
	OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
	OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
	OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
	
	while (1)
	{
		
	}
}

猜你喜欢

转载自blog.csdn.net/Zevalin/article/details/134809817