STM32速成笔记—Flash闪存

文章目录

  • 一、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 ++;   // 扇区地址增1secoff=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.hqwc.cn/news/14012.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单摆模型仿真(SMART PLC梯形图实现)

单摆模型详细介绍这里不再赘述,大家可以参看下面文章链接,单摆模型的仿真有助于大家理解分析力学的有关知识,同时模型的实现可以帮助大家更好的理解和运用微分和积分这2个强有力的工具。 单摆模型(博途PLC和Simulink仿真对比)_RXXW_Dor的博客-CSDN博客单摆模型的详细推导公…

网联V2X视频事件检测相机使用说明书

1 产品概览 网联 V2X视频事件检测相机 视频事件检测相机 &#xff0c;内置 1/1.8″逐行扫描 800万像素传感器&#xff1b;视 万像素传感器&#xff1b;视 频编码协议支持 H.265、H.264、MJPEG&#xff1b;具有 1个 10M/100M/1000M自适应以 太网 RJ45接口、 1路 RS485接口&#…

高楼的思考

博主是个高楼迷&#xff0c;会入职当前所在的公司有一定程度上也受此影响&#xff08;办公地点为华南第一高楼&#xff09;。 大概头条的大数据平台给我打的其中一个标签就是高楼迷&#xff0c;所以经常会给我推送一些高楼相关的文章。最有印象的便是深圳200米以上高楼数远超纽…

如何用手机制作3D人物模型素材

3D人物模型素材是现代3D游戏和电影制作中必不可少的一部分。它们是数字艺术家和设计师们用来创造逼真世界的关键。3D人物模型素材是用计算机程序制作的虚拟人物&#xff0c;可以被用于电影、电视、游戏和虚拟现实应用中。它们可以被用来代替实际演员&#xff0c;也可以被用来创…

程序请求报错java.lang.NoSuchMethodError

[23-7-3 9:09:19:069 CST] 00000017 ServletWrappe E com.ibm.ws.webcontainer.servlet.ServletWrapper service SRVE0068E:应用程序 east5_20230629_war 中 servlet XXX 的某一服务方法创建了未捕获到的异常。 创建的异常&#xff1a;org.springframework.web.util.NestedServ…

SpringBoot项目出现Failed to configure a DataSource错误时解决方法

若在运行SpringBoot项目时&#xff0c;出现如下错误&#xff1a; Description:Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured.Reason: Failed to determine a suitable driver classAction:Consider the…

Java-API简析_java.net.InetAddress类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/131590559 出自【进步*于辰的博客】 因为我发现目前&#xff0c;我对Java-API的学习意识比较薄弱…

Pandas+Pyecharts | 双十一美妆销售数据分析可视化

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 数据信息2.3 筛选有销量的数据 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 双十一前后几天美妆订单数量3.2 双十一前后几天美妆销量3.3…

go数据结构之slice与map

1. 切片 1. 切片结构定义 type slice struct {array unsafe.Pointerlen intcap int }array:引用的底层数组&#xff0c;动态数组&#xff0c;可以修改 如果多个切片的array指针指向同一个动态数组&#xff0c;则它们都可以对底层这个动态数组元素进行修改。 len:&#xf…

简要介绍 | 两阶段点云目标检测:理论与实践

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对两阶段点云目标检测进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 两阶段点云目标检测&#xff1a;理论与实践 在这篇博客中&#xff0c;我们将探索两阶段点云目标检测的理论基础和实际应用…

mac与pd虚拟机之间不能粘贴文字或粘贴文件

首先确保共享打开&#xff1a; 然后检查虚拟机的Parallels Tools是否正常 一个简单的判断方式就是&#xff0c;退出虚拟机全屏之后&#xff0c;如果能够正常进入融合模式&#xff0c;那么Parallels Tools可用&#xff0c;否则就要排查问题 检查Parallels Tools是否随系统正常启…

基于微信小程序学校部门年终绩效考核自动评分系统(源码+文档+数据库+PPT)

基于微信小程序的部门年终绩效考核系统&#xff0c;为加强学校运营队伍建设提高学校管理力&#xff0c;合理评价教师及部门年度工作计划完成情况&#xff0c;促进整体绩效改进&#xff0c;鼓励管理团队注重对下属进行帮助、提升&#xff0c;促进团队扩张和发展&#xff0c;特制…