Flash模拟EEPROM

前言

在实际项目中总会遇到对数据存储的需求,而大多数MCU内置的ROM都是Flash,Flash和EEPROM主要差别是FLASH按块/扇区进行读写操作,且写之前需擦除原有数据,EEPROM支持按字节读写操作。在如今MCU主频如此高的情况下,可以尝试用Flash模拟EEPROM来降低成本。

前提条件

在这里讲一下我使用的方法,需要的前提条件、Flash规格以及模拟出来的EEPROM规格:

前提:
1.MCU至少留1.5KB RAM。

我使用3页Flash模拟了256Byte EEPROM,数据有备份和CRC8校验。

  • FLASH_EEPROM_DATAPAGE1_ADDR:用于读写数据。
  • FLASH_EEPROM_DATAPAGE2_ADDR:用于备份数据。
  • FLASH_EEPROM_DATAPAGE3_ADDR:用于存储CRC校验值。

Flash规格:
1.写仅支持32位操作。
2.擦除最小块是一页,0.5KB。
3.32位编程时间175us,单页擦除时间2.23ms。

模拟EEPROM规格:
1.容量256Byte。

一、总体思路

总体思路
我与上位机通讯交互使用的是USART,在通讯中断中更新缓冲区数据,主循环中再去检测是否有新数据需要处理。

环形缓冲区

环形缓冲区的主要作用是临时存储主循环还未来得及处理的数据,这就有个要求,单次写入的数据在填满环形缓冲区后,下次写入的数据可能会丢失,这个是必然的,只能加大环形缓冲区或者限制两次写入的间隔时间,不过正常情况只要单次写入数据未填满环形缓冲区,是不会丢失数据的。

typedef struct
{
	//缓冲区数据头,记录下一个待操作值的位置
	uint16_t Head;
	//缓冲区数据尾,记录下一个数据存放的位置
	uint16_t Tail;
	//缓冲区数据长度,记录当前缓冲区中未操作的数据长度
	uint16_t Lenght;
	//缓冲区
	uint16_t Ring_Buffer[FLASH_EEPROM_RING_SIZE];
}RingBuffer_EEPROM_h;

Flash数据缓冲区

用于临时存放从Flash中读取的数据。

uint32_t EEPROM_DataBuffer[FLASH_PAGE_SIZE / 4];

EEPROM数据缓冲区

用于存储模拟EEPROM数据 。

uint8_t EEPROM_BUFFER[FLASH_EEPROM_SIZE] = {0};

二、写入数据

