【嵌入式学习03】STM32的“固态硬盘”——flash(闪存)

一、存储器
        程序正常编译生成的二进制文件,需要下载烧录到单片机里面去,这个文件保存在单片机的ROM中,简称只读存储器。
1、分类
        ROM一般可以分为四大类:
1)PROM(Programmable Read-only Memory),可编程只读存储器,也叫one-Time Programmabe(OTP)ROM“一次可编程只读存储器”,是一种可以用程,只可以擦写一次,例如芯片的全球唯一ID码。
2)EPROM(Erasable Programmable Read-onlyMemory),可擦除可编程只读存储器,一旦编程完成后,EPROM只能用强紫外线照射来擦除。
3)EEPROM(Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
4)FLASH,可以擦写多次,按区/块擦除。
我们一般使用的单片机里面使用的是EEPROM(E2PROM),STM32则使用FLASH。

2、EPROM

        EPROM是一种具有可擦除功能,擦除后即可进行再编程(写入数据)的ROM内存,写入前必须先把里面的内容用紫外线照射它的IC卡上的透明视窗的方式来清除掉。这一类芯片比较容易识别,其封装中包含有“石英玻璃窗”,一个编程后的EPROM芯片的“石英玻璃窗”一般使用黑色不干胶纸盖住, 以防止遭到阳光直射。EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12~24V,随不同的芯片型号而定)。

3、EEPROM
        EEPROM拯救了这一切?
        EEPROM的全称是「电可擦除可编程只读仔储器」,即Electrically Erasable Programmable Read-onlyMemory。EEPROM的出现可以说是跨时代的因为ROM可以多次编程了,对于程序员来说,终于可以多次烧写单片机了,更让我们兴奋的是,我们可以使用电擦除,而不是紫外线擦除了。从擦除次数上,EEPROM可以擦除100万次,而且EEPROM可以针对每一个区块,也就是每一个位置写[0]或者写[1],如果大家知道FLASH特性的话,就会觉得EEPROM是多么优秀。而且数据的保存时间可以达到100年。当然了,特点就是电路复杂,成本高,因为成本高就导致了EEPROM的大小不是非常大,一般在512KB以下
4、FLASH
        如果从电擦除这个特性上说的话,FLASH也是EEPROM的一种,不同的是,FLASH的擦除区块不是一个字节,而是扇区来擦除,也是因为这样的特性,才导致FLASH价格比EEPROM便宜。

        闪存(flash Memory)是一种长寿命的非易失性(在断电情况下仍能保持所存储的数据信息)的存储器。用途SD卡、固态硬盘、芯片内存存储单元存储代码

Flash 具有以下主要特性:

  1. 对于 STM32F40x和 STM32F41x,容量高达1MB;对于 STM32F42x和 STM32F43x,容量高达 2 MB
  2. 128 位宽数据读取
  3. 字节(8bit)、半字(16bit)、字(32bit)和双字(64bit)数据写入
  4. 扇区擦除与全部擦除
  5. 存储器组织结
    Flash 结构如下:
    ——主存储器块,分为4个 16 KB 扇区、1 个 64 KB 扇区和 7个 128 KB 扇区
    ——系统存储器,器件在系统存储器自举模式下从该存储器启动
    ——512 字节 OTP(一次性可编程),用于存储用户数据
    OTP 区域还有 16 个额外字节,用于锁定对应的 OTP 数据块。
    ——选项字节,用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。
     
  6. 低功耗模式
     


STM32F4系列芯片大部分是对扇区4进行操作,其大小为64KB,足够日常程序的使用,扇区3的16KB不满足日常的容量使用。

Flash 的作用:
①存储程序代码
②存储小图片、小视频
③存储自定义的数据,如:密码、账户信息
如果要在某个扇区写入数据,其前提条件是:当前写入数据存储的地址一定要被擦除过,才能被写入。(类似于黑板的使用,在写之前得先将黑板擦干净)。

在项目开发中如何对某个扇区的字节进行修改?
①先保存整个扇区的数据
②针对保存中的数据进行修改
③擦除该扇区
④将之前保存的所有数据写入该扇区

擦除就像咱们电脑硬盘的格式化
扇区擦除=分区格式化、全部擦除=硬盘格式化

二、FLASH代码编写
1、FLASH_Status FLASH_EraseSector(uint32_t FLASH_Sector, uint8_t VoltageRange)
a.功能:擦除指定的闪存扇区。(如果同时请求擦除和编程操作,擦除操作在编程操作之前执行。)
b.传参:①FLASH_Sector:要擦除的扇区号。②VoltageRange:定义擦除并行性的设备电压范围。
此参数可以是以下值之一:VoltageRange_1(当器件电压范围为1.8V至2.1V时,操作将按字节(8位)完成);VoltageRange_2(当器件电压范围为2.1V至2.7V时,操作将通过半字(16位)完成);VoltageRange_3(当设备电压范围为2.7V至3.6V时,操作将由字(32位)完成);VoltageRange_4(当器件电压范围为2.7V至3.6V+外部Vpp时,操作将通过双字(64位)完成)。
我们一般是对字进行操作,这里第二参数选择VoltageRange_3。

