STM32速成笔记—Flash闪存

news2024/11/25 20:39:44

文章目录

  • 一、Flash简介
  • 二、STM32F1的Flash
  • 三、Flash操作步骤
  • 四、程序设计
    • 4.1 读取数据
    • 4.2 写入数据(不检查)
    • 4.3 写入数据(检查)
  • 五、注意事项

一、Flash简介

快闪存储器(flash memory),是一种电子式可清除程序化只读存储器的形式,允许在操作中被多次擦或写的存储器。它是一种非易失性存储器,即断电数据也不会丢失。

二、STM32F1的Flash

STM32F103ZET6的Flash大小为512KB,属于大容量产品。在中文参考手册中给出了大容量产品的Flash模块组织结构图

大容量产品Flsh模块组织结构图

  • 主存储器
    主存储器用来存储我们的代码和定义的一些常量数据。当Boot0和Boot1都接GND时,芯片从主存储器的起始地址0x0800 0000开始运行代码。
  • 信息块
    系统存储器中存储的是启动程序代码。启动程序就是串口下载的代码。当Boot0接VCC,Boot1接GND时,运行的就是系统存储器中的代码。系统存储器中存储的启动代码,是ST公司在芯片出厂时就已经下载好的,用户无法修改。选择字节是用来配置写保护和杜保护功能。
  • 闪存存储器接口寄存器
    闪存存储器接口寄存器,是整个闪存的控制机构,里面包含了很多的闪存的控制寄存器和状态寄存器。

在执行闪存写操作时,任何对闪存的读操作都会被锁住。只有对闪存的写操作结束后,读操作才能够正常执行。也就是说,在对闪存进行写操作或者擦除操作时,无法对闪存进行读操作。

三、Flash操作步骤

  • 解锁和锁定
  • 写/擦除操作
  • 获取Flash状态
  • 等待操作完成
  • 读取Flash指定地址数据

四、程序设计

操作内部Flash时,最小单位是半字(16位)。

4.1 读取数据

读取数据用的是指针的方式,在之前博主的文章中有关于如何利用指针在指定地址读写数据的操作。

/*
 *==============================================================================
 *函数名称:Med_Flash_ReadHalfWord
 *函数功能:读取指定地址的半字(16位数据)
 *输入参数:faddr:读取地址
 *返回值:对应读取地址数据
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */
vu16 Med_Flash_ReadHalfWord (u32 faddr)
{
	return *(vu16*)faddr; 
}
/*
 *==============================================================================
 *函数名称:Med_Flash_Read
 *函数功能:从指定地址开始读出指定长度的数据
 *输入参数:ReadAddr:读取起始地址;pBuffer:数据指针;
						NumToRead:读取(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */
void Med_Flash_Read (u32 ReadAddr,u16 *pBuffer,u16 NumToRead)
{
	u16 i;
	for(i = 0;i < NumToRead;i ++)
	{
		pBuffer[i] = Med_Flash_ReadHalfWord(ReadAddr);   // 读取2个字节.
		ReadAddr += 2;   // 偏移2个字节.	
	}
}

4.2 写入数据(不检查)

这里的不检查,是指在写入之前,不检查写入地址是否可写。

/*
 *==============================================================================
 *函数名称:Med_Flash_Write_NoCheck
 *函数功能:不检查的写入
 *输入参数:WriteAddr:写入起始地址;pBuffer:数据指针;
						NumToWrite:写入(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */
void Med_Flash_Write_NoCheck (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{		 		 
	u16 i;
	for(i = 0;i < NumToWrite;i ++)
	{
		FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
		WriteAddr += 2;   // 地址增加2.
	}  
}

4.3 写入数据(检查)

/*
 *==============================================================================
 *函数名称:Med_Flash_Read
 *函数功能:从指定地址开始写入指定长度的数据
 *输入参数:WriteAddr:写入起始地址;pBuffer:数据指针;
						NumToRead:写入(半字)数
 *返回值:无
 *备  注:对内部Flash的操作是以半字为单位,所以读写地址必须是2的倍数
 *==============================================================================
 */

// 根据中文参考手册,大容量产品的每一页是2K字节
#if STM32_FLASH_SIZE < 256
	#define STM32_SECTOR_SIZE   1024   // 字节
#else 
	#define STM32_SECTOR_SIZE   2048
#endif

// 一个扇区的内存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];

void Med_Flash_Write (u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)
{
	u32 secpos;   // 扇区地址
	u16 secoff;   // 扇区内偏移地址(16位字计算)
	u16 secremain;   // 扇区内剩余地址(16位计算)	   
 	u16 i;    
	u32 offaddr;   // 去掉0X08000000后的地址
	
	// 判断写入地址是否在合法范围内
	if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
	{
		return;   // 非法地址
	}
	
	FLASH_Unlock();   // 解锁
	offaddr = WriteAddr - STM32_FLASH_BASE;   // 实际偏移地址
	secpos = offaddr / STM32_SECTOR_SIZE;   // 扇区地址
	secoff = (offaddr % STM32_SECTOR_SIZE) / 2;   // 在扇区内的偏移(2个字节为基本单位)
	secremain = STM32_SECTOR_SIZE / 2 - secoff;   // 扇区剩余空间大小
	
	if (NumToWrite <= secremain)
	{
		secremain = NumToWrite;   // 不大于该扇区范围
	}
	while (1) 
	{
		// 读出整个扇区的内容
		Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
		
		// 校验数据
		for (i = 0;i < secremain;i ++)
		{
			// 需要擦除 
			if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
			{
				break; 
			}				
		}
		// 需要擦除
		if (i < secremain)
		{
			FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE);   // 擦除这个扇区
			
			// 复制
			for (i = 0;i < secremain;i ++)
			{
				STM32_FLASH_BUF[i + secoff] = pBuffer[i];	  
			}
			// 写入整个扇区
			Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
		}
		else
		{
			// 写已经擦除了的,直接写入扇区剩余区间
			Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
		}
		
		if (NumToWrite == secremain)
		{
			break;   // 写入结束了
		}
		// 写入未结束
		else
		{
			secpos ++;   // 扇区地址增1
			secoff=0;   // 偏移位置为0 	 
			pBuffer+=secremain;   // 指针偏移
			WriteAddr+=secremain;   // 写地址偏移	   
			NumToWrite-=secremain;   // 字节(16位)数递减
			if (NumToWrite>(STM32_SECTOR_SIZE/2))
			{
				secremain=STM32_SECTOR_SIZE/2;   // 下一个扇区还是写不完
			}
			else
			{
				secremain=NumToWrite;   // 下一个扇区可以写完了
			}
		}	 
	}	
	FLASH_Lock();   // 上锁
}

宏定义如下

// STM32的Flash容量,单位为KB
#define STM32_FLASH_SIZE   512

// FLASH主存储块起始地址
#define STM32_FLASH_BASE   0x08000000

上面的读取数据和不检查的写入都比较简单,因此并没有再做分析。这里分析一下带检查的写入的程序设计思路。

  • 首先用一小段条件编译来区分一下大容量产品和其他产品。因为大容量产品的一页(一个扇区)是2K字节,中小容量产品的一页是1K字节。定一个了一个数组,数组大小是一个扇区的大小。
// 根据中文参考手册,大容量产品的每一页是2K字节
#if STM32_FLASH_SIZE < 256
	#define STM32_SECTOR_SIZE   1024   // 字节
#else 
	#define STM32_SECTOR_SIZE   2048
#endif

// 一个扇区的内存
u16 STM32_FLASH_BUF[STM32_SECTOR_SIZE / 2];

大容量产品,一个扇区2K字节,除以2是因为在对内部Flash操作时,最小单位是半字。

  • 接下来,判断要写入的地址是否合法,也就是是否在主存储块地址范围内。
	// 判断写入地址是否在合法范围内
	if (WriteAddr < STM32_FLASH_BASE || (WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE)))
	{
		return;   // 非法地址
	}
  • 如果要写入的地址合法,那么解锁后计算一些参数值。
	offaddr = WriteAddr - STM32_FLASH_BASE;   // 实际偏移地址

