在很多设备中,需要存储几个字节的参数,且需要掉电保存。如果新增一个eeprom或一片Flash,会增加bom成本。对于这种掉电存储的且不需要频繁擦写的参数,可以利用mcu内部的Flash进行模拟eeprom。
优点: 1. 节约成本,可减少外部芯片的使用;
2. 读写速率快,MCU内部的Flash读写速率要远远高于EEPROM;
3. 抗干扰能力强,MCU内部的Flash没有使用I2C、SPI这类通讯总线,不存在这类总线被干扰的问题。
步骤1. 开辟mcu内部三个扇区作为存储空间:
所以代码中使用了 1~3 这三个扇区:
+--------------------------------------------------------------+
| Flash |
+-------------------------------+------------------------------+
| Code | User Para area | Code |
+--------+--------+--------+--------+--------+-----------------+
| 16KB | 16KB | 16KB | 16KB | 64KB | 128KB | 128KB |
| sector | sector | sector | sector | sector | sector | sector |
| 0 | 1 | 2 | 3 | 4 | N | N + 1 |
| | | | | | | |
+--------+--------+--------+--------+--------+-----------------+
第二步: 写读写接口:
/* flash sector satrt address */
#define ADDR_FLASH_SECTOR_1 ((uint32_t)0x08004000) /* 16 Kbytes */
#define ADDR_FLASH_SECTOR_2 ((uint32_t)0x08008000) /* 16 Kbytes */
#define ADDR_FLASH_SECTOR_3 ((uint32_t)0x0800C000) /* 16 Kbytes */
/* flash sector size */
#define FLASH_SECTOR_SIZE ((uint32_t)(1024 * 16))
/* flash emulation eeprom total size. This value must be a multiple of 16KB */
#define FLASH_EE_TOTAL_SIZE ((uint32_t)(1024 * 16 * 2))
/* flash emulation eeprom sector start address, it's must be sector aligned */
#define FLASH_EE_START_ADDR ADDR_FLASH_SECTOR_1
/* flash emulation eeprom sector start address */
#define FLASH_EE_END_ADDR (ADDR_FLASH_SECTOR_1 + FLASH_EE_TOTAL_SIZE)
Write接口内部自带flash 擦除操作哦,争对用户来说,只需要按照eeprom的操作方式读写即可:
/*!
* [url=home.php?mod=space&uid=159083]@brief[/url] Write the specified length of data from the specified address.
* Can be written across sectors.
*
* @param WriteAddr: write address.
*
* @param pData: save the write data.
*
* @param len: write data length.
*
* @retval None.
*
* @note The example must be performed in the sectors 1~3.
*
*/
void Flash_EE_Write(uint32_t WriteAddr, uint8_t* pData, uint32_t len)
{
uint32_t NumOfSector = 0, NumOfByte = 0, OfsetAddr = 0;
uint32_t count = 0, temp = 0;
/* offerset address in the sector */
OfsetAddr = WriteAddr % FLASH_SECTOR_SIZE;
/* The size of the remaining space inthe sector from WriteAddr */
count = FLASH_SECTOR_SIZE - OfsetAddr;
/* Calculate how many sectors to write */
NumOfSector = len / FLASH_SECTOR_SIZE;
/* Calculate how many bytes are left less than one sector */
NumOfByte = len % FLASH_SECTOR_SIZE;
/* OfsetAddr = 0, WriteAddr is sector aligned */
if (OfsetAddr == 0)
{
/* len < FLASH_SECTOR_SIZE */
if (NumOfSector == 0)
{
Flash_EE_WriteOneSector(WriteAddr, pData, len);
}
/* len > FLASH_SECTOR_SIZE */
else
{
/* write NumOfSector sector */
while (NumOfSector--)
{
Flash_EE_WriteOneSector(WriteAddr, pData, FLASH_SECTOR_SIZE);
WriteAddr += FLASH_SECTOR_SIZE;
pData += FLASH_SECTOR_SIZE;
}
/* write remaining data */
Flash_EE_WriteOneSector(WriteAddr, pData, NumOfByte);
}
}
/* OfsetAddr != 0, WriteAddr is not sector aligned */
else
{
/* len < FLASH_SECTOR_SIZE, the data length is less than one sector */
if (NumOfSector == 0)
{
/* NumOfByte > count, need to write across the sector */
if (NumOfByte > count)
{
temp = NumOfByte - count;
/* fill the current sector */
Flash_EE_WriteOneSector(WriteAddr, pData, count);
WriteAddr += count;
pData += count;
/* write remaining data */
Flash_EE_WriteOneSector(WriteAddr, pData, temp);
}
else
{
Flash_EE_WriteOneSector(WriteAddr, pData, len);
}
}
/* len > FLASH_SECTOR_SIZE */
else
{
len -= count;
NumOfSector = len / FLASH_SECTOR_SIZE;
NumOfByte = len % FLASH_SECTOR_SIZE;
/* write count data */
Flash_EE_WriteOneSector(WriteAddr, pData, count);
WriteAddr += count;
pData += count;
/* write NumOfSector sector */
while (NumOfSector--)
{
Flash_EE_WriteOneSector(WriteAddr, pData, FLASH_SECTOR_SIZE);
WriteAddr += FLASH_SECTOR_SIZE;
pData += FLASH_SECTOR_SIZE;
}
if (NumOfByte != 0)
{
Flash_EE_WriteOneSector(WriteAddr, pData, NumOfByte);
}
}
}
}
读接口:
static uint8_t Flash_EE_ReadByte(uint32_t Addr)
{
return (*(__IO uint8_t*)Addr);
}
然后,用一个测试程序进行测试:
void eeprom_test(void)
{
/* flash emulation eeprom test address */
#define FLASH_EE_TEST_ADDR ((uint32_t)0x08008000)
/* test write buffer */
static uint8_t Test_Write_buffer[64];
/* test read buffer */
static uint8_t Test_Read_buffer[64];
/* fill Test_Write_buffer */
for (int i = 0; i< 64; i++)
{
Test_Write_buffer[i] = i;
}
/* write the specified sector address data */
Flash_EE_Write(FLASH_EE_TEST_ADDR, Test_Write_buffer, 64);
/* read the specified sector address data */
Flash_EE_Read(FLASH_EE_TEST_ADDR, Test_Read_buffer, 64);
/* compare the values of two buffers for equality */
for (int i = 0; i < 64; i++)
{
if (Test_Write_buffer[i] != Test_Read_buffer[i])
{
printf("Test Error!\r\n");
}
}
printf("Test Successful!\r\n");
/* write the specified sector address data */
Flash_EE_Write(FLASH_EE_TEST_ADDR + 128, Test_Write_buffer, 64);
}
测试发现:读出来的和写入的一致:
我打算在原来写入的地址处偏移128字节处再次写入64字节,看之前写入的是否会被清除:
结论:这个模拟eeprom方式很实用,也可以移植到其他mcu上。
eePROM的擦写寿命是FLASH的10倍以上,用FLASH模拟出来也只适合做参数配置类的数据保存,不适合做记录数据等存储。所以像ST等大部分芯片,把FLASH的最后几个扇区的工艺改进,延长FLASH的擦写寿命,如果要用FLASH当EEPROM用,建议还是参考芯片手册,使用它为此增强的那部分FLASH空间。
今天回头测试,发现之前的代码有bug:不同的编译器需要分开定义静态数组:
#ifdef MDK
/* Specifies the start address of the sector. The purpose is to occupy a space at the specified address of MCU flash. */
static const uint8_t Flash_Para_Area[FLASH_EE_TOTAL_SIZE] __attribute__((section(".ARM.__at_0x08004000")));
#else
__root static const uint8_t Flash_Para_Area[FLASH_EE_TOTAL_SIZE]@ FLASH_EE_START_ADDR = {0};
#endif