江科大笔记—读写内部闪存FLASH&读取芯片ID

读写内部闪存FLASH

右下角是OLED,然后左上角在PB1和PB11两个引脚,插上两个按键用于控制。下一个代码读取芯片ID,这个也是接上一个OLED,能显示测试数据就可以了。
在这里插入图片描述

STM32-STLINK Utility

在这里插入图片描述
本节的代码调试,使用辅助软件STM32-STLINK Utility,在使用之前我们需要用stink把STM32连接好,然后我们点击这个按钮连接,可以看到下面这个窗口里显示的,就是闪存里面存储的数据了。这个软件可以直接修改闪存里的数据,可以修改选项字节数据,不用写任何代码,非常方便。

Address:可以指定想要查看的起始地址,目前是0x0800 0000,也就是整个闪存的起始地址。
Size:就是从起始地址开始,总共查看多少个字节,目前0x10000,就是查看64KB的字节数
Data Width:数据宽度,可以指定以32位/16位/8位的形式显示,分别对应,字/半字/字节。

16位
在这里插入图片描述
8位
在这里插入图片描述
闪存的操作:

第一步,读取数据,直接封装一下uint16_t Data = *((__IO uint16_t *)(0x08000000));
第二步,擦除:全擦除或页擦除,这两个功能分别对应一个库函数。执行之前,手动调用,解锁;执行之后,在加锁。
第三步,编程,有对应库函数直接调用。执行之前,手动调用,解锁;执行之后,在加锁。

选项字节的擦除和编成与主闪存的擦除和编程类似,有对应的函数。

在这里插入图片描述

用代码配置读写保护,容易造成芯片自锁,如把闪存写保护了,但程序里并没有预留解除写保护的代码,这样锁住之后,芯片就没法下载程序。但还可以再软件里自救,在这个选项字节配置里,把读写保护去掉,再Apply就能救活芯片了。

flash.h库函数

在这里插入图片描述

在这里插入图片描述
FLASH_Status返回值,是这个操作的完成状态,函数返回状态功能:返回第一个,表示芯片当前忙;返回第二个,表示编程错误;返回第三个,表示写保护错误;返回第四个,表示返回完成;返回第五个,表示等待超时。

//这三个是内核相关,不用过多了解与调用
void FLASH_SetLatency(uint32_t FLASH_Latency);
void FLASH_HalfCycleAccessCmd(uint32_t FLASH_HalfCycleAccess);
void FLASH_PrefetchBufferCmd(uint32_t FLASH_PrefetchBuffer);
//加锁解锁
void FLASH_Unlock(void);//解锁
void FLASH_Lock(void);//加锁,把CR寄存器LOCK位置1
//主闪存
FLASH_Status FLASH_ErasePage(uint32_t Page_Address); //闪存擦除某一页
FLASH_Status FLASH_EraseAllPages(void);//全擦除
FLASH_Status FLASH_EraseOptionBytes(void);//擦除选项字节

FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data);//在指定地址,写入字
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data);//在指定地址,写入半字

//选项字节的写入
FLASH_Status FLASH_ProgramOptionByteData(uint32_t Address, uint8_t Data);//自定义的Data0和Data1
FLASH_Status FLASH_EnableWriteProtection(uint32_t FLASH_Pages);//写保护
FLASH_Status FLASH_ReadOutProtection(FunctionalState NewState);//读保护
FLASH_Status FLASH_UserOptionByteConfig(uint16_t OB_IWDG, uint16_t OB_STOP, uint16_t OB_STDBY);//用户选项的3个配置位

//获取选项字节当前状态
uint32_t FLASH_GetUserOptionByte(void);//获取用户选项的三个配置位
uint32_t FLASH_GetWriteProtectionOptionByte(void);//获取写保护状态
FlagStatus FLASH_GetReadOutProtectionStatus(void);//获取读保护状态  
............................//获取自定义的Data0和Data1没有给现成函数,直接指针访问就可以了。

FlagStatus FLASH_GetPrefetchBufferStatus(void);//获取预取缓冲区状态 不用了解
void FLASH_ITConfig(uint32_t FLASH_IT, FunctionalState NewState);//中断使能
FlagStatus FLASH_GetFlagStatus(uint32_t FLASH_FLAG);//获取标志位
void FLASH_ClearFlag(uint32_t FLASH_FLAG);//清除标志位
FLASH_Status FLASH_GetStatus(void);//获取状态
FLASH_Status FLASH_WaitForLastOperation(uint32_t Timeout);//等待上一次操作 等待忙等待BSY为0(上面主闪存这些函数内部已经实现等待忙操作了,此函数不用我们单独调用)

注意:使用完STLINK这个软件,要及时断开连接,要不然设备占用,keil下载出错。

在Store模块,要有SRAM缓存数组来管理FLASH的最后一页,实现参数任意读写和保存

MyFLASH.c

#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

#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

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];				//定义SRAM数组