实际偏移地址,指的是要写入的地址与主存储块基地址(0x0800 0000)的差值。

	secpos = offaddr / STM32_SECTOR_SIZE;   // 扇区地址

扇区地址指的是要写入的地址所在扇区前面的扇区数。由于所有的参数都不是浮点型,因此在做除法时,小数位都是0。最终除出来的结果就是当前扇区前面的扇区数。

	secoff = (offaddr % STM32_SECTOR_SIZE) / 2;   // 在扇区内的偏移(2个字节为基本单位)

在扇区内的偏移指的是要写入的地址与其所在扇区首地址的差值。用要写入的地址取余每一个扇区的字节数,余数就是偏移地址。但是由于操作内部Flash时的最小单位是半字,因此要除以2。

	secremain = STM32_SECTOR_SIZE / 2 - secoff;   // 扇区剩余空间大小

扇区内剩余空间大小只需要用扇区总的空间大小减去偏移地址即可得到。但是需要注意的是,单位都是半字。这里的剩余空间大小,并不是真正的剩余空间大小。而是指写入地址后面的扇区大小。这里不太好理解,画一个图表示一下
扇区内剩余空间大小示意图

正是因为这里的扇区剩余空间大小并不是指真正的剩余空间大小。在剩余空间内,也可能存在已经写入数据的地址。所以后面需要进行判断,来确定是否需要擦除。

  • 判断在写入地址所在扇区能否将写入内容全部写入完成
	if (NumToWrite <= secremain)
	{
		secremain = NumToWrite;   // 不大于该扇区范围
	}