void Flash_EEPROM_WriteData(void)
{
		uint16_t i,OutData;
		uint8_t WriteAddr,CRCData;
		uint32_t FlashData,WriteData;	
		if(RingBuffer_EEPROM.Lenght == 0)
		{
				//缓冲区为空
				return;
		}
		OutData = RingBuffer_EEPROM.Ring_Buffer[RingBuffer_EEPROM.Head];
		WriteAddr = (uint8_t)(OutData >> 8);
		
		FlashData = Flash_ReadWord(FLASH_EEPROM_DATAPAGE1_ADDR + (WriteAddr / 2) * 4);
				
		//先读取该数据在Flash中是否有效(已经写过)
		if((uint8_t)FlashData == FLASH_VALUE_VALID)
		{
				//已经写过
				for(i = 0;i < FLASH_PAGE_SIZE / 4;i++)
				{
						EEPROM_DataBuffer[i] = Flash_ReadWord(FLASH_EEPROM_DATAPAGE1_ADDR + i * 4);
				}
									
				//32位数据最高字节放奇数地址,中字节放偶数数据,最低字节放是否写过标志
				if(WriteAddr % 2)
				{
						//写入地址为奇数(数据放在最高字节)						
						WriteData = ((FlashData & 0x00FF00FF) | ((uint8_t)OutData << 24) | FLASH_VALUE_VALID);
				}
				else
				{
						//写入地址为偶数(数据放在中字节)	
						WriteData = ((FlashData & 0xFF0000FF) | ((uint8_t)OutData << 16) | FLASH_VALUE_VALID);						
				}		
				
				//替换要写的地址数据			
				EEPROM_DataBuffer[WriteAddr / 2] = WriteData;
		
				//擦除页
				if(!Flash_EraseChip(FLASH_EEPROM_DATAPAGE1_ADDR))
				{
						//擦除页失败
						Send_Status(STATUS_FLASH_ERASEPAGE_FAIL,true);
				}		
				
				//写入数据
				for(i = 0;i < FLASH_PAGE_SIZE/4;i++)
				{
						//只写写过的地址
						if((uint8_t)EEPROM_DataBuffer[i] != FLASH_VALUE_VALID)
						{
								continue;
						}
						if(!Flash_WriteWord(FLASH_EEPROM_DATAPAGE1_ADDR + i * 4,EEPROM_DataBuffer[i],false))
						{
								Send_Status(STATUS_FALSH_WRITEWORD_FAIL,true);
						}
				}	
		}
		else
		{
				//未写过
				//32位数据最高字节放奇数地址,中字节放偶数数据,最低字节放是否写过标志
				if(WriteAddr % 2)
				{
						//奇数
						WriteData = (0x00000000 | ((uint8_t)OutData << 24) | FLASH_VALUE_VALID);						
				}
				else
				{
						//偶数
						WriteData = (0x00000000 | ((uint8_t)OutData << 16) | FLASH_VALUE_VALID);							
				}				
				
				if(!Flash_WriteWord(FLASH_EEPROM_DATAPAGE1_ADDR + (WriteAddr / 2) * 4,WriteData,false))
				{
						Send_Status(STATUS_FALSH_WRITEWORD_FAIL,true);
				}				
		}	
		Ring_EEPROM_Out(&OutData);
		//计算校验值,EEPROM中含有温度数据,掉电不保持
		//CRC8_Calculat_07,CRC8校验,这个自己可以任选多项式以及校验长度
		CRCData = CRC8_Calculat_07(EEPROM_BUFFER,FLASH_EEPROM_SIZE - 1);
		//擦除页并写入校验值
		if(!Flash_WriteWord(FLASH_EEPROM_DATAPAGE3_ADDR,(CRCData << 24 | FLASH_VALUE_VALID),true))
		{
				Send_Status(STATUS_FALSH_WRITEWORD_FAIL,true);
		}				
}

三、加载数据

写入数据后,MCU每次上电还需要预加载数据,将Flash中的数据读取到EERPOM缓冲区中,避免每次读取数据时还需从Flash中读取。

