前言
在实际项目中总会遇到对数据存储的需求,而大多数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;
}
}
}
}
}
如果错误或是更好的办法可以评论讨论。