/**
  * 函    数:参数存储模块初始化    
  * 参    数:无
  * 返 回 值:无
  */
  void Store_Init(void)      //第一大步,第一次使用的时候,对闪存进行初始化
{
	/*判断是不是第一次使用*/
	if (MyFLASH_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5)	//读取第一个半字的标志位,if成立,则执行第一次使用的初始化
	{
		MyFLASH_ErasePage(STORE_START_ADDRESS);					//擦除指定页
		MyFLASH_ProgramHalfWord(STORE_START_ADDRESS, 0xA5A5);	//在第一个半字写入自己规定的标志位,用于判断是不是第一次使用
		
		//把剩余的存储空间,全都置为默认值0
		for (uint16_t i = 1; i < STORE_COUNT; i ++)				//循环STORE_COUNT次,除了第一个标志位。注意;要从1开始,而不是0,因为第一个半字是标志位,剩下的才是有效数据
		{
			MyFLASH_ProgramHalfWord(STORE_START_ADDRESS + i * 2, 0x0000);		//除了标志位的有效数据全部清0。注:因为一个半字要占用两个地址,所以i要乘2
		}
	}
	
	/*第2大步,上电时,将闪存数据加载回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次,除了第一个标志位。注:i要从1开始,不用把标志位清0
	{
		Store_Data[i] = 0x0000;							//SRAM数组有效数据清0
	}
	Store_Save();										//更新保存数据到闪存。每次修改完数组后,都要Store_Save();保证数值和闪存数据一样;如果不Save,那下次上电,加载的是以前保存的数据
}

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

main.c

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

uint8_t KeyNum;					//定义用于接收按键键码的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();				//OLED初始化
	Key_Init();					//按键初始化
	Store_Init();				//参数存储模块初始化,在上电的时候将闪存的数据加载回Store_Data,实现掉电不丢失
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Flag:");
	OLED_ShowString(2, 1, "Data:");
	
	while (1)
	{
		KeyNum = Key_GetNum();		//获取按键键码
		
		if (KeyNum == 1)			//按键1按下
		{
			Store_Data[1] ++;		//变换测试数据
			Store_Data[2] += 2;
			Store_Data[3] += 3;
			Store_Data[4] += 4;
			Store_Save();			//将Store_Data的数据备份保存到闪存,实现掉电不丢失
		}
		
		if (KeyNum == 2)			//按键2按下
		{
			Store_Clear();			//将Store_Data的数据全部清0
		}
		
		OLED_ShowHexNum(1, 6, Store_Data[0], 4);	//显示Store_Data的第一位标志位
		OLED_ShowHexNum(3, 1, Store_Data[1], 4);	//显示Store_Data的有效存储数据
		OLED_ShowHexNum(3, 6, Store_Data[2], 4);    //3行6列显示Store_Data[2],长度为4
		OLED_ShowHexNum(4, 1, Store_Data[3], 4);
		OLED_ShowHexNum(4, 6, Store_Data[4], 4);
	}
}

程序现象
Flag显示标志位:A5A5,这是之前保存的。按下Key2,可以去清0所以参数;按下Key1,可以变换测试数据,之后断定在重新上电,数据保持原样,按复位键,数据也不会丢失。
在这里插入图片描述
按下Key2,可以去清0所以参数
在这里插入图片描述
按下Key1,可以变换测试数据
在这里插入图片描述

前面一部分存储的是程序文件,最后一页存储的是用户数据,目前我们的假设是程序文件比较小,最后一页肯定是没有用到的,所以我们放心的使用最后一页,但是如果程序比较大,触及到了最后一页,那程序和用户数据存储的位置就冲突了,或者说如果你参数非常多,最后10页很大一部分都是留着存储用户数据的,这样如果前面的程序文件长一些,那样非常容易和用户数据冲突,并且这种冲突如果没发现,就会产生非常隐蔽的bug,那如何解决这个问题呢,这时我们可以给程序文件限定一个存储范围,不让它分配到后面我们用户数据的空间来。

在这里插入图片描述

这就是编译器,给各个数据分配的空间地址和范围了。如,片上的ROM,起始地址是0800 0000,注意,最高位的0省略了。它的Size:是0x10000,默认全部的64k闪存,都是程序代码分配的空间 ,

如果你想把闪存的尾部空间留着自己用,那可以把这个程序空间的size改小点,比如我们改成0XFC00,这样编译后的代码,无论如何也不会分配到最后一页了。如果Size过小,那编译的时候也会报错。

所以如果你计划把闪存尾部的很多空间都留着自己用,那就把这个程序代码的空间改小点,以免冲突。然后这个下载程序的起始地址也可以改,比如你想写个BOOTLOADER程序放在闪存尾部,那可以在这里修改下载到闪存的起始位置。

在这里插入图片描述

最右边这里是片上RAM的起始地址和大小,2000开始大小5000对应就是20K。

在这里插入图片描述

这里debug, settings,Flash download,在这里是配置下载选项,其中这个选项我们要选择第二个,擦除扇区,也就是页擦除。
第一个是,每次下载代码都全擦除再下载。
第二个是用到多少页,就擦多少页这个下载速度更快一些,如果你想在闪存尾部存储数据,那也最好选择页擦除的下载,要不然每次下载程序芯片都全擦除了。

在这里插入图片描述

程序编译之后,到底占用了多大的空间,我们可以全部编一下,在下面有一行信息就显示了Program Size,程序大小,其中有四个数,这四个数分别是什么意思,感兴趣的话可以网上搜索。
这里只需要记住,前三个数相加,得到的就是程序占用闪存的大小,后两个数相加,得到的是占用SRAM的大小。

在这里插入图片描述

这个程序大小,也可以在Target1这里(工程目录最上面)双击,会打开一个.map文件,这就是详细的编译信息。这个.map文件看完就删掉,要不然每次编译后,都会弹出窗口问你是不是要重新加载。

我们看最后面这里也有写程序的大小,并且有计算结果:
倒数第二行,是占用SRAM的大小,这里结果是2664字节,2.6kb,最后一行,是占用闪存的大小,这里是4576字节,4.47kb。

在这里插入图片描述

下载到STM32里,验证一下。可以看到,程序在0x0800 11E0地址终止,用计算机转换:16进制 11E0就是10进制的4576。观察的数据和编译的结果相符,没问题。

读取芯片ID

复制OLED的工程。

main.c

#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);  //第2次读是,基地址+0x02地址偏移
	OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
	OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
	
	while (1)
	{
		
	}
}

程序现象
在这里插入图片描述
ID号对不对,可以用软件来验证。

在这里插入图片描述

手册

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值