void Flash_EEPROM_Data_Init(void)
{
		uint8_t i,CRC_EEPROMData;
		uint16_t j;
		bool WritePage_CRCError= false;
		uint32_t CRC_FlashData,Read_Data;	
	
		j = 0;					
		//读取写入页(0-255)数据,复制到EEPROM缓冲区
		for(i = 0;i < FLASH_PAGE_SIZE/4;i++)
		{
				//32位数据最高字节放奇数地址,中字节放偶数数据,低字节放高字节是否写过标志,最低字节放中字节是否写过标志
				Read_Data = Flash_ReadWord(FLASH_EEPROM_DATAPAGE1_ADDR+i*4);
				if((uint8_t)Read_Data == FLASH_VALUE_VALID)
				{						
						EEPROM_BUFFER[j] = (uint8_t)(Read_Data >> 16);	
						EEPROM_BUFFER[j + 1] = (uint8_t)(Read_Data >> 24);	
				}				
				j += 2;
		}
		
		//判断写入页数据校验是否正确
		
		//读CRC校验
		CRC_FlashData = Flash_ReadWord(FLASH_EEPROM_DATAPAGE3_ADDR);
		//判断CRC校验值是否有效(是否有数据写入)
		if((uint8_t)CRC_FlashData == FLASH_VALUE_VALID)
		{
				CRC_EEPROMData = CRC8_Calculat_07(EEPROM_BUFFER,FLASH_EEPROM_SIZE - 1);
				if((uint8_t)(CRC_FlashData >> 24) != CRC_EEPROMData)
				{
						//EEPROM写入页数据CRC校验错误
						WritePage_CRCError = true;
						Send_Status(STATUS_EEPROM_WRITEPAGE_CRC_ERROR,false);
				}
				
				//判断EEPROM写入页CRC校验
				if(WritePage_CRCError)
				{
						//校验错误
						//加载备份页数据
						Read_Data = Flash_ReadWord(FLASH_EEPROM_DATAPAGE2_ADDR);
						//备份页CRC值是否有效
						if((uint8_t)Read_Data == FLASH_VALUE_VALID)
						{
								j = 0;
								for(i = 0;i < FLASH_PAGE_SIZE/4;i++)
								{
										Read_Data = Flash_ReadWord(FLASH_EEPROM_DATAPAGE2_ADDR+i*4);			
										//低16位
										EEPROM_BUFFER[j] = (uint8_t)(Read_Data >> 16);
										EEPROM_BUFFER[j+1] = (uint8_t)(Read_Data >> 24);
										j += 2;
								}
								//读取备份页CRC数据			
								CRC_EEPROMData = (uint8_t)(Flash_ReadWord(FLASH_EEPROM_DATAPAGE2_ADDR) >> 8);
								if((uint8_t)(CRC_FlashData >> 24) != CRC_EEPROMData)
								{
										//EEPROM备份页数据CRC校验错误				
										Send_Status(STATUS_EEPROM_BACKUPPAGE_CRC_ERROR,true);
								}
								else
								{
										//写入页CRC校验错误,备份页CRC校验正确
										//擦除写入页,写入备份页数据
										if(!Flash_EraseChip(FLASH_EEPROM_DATAPAGE1_ADDR))
										{
												//擦除页失败
												Send_Status(STATUS_FLASH_ERASEPAGE_FAIL,true);
										}
										j = 0;
										//写入数据
										for(i = 0;i < FLASH_PAGE_SIZE/4;i++)
										{							
												Read_Data = ((EEPROM_BUFFER[j + 1] << 24) | (EEPROM_BUFFER[j] << 16) | FLASH_VALUE_VALID);										
												if(!Flash_WriteWord(FLASH_EEPROM_DATAPAGE1_ADDR + i * 4,Read_Data,false))
												{
														Send_Status(STATUS_FALSH_WRITEWORD_FAIL,true);
												}
												j += 2;
										}
								}
						}
						else
						{
								//写入页CRC校验错误,且备份区CRC校验无效
								Send_Status(STATUS_EEPROM_WRITEPAGE_CRC_ERROR,true);
						}						
				}
				else
				{
						//校验正确
						//检测备份页是否需要擦除(CRC值相同不擦除)
						CRC_EEPROMData = (uint8_t)(Flash_ReadWord(FLASH_EEPROM_DATAPAGE2_ADDR) >> 8);	
						if((uint8_t)(CRC_FlashData >> 24) != CRC_EEPROMData)
						{
								//擦除备份页
								if(!Flash_EraseChip(FLASH_EEPROM_DATAPAGE2_ADDR))
								{
										//擦除页失败
										Send_Status(STATUS_FLASH_ERASEPAGE_FAIL,true);
								}
								j = 0;
								//写入数据
								for(i = 0;i < FLASH_PAGE_SIZE/4;i++)
								{							
										Read_Data = ((EEPROM_BUFFER[j + 1] << 24)| (EEPROM_BUFFER[j] << 16) | ((uint8_t)CRC_FlashData << 8) | FLASH_VALUE_VALID);										
										if(!Flash_WriteWord(FLASH_EEPROM_DATAPAGE2_ADDR + i * 4,Read_Data,false))
										{
												Send_Status(STATUS_FALSH_WRITEWORD_FAIL,true);
										}
										j += 2;
								}
						}						
				}
		}
}

如果错误或是更好的办法可以评论讨论。

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值