2、FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_t Data)
a.功能:在指定地址编程一个字(32位)。当设备电压范围为2.7V至3.6V时,必须使用此功能。
b.传参:①Address:指定要编程的地址。②Data:指定要编程的数据。

3、往扇区写入数据的步骤:
①解锁FLASH
②擦除扇区
③扇区写入
④锁定扇区
实例:将0x12345678写入扇区4。

uint32_t d;
     
    /* 解锁FLASH(闪存)*/
    FLASH_Unlock();
    
    /* 擦除扇区4 */
    if(FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3) != FLASH_COMPLETE)
    {
        printf("FLASH_EraseSector error\r\n");
        while(1);
    }
    
    /* 向扇区4首地址写入0x12345678 */
    if(FLASH_ProgramWord(0x8010000,0xAABBCCDD)!= FLASH_COMPLETE)
    {
        printf("FLASH_ProgramWord error\r\n");
        while(1);        
        
    }
    
    /* 不再需要修改数据,则锁定闪存 */
    FLASH_Lock(); 
    
    d = *(__IO uint32_t*)0x8010000;

    /* 打印写入的数据  */
    printf("read addr at 0x8010000 is 0x%08X\r\n",d);

三、思考题
1、对某一扇区连续写入64字的数据,并读取验证数据是否正确。(64字=64*4字节)

    __IO uint32_t *d;
	
	/* 解锁FLASH(闪存)*/
	FLASH_Unlock();
	
	/* 擦除扇区4 */
	if(FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3) != FLASH_COMPLETE)
	{
		printf("FLASH_EraseSector error\r\n");
		while(1);
	}
	
	/* 向扇区4首地址写入0xAABBCCDD */
	for(i=0;i<64;i++)
	{                      /*   这里地址偏移是对字节操作,一个字是4个字节,所以这里是*4 */
		if(FLASH_ProgramWord(0x8010000+i*4,0xAABBCCDD)!= FLASH_COMPLETE)
		{
			printf("FLASH_ProgramWord error\r\n");
			while(1);		
			
		}
	}
	
	/* 不再需要修改数据,则锁定闪存 */
	FLASH_Lock(); 
	
	d = (__IO uint32_t*)0x8010000;
	
	for(i=0;i<64;i++)
	{
		printf("read addr at 0x8010000 is 0x%08X\r\n",*d++);
	}

2、温湿度数据记录仪,实现步骤如下:按下KEY1或甲口1接收到”start”,居动温湿度数据记录模式,将读取到温湿度模块教据保存到STM32内部PLASH当中,最大记录数目100条。
按下KEY2或串口1接收到”stop”,停止温湿度数据记录。
按下KEY3或串口1接收到”show”,显示当前所有温湿度所有记录,显示格式如下:
[001]2020/03/25 Week 6 9:40:12 Temp=30.0 Humi=92.0
[002]2020/03/25 Week 6 9:42:32 Temp=31.0 Humi=93.0
按下KEY4或串口1接收到”clear”,清空所有温湿度数据记录
如何界定存储的数据达到100条?如果超过100条记录,请打印“存储已满”
应用场景:例如智能手表存储用户的身体健康的数据;指纹锁与密码锁,指纹数据与密码数据都可以存储到FLASH。

扇区写入每条记录

/*  扇区写入记录  */
uint32_t flash_write_record(char *pbuf,uint32_t record_count)
{
	uint32_t addr_start=ADDR_FLASH_SECTOR_4+record_count*64;
	uint32_t addr_end  =addr_start+64;

	uint32_t i=0;
	
	while (addr_start < addr_end)
	{
		//每次写入数据是4个字节
		if (FLASH_ProgramWord(addr_start, *(uint32_t *)&pbuf[i]) == FLASH_COMPLETE)
		{
			//地址每次偏移4个字节
			addr_start +=4;
			
			i+=4;
		}

		else
		{ 
			printf("flash write record fail,now goto erase sector!\r\n");
			
			//重新擦除扇区
			flash_erase_record();

			return 1;
		}
	}
	return 0;
}

扇区读取每条记录

/*  扇区读取记录  */ 
void flash_read_record(char *pbuf,uint32_t record_count)
{
	/* 每条记录是64个字节 */
	uint32_t addr_start=ADDR_FLASH_SECTOR_4+record_count*64;
	uint32_t addr_end  =addr_start+64;

	uint32_t i=0;
	
	while (addr_start < addr_end)
	{
		*(uint32_t *)&pbuf[i] = *(__IO uint32_t*)addr_start;

		addr_start+=4;   /* 每次写入4个字节 */
		
		i = i + 4;
	}

}

扇区擦除所有记录