如果可以,直接将要写入的半字数赋值给当前扇区剩余空间大小。如果当前扇区剩余空间大小可以容纳要写入的半字数,那么只需要写入一次即可,在后续判断是否写完时,直接通过,while循环只执行一次。

  • 读出整个扇区内容,判断是否需要擦除
		// 读出整个扇区的内容
		Med_Flash_Read(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
		
		// 校验数据
		for (i = 0;i < secremain;i ++)
		{
			// 需要擦除 
			if (STM32_FLASH_BUF[secoff + i] != 0XFFFF)
			{
				break; 
			}				
		}

要对内部Flash某个地址写入数据时,需要确保该地址数值为0xFFFF。判断方法就是从扇区内的偏移开始,利用for循环判断读出地扇区剩余空间内,是否存在已经被写入内容的地址。for循环找到i的值,i加上在扇区内的偏移加1之后的空间,才是真正的扇区剩余空间大小。

for循环结束后,判断是否需要进行擦除

		// 需要擦除
		if (i < secremain)
		{
			FLASH_ErasePage(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE);   // 擦除这个扇区
			
			// 复制
			for (i = 0;i < secremain;i ++)
			{
				STM32_FLASH_BUF[i + secoff] = pBuffer[i];	  
			}
			
			// 写入整个扇区
			Med_Flash_Write_NoCheck(secpos * STM32_SECTOR_SIZE + STM32_FLASH_BASE,STM32_FLASH_BUF,STM32_SECTOR_SIZE / 2);
		}
		else
		{
			// 写已经擦除了的,直接写入扇区剩余区间
			Med_Flash_Write_NoCheck(WriteAddr,pBuffer,secremain);
		}

擦除时,最小单元为一个扇区。在大容量产品中,也就是2048字节。

  • 最后,将需要写入的数据,写入到对应位置。如果是需要擦除的情况,写入时是先将原来的内容提取出来,然后在后面填充上需要写入的内容,擦除整个扇区之后再一起写入。如果是不需要擦除的情况,直接写入即可。

五、注意事项

在操作Flash时,注意不要对代码区内容进行擦写。如果擦写的地址在代码区,会导致程序运行异常。那么如何确保我们操作的地址不是在代码区?这就需要我们知道我们的代码所占的内存是多少。在Keil5编译完成后,会显示下面的内容

keil5编译后提示

  • Code
    程序所占用的内存大小(存放在Flash中)
  • RO-data
    程序定义的常量所占内存大小(存放在Flash中)
  • RW-data
    已被初始化的全局变量所占内存大小(在程序初始化的时候,RW-data会从FLASH中拷贝到RAM中)
    ZI-data
    未被初始化的全局变量所占内存大小(存放在RAM中)

最后,计算程序代码所占Flash空间。flash = Code + RO-data + RW-data

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/716133.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

物理与IP环境的重要性:打造稳定可靠的亚马逊测评环境

在亚马逊平台上进行测评补单、撸卡和撸货等活动&#xff0c;首要问题是确保环境的安全性和稳定性。一个稳定的环境是进行测评和撸卡的基础&#xff0c;如果无法解决安全性问题&#xff0c;那么从事这些项目就不值得。在环境技术研发领域已经有六七年的经验&#xff0c;在早期测…

红利期已过?2023跨境电商还吃香吗?亚马逊还能做吗?

2022年&#xff0c;由于疫情反复和外部因素的影响&#xff0c;跨境电商的情况并不乐观。但这并不意味着跨境电商已经走到了绝境。随着贸易全球化的深入发展&#xff0c;平台规则不断完善&#xff0c;国家相继出台最新的扶持政策&#xff0c;为跨境电商企业带来了更多的发展机遇…

Spring Boot 中的 Zookeeper 分布式锁

Spring Boot 中的 Zookeeper 分布式锁 分布式锁是分布式系统中常用的一个同步工具&#xff0c;它可以在多个进程之间协调访问共享资源&#xff0c;避免数据不一致或重复处理。在分布式环境中&#xff0c;由于网络通信的延迟和节点故障等原因&#xff0c;传统的锁机制无法满足需…

MES与ERP系统的生产计划管理到底有什么不同?

MES 的生产计划管理与 ERP 的生产计划管理到底有什么不同&#xff1f; 生产计划管理是企业发展的重要一环&#xff0c;对于提升企业生产效率&#xff0c;提高客户满意度&#xff0c;降低成本&#xff0c;提高客户满意度等方面都有重要意义。 我们首先来看MES和ERP生产计划管理…

Cisco Catalyst 9000 Series Switches, IOS-XE Release Dublin-17.11.1 ED

Cisco Catalyst 9000 Series Switches, IOS-XE Release Dublin-17.11.1 ED Cisco Catalyst 9000 交换产品系列 请访问原文链接&#xff1a;https://sysin.org/blog/cisco-catalyst-9000/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;…

Vox-E: Text-guided Voxel Editing of 3D Objects(3D目标的文本引导体素编辑)

Vox-E: Text-guided Voxel Editing of 3D Objects &#xff08;3D目标的文本引导体素编辑&#xff09; Paper&#xff1a;https://readpaper.com/paper/1705264952657440000 Code&#xff1a;http://vox-e.github.io/ 原文链接&#xff1a;Vox-E: 3D目标的文本引导体素编辑 &…

如何写出高效的软件测试用例,测试人都用得到

要编写出高效的测试用例&#xff0c;需要搞清楚什么是测试用例&#xff0c;以及如何编写出高效的测试用例&#xff1f;接下来将从以下几个部分来进行展开 1、什么是测试用例 2、如何编写测试用例 一、什么是测试用例 测试用例 &#xff1a;为了特定目的而设计的由一组测试输…

【el-tree大量数据卡顿解决】el-tree利用懒加载解决大数据量卡顿问题,el-tree懒加载回显方法

描述 问题是这样&#xff1a;我的项目中&#xff0c;有一个角色管理的页面。以前的老代码&#xff0c;直接用el-tree渲染的树形结构&#xff0c;勾选设置对应的权限。其他的部门倒是还好&#xff0c;但是涉及到老板的部门设置的时候&#xff0c;由于我们这边的权限太多&#x…

ESP32开发:1、环境搭建(基于vscode+ESP-IDF)

1、ESP-IDF ESP-IDF提供操作ESP32芯片的API函数&#xff0c;供用户编写的用户程序调用。当用户程序编写好后&#xff0c;ESP-IDF需要借助一系列编译工具才能将用户程序API函数编译成能运行在ESP32上的二进制文件。 如上图所示这个1个G左右大的压缩包就是ESP-IDF。如果电脑上已经…

sslTrus (RSA) OV CA

sslTrus (RSA) OV CA品牌证书是建立在Sectigo CA机构的一种面向中国大陆的PKI定制中级根证书服务&#xff0c;OCSP国内本地网络优化&#xff0c;更适合中国网络。采取的Sectigo根证书建立的信任&#xff0c;更是完整。 sslTrus (RSA) OV CA可以选择&#xff1a;单域名、通配符…

生产级Redis Cluster部署(4.0.10版本)

生产级Redis Cluster部署 环境准备 主机名 IP地址 端口 描述 redis-master 192.168.1.51 7000 redis-master01 7001 redis-master02 7002 redis-master03 redis-slave 192.168.1.52 8000 redis-slave01 8001 redis-slave02 8002 redis-slave03 初始化…

代码随想录二刷day42 | 动态规划之背包问题 416. 分割等和子集

day42 416. 分割等和子集确定dp数组以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 416. 分割等和子集 题目链接 解题思路&#xff1a; 这是一维的背包问题 只有确定了如下四点&#xff0c;才能把01背包问题套到本题上来。 背包的体积为sum / 2背包要…

Java 实现快慢指针法返回链表的中间结点

一、思路 这里分为链表结点个数是 奇数 和 偶数 两种情况。 如果是奇数&#xff0c;中间结点只有一个&#xff0c;返回即可&#xff1b;如果是偶数&#xff0c;中间结点则有两个&#xff0c;这里要求返回第二个。 上述图片展示的就是奇数的情况&#xff0c;此时中间结点就是…

找不到“$libdir/postgis-X.X“问题解决方案

背景&#xff1a; 数据库从postgresql-11.9 升级到11.20版本&#xff0c;11.20版本采用了docker镜像 postgis/postgis:11-3.3 (截止20230703实际对应的版本为pg11.20postgis3.3) 升级版本&#xff0c;使用了原来的data&#xff0c;主要版本不变&#xff0c;次要版本升级&#…

文档管理系统是业迈向数字化办公的新时代

随着信息技术的不断发展&#xff0c;企业数字化办公已成为越来越多企业的选择。在数字化办公中&#xff0c;文档管理系统是一个非常重要的组成部分&#xff0c;可以帮助企业打破时空限制&#xff0c;提高工作效率和质量&#xff0c;推动企业向数字化办公的新时代迈进。 什么是…

力扣 39. 组合总和

题目来源&#xff1a;https://leetcode.cn/problems/combination-sum/description/ C题解&#xff1a; 递归法。递归前对数组进行有序排序&#xff0c;可方便后续剪枝操作。 递归函数参数&#xff1a;定义两个全局变量&#xff0c;二维数组result存放结果集&#xff0c;数组pa…

当使用POI打开Excel文件遇到out of memory时该如何处理?

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 当我们开发处理Excel文件时&#xff0c;Apache POI 是许多人首选的工具。但是&#xff…

ssh Permission denied, please try again

Permission denied, please try again 修改 vi /etc/sshd_config 最后重启配置或者重启板子&#xff0c;重新ssh连接

优化springboot

介绍 在SpringBoot的Web项目中&#xff0c;默认采用的是内置Tomcat&#xff0c;当然也可以配置支持内置的jetty&#xff0c;内置有什么好处呢&#xff1f; 1. 方便微服务部署。 2. 方便项目启动&#xff0c;不需要下载Tomcat或者Jetty 针对目前的容器优化&#xff0c;目前来…

Dual In-Line Package(双列直插式封装)

DIP封装示意图 DIP的详细介绍&#xff1a; 1.封装形式&#xff1a;DIP是一种插件式封装&#xff0c;它由一个狭长的塑料或陶瓷封装体组成&#xff0c;具有在两侧排列的引脚。引脚通常是分布均匀的&#xff0c;并以一定的间隔排列&#xff0c;以便与插座或印刷电路板上的插孔对…