笔记来源于江科协议的视频

芯片采用与stm32F103C8T6

简介

•STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程,系统存储器里面存储的是BootLoader,是不允许修改的。

•读写FLASH的用途:

利用程序存储器的剩余空间来保存掉电不丢失的用户数据,一般是找最后一部分存储数据,以防覆盖程序本身代码。

通过在程序中编程(IAP)也叫OTA,实现程序的自我更新

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

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

FLASH

模块组织

这里分为三块,第一个是主存储器(程序存储器),用来存放程序代码的,第二个是信息块,里面分为启动程序代码和用户选择字节,启动程序代码就是刚才的系统存储器,存放的是原厂写入的BootLoader,用于串口下载,用户选择字节也就是选项字节,存放一些独立的参数。第三块是闪存存储器接口寄存器,这一块的存储器实际上并不是闪存,其实本质上是一个普通的外设,存储介质是SRAM,就是上面的闪存管理员,这些寄存器用来控制擦除和编程的过程,前两块是真正的闪存,第三块为闪存的管理员。

对于主存存储器,进行了分页,擦除和写保护都是以页为单位的,写入前必须擦除,擦除以最小单元进行。擦除后数据全为1,数据只能1写0,不能0写1,擦除和写入都需要等待忙,与W25Q64的特性是一致的,但是这里只有基本单位页,没有块和扇区,每页的大小都是1K,一共128页,容量为128K,对于C8T6来说,它只有64K,所以C8T6的页只有一半,0~63,总共64页,共64K,第一页的起始地址为0x0800 0000,之后就是一个字节一个地址,依次线性分配,规律:对低3位进行累加4,以400、800、C00结尾的就是起始地址,系统存储器的起始地址为0x1FFF F000,容量2K,选项字节起始地址为0x1FFF F800,容量16个字节,里面只有几个字节的配置参数。平时讲的32K、64K、128K,说的是主存储器容量,对信息块没有影响,都是固定的2K、16K。 最后就是一个闪存接口寄存器,里面包括键寄存器、SR状态寄存器、CR控制寄存器等待,外设的起始地址:0x4002 2000,每个寄存器4个字节,也就是32位。

逻辑框图

这里以C8T6为例,64K,一共64页,最后一页地址0x0800 FC00,左边是闪存存储器接口,也叫闪存编程和擦除控制器(FPEC),可以对程序存储器进行擦除和编程,也可以对选项字节进行擦除和编程,系统存储器只读,选项字节中很大一部分是配置主程序存储器的读写保护,右边黄色箭头的写入选项字节可以配置程序存储器的读写保护。

细节问题

Flash解锁

Flash操作之前需要解锁,目的是防止误操作,解锁方式是通过键寄存器写入指定的键值来实现,FPEC一共有三个键值,RDPRT解除读保护的密钥,值是0xA5,KEY1键是0x45670123,KEY2键是0xCDEF89AB。

解锁:复位后,FPEC被保护,不能写入FLASH_CR,复位后默认Flash是锁着的,在FLASH_KEYR先写入KEY1,再写入KEY2,解锁。错误的操作序列会在下次复位前锁死FPEC和FLASH_CR,一旦没有先写入KEY1再写入KEY2,整个模块就会完全锁死,除非复位。

•加锁:设置FLASH_CR中的LOCK位锁住FPEC和FLASH_CR

指针访问寄存器

*((IO uint16_t *)(0x08000000)):这个变量为地址,这里强制类型转换为uint16_t的指针类型,指针指向这个地址,最后一个✳号指针取内容,把这个指针指向的存储器取出来,值就是指定存储器的值,对于闪存是不需要解锁的,不对其进行更改。

*((IO uint16_t *)(0x08000000)) = 0x1234,指定地址写,先给定地址,再强转为指针,最后取内容,然后对其赋值,这里写数据时,要先对Flash进行解锁,还要套入写入的流程。如果这里写的是SRAM地址,如:0x2000 0000那么就不需要解锁,可以直接写入,因为SRAM在程序运行时是可读可写的,