/*  扇区擦除所有记录  */
void flash_erase_record(void)
{

	
	/* 如果不擦除,写入会失败,读取的数据都为0 */
	printf("FLASH_EraseSector start\r\n");
	

	if (FLASH_EraseSector(FLASH_Sector_4, VoltageRange_3) != FLASH_COMPLETE)
	{ 
			printf("Erase error\r\n");
			return;
	}

	printf("FLASH_EraseSector ends\r\n");
}

主要代码

/*解锁FLASH,允许操作FLASH*/
	FLASH_Unlock();

	/* 清空相应的标志位*/  
	FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | 
                   FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR); 	
	
	//尝试获取100条记录
	for(i=0;i<100;i++)
	{
		//获取存储的记录
		flash_read_record(buf,i);
		
		//检查记录是否存在换行符号,不存在则不打印输出
		if(strstr((const char *)buf,"\n")==0)
			break;		
		
	}	
	
	dht11_rec_cnt=i;	
	
	printf("data records count=%d\r\n",dht11_rec_cnt);
	
while(1)
	{
		//rtc唤醒事件
		if(g_rtc_wakeup_event)
		{
			g_rtc_wakeup_event=0;
			
			//开始采集温湿度数据
			if(g_dht11_start)
			{
				//读取温湿度数据
				if(dht11_read_data(dht11_data) == 0)
				{
					//检查当前的数据记录是否小于100条
					if(dht11_rec_cnt<100)
					{
						// 获取日期
						RTC_GetDate(RTC_Format_BCD,&RTC_DateStructure);
						
						// 获取时间
						RTC_GetTime(RTC_Format_BCD, &RTC_TimeStructure);
						
						//格式化字符串,末尾添加\r\n作为一个结束标记,方便我们读取的时候进行判断
						//格式如下:[001]2020/03/25 Week 6 9:40:12 Temp=30.0 Humi=92.0
						sprintf((char *)buf,"[%03d]20%02x/%02x/%02x Week:%x %02x:%02x:%02x Temp=%d.%d Humi=%d.%d\r\n", \
										dht11_rec_cnt,\
										RTC_DateStructure.RTC_Year, RTC_DateStructure.RTC_Month, RTC_DateStructure.RTC_Date,RTC_DateStructure.RTC_WeekDay,\
										RTC_TimeStructure.RTC_Hours, RTC_TimeStructure.RTC_Minutes, RTC_TimeStructure.RTC_Seconds,\
										dht11_data[2],dht11_data[3],dht11_data[0],dht11_data[1]);
					
						//写入温湿度记录
						if(0==flash_write_record(buf,dht11_rec_cnt))
						{
							//显示
							printf(buf);					

							//记录自加1
							dht11_rec_cnt++;						
						}
						else
						{
							//数据记录清零,重头开始存储数据
							dht11_rec_cnt=0;
						}
						
					}
					else
					{
						//超过100条记录则打印
						printf("The record has reached 100 and cannot continue writing\r\n");
					}
				}			
			
			}		
		}

		//判断串口接收到的字符串
		//执行串口1事件
		if(g_usart1_event)
		{
			//判断接收到的字符串为start
			if(strstr((char *)g_usart1_recv_buf,"start"))
			{
				//开启温湿度数据采集
				g_dht11_start =1;
				
				printf("Open temperature and humidity data acquisition\r\n");
												
			}		
			
			//判断接收到的字符串为stop
			if(strstr((char *)g_usart1_recv_buf,"stop"))
			{
				//停止温湿度数据采集
				g_dht11_start =0;
				
				printf("Stop temperature and humidity data acquisition\r\n");
												
			}		

			//判断接收到的字符串为clear
			if(strstr((char *)g_usart1_recv_buf,"clear"))
			{
				//清空所有记录
				printf("Emptying all data records is being executed......\r\n");
				
				//扇区擦除
				flash_erase_record();
				
				printf("Empty all data records successfully\r\n");
				
				//清零记录计数值
				dht11_rec_cnt=0;
												
			}	

			//判断接收到的字符串为show
			if(strstr((char *)g_usart1_recv_buf,"show"))
			{
				printf("Execution temperature data record display:\r\n");
				
				//尝试获取100条记录
				for(i=0;i<100;i++)
				{
					//获取存储的记录
					flash_read_record(buf,i);
					
					//检查记录是否存在换行符号,不存在则不打印输出
					if(strstr(buf,"\n")==0)
						break;		
					
					//打印记录
					printf(buf);
					
				}
				
				//如果i等于0,代表没有一条记录
				if(i==0)
				{
					printf("There is no record\r\n");
				}
												
			}	

			//清空串口1事件
			g_usart1_event=0;
			
			//清空串口1接收数据缓冲区
			memset((uint8_t *)g_usart1_recv_buf,0,sizeof g_usart1_recv_buf);
			
			//清空串口1接收计数值
			g_usart1_recv_cnt=0;
		}	
	}

作者:insanity_7

物联沃分享整理
物联沃-IOTWORD物联网 » 【嵌入式学习03】STM32的“固态硬盘”——flash(闪存)

发表回复