STM32速成笔记—Flash闪存

这篇具有很好参考价值的文章主要介绍了STM32速成笔记—Flash闪存。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


🎀 文章作者:二土电子

🌸 关注文末公众号获取其他资料和工程文件!

🐸 期待大家一起学习交流!


一、Flash简介

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

二、STM32F1的Flash

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

stm32flash,STM32速成笔记,stm32,笔记,嵌入式硬件

  • 主存储器
    主存储器用来存储我们的代码和定义的一些常量数据。当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;   // 扇区剩余空间大小

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

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

  • 判断在写入地址所在扇区能否将写入内容全部写入完成
	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编译完成后,会显示下面的内容

stm32flash,STM32速成笔记,stm32,笔记,嵌入式硬件

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

最后,计算程序代码所占Flash空间。flash = Code + RO-data + RW-data文章来源地址https://www.toymoban.com/news/detail-590453.html

到了这里,关于STM32速成笔记—Flash闪存的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32笔记 Flash

    笔记来源于江科协议的视频 芯片采用与stm32F103C8T6 •STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分,通过闪存存储器接口(外设)可以对程序存储器和选项字节进行擦除和编程,系统存储器里面存储的是BootLoader,是不允许修改的。 •读写FLASH的用途: 利

    2024年01月18日
    浏览(39)
  • STM32CubeMX学习笔记16--- STM32内部FLASH

    1. 内部FLASH简介         之前的文章中介绍过STM32F1利用SPI与外部FLASH(W25QXX芯片)通讯的例程,本例程将介绍STM32F1的内部FLASH,通过内部FLASH实现数据读写操作。 不同型号的STM32,其FLASH容量也有所不同,最小的只有16K字节,最大的则达到了1024K字节。此处我们使用的是ST

    2024年04月09日
    浏览(41)
  • 蓝桥杯嵌入式STM32 G431 hal库开发速成——ADC与DAC

    模数转换器(ADC):它将模拟信号转换为单片机能够处理的数字信号。在很多应用中,比如温度传感器、压力传感器等,信号最初都是模拟形式的。ADC 读取这些模拟信号,然后将它们转换为数字形式,以便单片机可以读取和处理。 数模转换器(DAC):它执行相反的操作,将

    2024年02月01日
    浏览(59)
  • STM32CubeMX学习笔记(48)——USB接口使用(MSC基于外部Flash模拟U盘)

    USB(Universal Serial BUS)通用串行总线 ,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、Microsoft 等多家公司联合提出的。 USB 发展到现在已经有 US

    2024年01月16日
    浏览(48)
  • STM32CubeMX学习笔记(47)——USB接口使用(MSC基于内部Flash模拟U盘)

    USB(Universal Serial BUS)通用串行总线 ,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、Microsoft 等多家公司联合提出的。 USB 发展到现在已经有 US

    2024年02月15日
    浏览(44)
  • 【嵌入式学习笔记】嵌入式基础9——STM32启动过程

    程序段交叉引用关系(Section Cross References):描述各文件之间函数调用关系 删除映像未使用的程序段(Removing Unused input sections from the image):描述工程中未用到被删除的冗余程序段(函数/数据) 映像符号表(Image Symbol Table):描述各符号(程序段/数据)在存储器中的地址、类

    2024年02月15日
    浏览(81)
  • 嵌入式学习笔记——STM32的时钟树

    在之前的所有代码编程的过程中,似乎每次都绕不开一个叫做时钟使能的东西,当时我们是在数据手册上直接看其挂接在那条时钟线上的,那么STM32内部的时钟到底是怎么一个构型呢,本文来对此做一个介绍。 老规矩,一个新的名词出现,首先需要搞清楚它是个啥,下图中对

    2024年02月02日
    浏览(53)
  • 嵌入式学习笔记——STM32硬件基础知识

    上一篇中我们重点是讲了一下怎么搭建开发环境以及怎么下载烧录的过程,这都是解决的电脑端的开发环境问题,还没有到实际的开发板上,我们的单片机是都是焊接在开发板上的,PCB上有着它所需的工作电路。并不是直接给供电电压就可以让其工作的,本文主要是简介一下

    2024年01月22日
    浏览(68)
  • STM32内部Flash

    目录 一、内部Flash简介 二、内部Flash构成 1. 主存储器 2. 系统存储区 3. 选项字节 三、内部Flash写入过程 1. 解锁 2. 页擦除 3. 写入数据 四、工程空间分布 某工程的ROM存储器分布映像: 1. 程序ROM的加载与执行空间 2. ROM空间分布表         STM32芯片内部有一个Flash存储器,主要

    2024年02月19日
    浏览(48)
  • STM32的FLASH操作

    时间记录:2024/2/19 (1)flash大小64K,地址0x08000000-0x08010000 (2)此芯片内存大小属于中容量产品,根据数据手册可知中容量产品一个扇区的大小为1K (3)我们在这里需要知道的点就是,可以将数据保存在flash中实现掉电保存 (1)写数据操作(一种方法) 1.解除写保护,向K

    2024年02月21日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包