这里——IO是一个宏定义,对应C语言的关键字volatile,意思是易变的数据,这里加上是一个保障措施,防止编译器优化,keil编译器默认优化是最低优化等级,如果要提高编译优先等级,这里就有问题了。编译器优化是为了去除无用的繁杂代码,降低代码空间,提升运行效率。这里就是告诉编译器无论怎么优化,这里不能动。

编译器还会利用缓存来加速代码,如:要频繁读写内存的某个变量,最常见的优化方式:先把变量转移到高速缓存里面,在stm32内核中,有一个类似的缓存的工作组寄存器,这类寄存器访问速度最快,把变量放缓存里,需要读写时,直接访问缓存就可以了,用完之后,再写回内存。但是程序有多线程,如:中断函数,在中断函数里改变了原始变量,但是缓存并不知道中断更改了原始变量,下次程序还看缓存的变量,就会造成数据更改不同步的问题,这时,可以在读取变量定义的前面加上volatile,告诉编译器这个变量是易变的,要直接从内存中找,不用缓存优化。

如果开启了编译器优化,在无意义加减变量,多线程更改变量,读写与硬件相关的存储器时,都需要加上volatile,防止编译器优化。

存储器操作

全擦除

第一步:读取LOCK位,看下芯片锁了没,如果LOCK=1,那就是锁住了,就执行解锁过程,解锁过程在KEYR寄存器中,先写KEY1,再写KEY2。没锁就不用解锁了,之后置控制寄存器CR的MER位为1,然后再置STRT位为1,STRT的1的触发条件,触发之后,芯片开始工作,芯片检测到MER位=1,芯片就全擦除。擦除过程开始后,程序要执行等待,判断状态寄存器SR中的BSY位,BSY芯片是否处于忙状态,等于1表示忙,等于0不忙,等于1的时候会跳回去继续判断,直到BSY等于0,跳出循环。

页擦除

这里第一步仍是解锁的流程,第二步,置控制寄存器的PER位为1,在AR地址寄存器中选择要擦除的页,最后置控制寄存器CR的STRT的位为1,最后等待BSY位,

闪存写入

STM32的闪存会在写入之前检查有没有擦除,如果没有擦除就写入,STM32则不执行写入操作,除非写入的数据全是0。 写入第一步:解锁, 第二步:需要置控制寄存器CR中的PG位为1,表示要写入数据,第三步:在指定的地址写入半字,写入操作只能以半字的形式写入, 第四步:等待忙

选项字节

这里n的意思 如:写入RDP数据时,要同时写入nRDP数据的反码,这样写入数据才是有效的,如果这两个寄存器不是互为反码,则写入的数据无效。这里硬件会自动写入。

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

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

•Data0/1:用户可自定义使用

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

选项字节编程

•检查FLASH_SR的BSY位,以确认没有其他正在进行的编程操作

•解锁FLASH_CR的OPTWRE位

•设置FLASH_CR的OPTPG位为1

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

•等待BSY位变为0

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

选项字节擦除

•检查FLASH_SR的BSY位,以确认没有其他正在进行的闪存操作(相当于事前等待)

•解锁FLASH_CR的OPTWRE位,相当于选项字节的解锁,也是先写入KEY1,再写入KEY2

•设置FLASH_CR的OPTER位为1

•设置FLASH_CR的STRT位为1

•等待BSY位变为0

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

器件电子签名

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

•闪存容量寄存器:

基地址:0x1FFF F7E0

大小:16位

•产品唯一身份标识寄存器:

基地址: 0x1FFF F7E8

大小:96位

代码

读写Flash

MyFlash.c

#include "stm32f10x.h"                  // Device header

//读一个32位的字
uint32_t MyFlash_ReadWord(uint32_t Addr)
{
	return *((__IO uint32_t*)(Addr));		//使用指针访问指定地址下的数据并返回
}

//读一个32位的字
uint16_t MyFlash_ReadHalfWord(uint32_t Addr)
{
	return *((__IO uint16_t*)(Addr));		//使用指针访问指定地址下的数据并返回
}

//读一个32位的字
uint8_t MyFlash_ReadByte(uint32_t Addr)
{
	return *((__IO uint8_t*)(Addr));		//使用指针访问指定地址下的数据并返回
}


//全擦除
void MyFlash_EraserAllPage(void)
{
	FLASH_Unlock();		//开锁
	FLASH_EraseAllPages();			//全擦除
	FLASH_Lock();		//关锁
}

void MyFlash_EraserPage(uint32_t Addr)
{
	FLASH_Unlock();		//开锁
	FLASH_ErasePage(Addr);		//写入页擦除
	FLASH_Lock();		//关锁
}

//写入字
void MyFlash_ProgramWord(uint32_t Addr, uint32_t data)
{
	FLASH_Unlock();		//开锁
	FLASH_ProgramWord(Addr,data);			//写入字
	FLASH_Lock();		//关锁
}

//写入半字
void MyFlash_ProgramHalfWord(uint32_t Addr, uint32_t data)
{
	FLASH_Unlock();		//开锁
	FLASH_ProgramHalfWord(Addr,data);			//写入半字
	FLASH_Lock();		//关锁
}

Store.c

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

#define STORE_START_ADDRESS 0x0800FC00
#define STORE_COUNT   512

uint16_t Store_Data[STORE_COUNT];


void Store_Init(void)
{
	if(MyFlash_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)		//如果是第一次读写
	{
		MyFlash_EraserPage(STORE_START_ADDRESS);			//擦除
		MyFlash_ProgramHalfWord(STORE_START_ADDRESS,0xA5A5);			//写上自己的标志位
		for(uint16_t i=1;i<STORE_COUNT;i++)
		{
			MyFlash_ProgramHalfWord(STORE_START_ADDRESS+i*2,0x0000);		//除了标志位全部清0
		}
	}
	for(uint16_t i=0;i<STORE_COUNT;i++)
	{
		Store_Data[i] = MyFlash_ReadHalfWord(STORE_START_ADDRESS+i*2);
	}
}

//保存数据   存到闪存里面
void Store_Save(void)
{
	MyFlash_EraserPage(STORE_START_ADDRESS);			//擦除
	for(uint16_t i=0;i<STORE_COUNT;i++)
	{
		MyFlash_ProgramHalfWord(STORE_START_ADDRESS+i*2,Store_Data[i]);		//除了标志位全部清0
	}
}

//将模块数据清零
void Store_Clear(void)
{
	for(uint16_t i=1;i<STORE_COUNT;i++)
	{
			Store_Data[i] = 0x0000;
	}
	Store_Save();
}

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "Store.h"

uint8_t Key;

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Key_Init();
	Store_Init();

	OLED_ShowString(1,1,"Flag:");
	OLED_ShowString(2,1,"Data:");
	
	while (1)
	{
		Key = Key_GetNum();
		if(Key == 1)
		{
			Store_Data[1]++;
			Store_Data[2]++;
			Store_Data[3]++;
			Store_Data[4]++;
			Store_Save();
		}
		
		if(Key == 2)
		{
			Store_Clear();
		}
		OLED_ShowHexNum(1,6,Store_Data[0],4);
		OLED_ShowHexNum(3, 1, Store_Data[1], 4);	//显示Store_Data的有效存储数据
		OLED_ShowHexNum(3, 6, Store_Data[2], 4);
		OLED_ShowHexNum(4, 1, Store_Data[3], 4);
		OLED_ShowHexNum(4, 6, Store_Data[4], 4);
		
	}
}

读ID

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#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)
	{
		
	}
}

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 Flash编程笔记

发表评论