重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08
重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08EEPROM不用先擦再写,可以直接写。但是内部的擦和写仍然不是以字节为单位的,而是以page为单位。
你写一个字节,它内部实际动作是读岀整个page,合并入你想写的那个字节,然后整个page写入
本代码用于FLASH作为EEPROM,实现单字节的读写。
为了减少扇区擦除次数,每个扇区只存放64个字节,具有均衡磨损算法。
这样模拟1K容量的EEPROM就需要8K的FLASH空间----16个扇区。
占用了XDATA空间的64字节 + 16字节。
//eeprom.c
#include "eeprom.h"
//#include "STC32G.h"
//#include "config.h"
#define Main_Fosc_MHZ30
//#defineIAP_BASE 0xfe0000 //STC32G12K128,定义EEPROM用MOV访问时的基地址.
#define IAP_BASE0xffc000 //STC32G8K64,定义EEPROM用MOV访问时的基地址. EEPROM长度16k
#define EEPROM_LENGTH16384 //定义EEPROM长度(字节数),与下载程序时选择的EEPROM长度一致, 其中前8k用于模拟1k容量的EEPROM
u8 xdata EP_Buff ;
u8xdataCLUST_point_buff ;
//========================================================================
// 函数: void DisableEEPROM(void)
// 描述: 禁止EEPROM.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void Close_EEPROM(void) //禁止访问EEPROM
{
IAP_CONTR = 0; //关闭 IAP 功能
IAP_CMD = 0; //清除命令寄存器
IAP_TRIG = 0; //清除触发寄存器
IAP_ADDRE = 0xff; //将地址设置到非 IAP 区域
IAP_ADDRH = 0xff; //将地址设置到非 IAP 区域
IAP_ADDRL = 0xff;
}
//========================================================================
// 函数: void EEPROM_Trig(void)
// 描述: 触发EEPROM操作.
// 参数: none.
// 返回: none.
// 版本: V1.0, 2014-6-30
//========================================================================
void EEPROM_Trig(void)
{
u8 ie_ = IE; //保存全局中断
EA = 0; //禁止中断, 避免触发命令无效
_nop_();
IAP_TRIG = 0x5A;
IAP_TRIG = 0xA5; //先送5AH,再送A5H到IAP触发寄存器,每次都需要如此
//送完A5H后,IAP命令立即被触发启动
//CPU等待IAP完成后,才会继续执行程序。
_nop_(); //由于STC32G是多级流水线的指令系统,触发命令后建议加4个NOP,保证IAP_DATA的数据完成准备
_nop_();
_nop_();
_nop_();
IE = ie_; //恢复
}
//========================================================================
// 函数: void EEPROM_SectorErase(u32 EE_address)
// 描述: 擦除一个扇区.
// 参数: EE_address:要擦除的EEPROM的扇区中的一个字节地址.
// 返回: none.
// 版本: V1.0, 2014-6-30
//函数:擦除指定扇区
//输入:(扇区号)
//注:512字节为一个扇区。
//第一扇区(n = 0)起始地址:0000H 结束地址:1FFH
//第二扇区(n = 1)起始地址:0200H 结束地址:3FFH
//第三扇区(n = 2)起始地址:0400H 结束地址:5FFH
//第四扇区(n = 3)起始地址:0600H 结束地址:7FFH
//========================================================================
void EromEraseSector(u8 n) // n ---扇区数。对于8k EEPROM n = 0---15
{
IAP_CONTR = 0x80; //使能IAP //打开IAP 功能,
IAP_TPS = Main_Fosc_MHZ ; //设置Flash 操作等待时间
IAP_CMD = 3 //IAP扇区擦除命令,命令不需改变时,不需重新送命令
IAP_ADDRE = 0x00;
IAP_ADDRH = n * 0x02;
// IAP_ADDRL = (u8)EE_address;
EEPROM_Trig();
Close_EEPROM(); /
}
void get_eeprom_CLUST_point(void) //eeprom_init()
{
u8i, j, k;
bit b_tmp;
u32 addr;
for(i = 0;i < 16; i ++)
{
addr = IAP_BASE + (i * 512);
for(j = 0;j < 8; j ++)
{
addr += 64 * j;
b_tmp = 0;
for(k = 0;k < 64; k ++)
{
if(*(u8 far *)( addr + k) != 0xff)
{
CLUST_point_buff = j;
b_tmp = 1;
break;
}
}
if(b_tmp == 0)
{
if(j == 0)
{
CLUST_point_buff = 0;
}
break;
}
}
}
}
void EEPROM_Write_Byte(u16 addr,u8 new_value)
{
if(new_value != 0x00)
{
IAP_CONTR = 0x80; //使能IAP //打开IAP 功能,
IAP_TPS = Main_Fosc_MHZ ; //设置Flash 操作等待时间
IAP_CMD = 2; //设置IAP写命令,
IAP_ADDRE = 0x00;
IAP_ADDRH = addr / 256;
IAP_ADDRL = addr % 256;
IAP_DATA= ~new_value;
EEPROM_Trig();
Close_EEPROM();
}
}
void EEPROM_write_n(u16 EE_address,u8 number)
{
u8i = 0;
IAP_CONTR = 0x80; //使能IAP //打开IAP 功能,
IAP_TPS = Main_Fosc_MHZ ; //设置Flash 操作等待时间
IAP_CMD = 2; //设置IAP写命令,
do
{
IAP_DATA = ~EP_Buff;
if(IAP_DATA != 0xff)
{
IAP_ADDRE = 0x00;
IAP_ADDRH = EE_address / 256;
IAP_ADDRL = EE_address % 256;
EEPROM_Trig();
}
EE_address ++;
i ++;
}while(--number);
Close_EEPROM();
}
u8 eeprom_get_char(u16 addr)
{
u32 addr32;
u8sector = addr / 64; //addr >> 6
addr32 = (((addr & 0x03c0) * 8) | (addr & 0x3f) | IAP_BASE) + 64 * CLUST_point_buff;
return (~(*(u8 far *)addr32));
}
void eeprom_put_char( u16 addr, char new_value )
{
u8i;
u32 addr32;
u8sector ;
u8sector_per_cnt;
u8 tmp = eeprom_get_char(addr);
if(tmp == new_value)return;
sector_per_cnt = addr & 0x3f; //addr % 64;
sector = addr / 64;
addr = ((addr & 0x03c0) * 8) + (64 * CLUST_point_buff) ;
addr32 = addr | IAP_BASE;
if(tmp != 0x00)
{
for(i = 0;i < 64; i ++)
{
EP_Buff = ~(*(u8 far *)(addr32 + i));
}
EP_Buff = new_value;
if(CLUST_point_buff == 7)
{
EromEraseSector(sector);
addr -= 64 * CLUST_point_buff;
CLUST_point_buff = 0;
}
else
{
CLUST_point_buff += 1;
addr += 64;
}
EEPROM_write_n(addr,64);
}
else //可以直接写入
{
addr += sector_per_cnt;
EEPROM_Write_Byte(addr ,new_value);
}
}
//eeprom.h
#ifndef eeprom_h
#define eeprom_h
//#include "config.h"
#include "STC32G.h"
void get_eeprom_CLUST_point(void) ;//eeprom_init()
u8 eeprom_get_char(u16 addr);
void eeprom_put_char(unsigned int addr, char new_value);
#endif
可以将FLASH的10万+的寿命提升8倍,达100万次。 使用方法:
将get_eeprom_CLUST_point() ; 函数放在大循环之前运行,对EEPROM进行初始化,
然后就直接操作eeprom_get_char()和eeprom_put_char() 很遗憾地告诉大家,楼主位分享的代码有BUG。
这个BUG产生的情形是:在一个扇区的一个簇的64个字节空间中,只存储有一个字节,这次EEPROM操作要把这个字节写为0,
这个簇全为空。此时簇指针已经指向下一个簇,如果向这个新指向簇再写入数据,让这个簇不空,BUG就不会出现。
而如果不再写入数据,直接关机,就会出现BUG。
仔细回顾我十几年使用EEPROM的经验,此类bug不会出现,决定不修改楼主位的代码。如果坛友认为这个bug有隐患,请自行修改。
赠人玫瑰,手留余香。分享代码,可以让坛友指出bug,也可以督促自己更好的完善代码。让我们共同进步。 后续占位 这个只要存储个指针一直往后写就好了,也是均衡磨损的没有那么复杂。
比如实际只有10个字节要保存,那么就把这10个字节往后挪就好了,挪到扇区末尾了就写到下一扇区
所有扇区都走完了就全部擦除从头开始,这样就延长很多倍了。 我的做法是,每60秒检测一次数据有没有改动,如果有改动,则写入EEPROM,若没改动,则重新计时检测 externuint8read_eeprom_u8 (uint16 u16_addr, uint8 u8_FAC); // 从指定的eeprom/flash地址中读出char型变量
externuint16 read_eeprom_u16(uint16 u16_addr, uint8 u8_FAC); // 从指定的eeprom/flash地址中读出int型变量
externuint32 read_eeprom_u32(uint16 u16_addr, uint8 u8_FAC); // 从指定的eeprom/flash地址中读出long型变量
extern bit write_eeprom_u8 (uint8u8_data,uint16 u16_addr, uint8 u8_FAC);// 将char型变量写入指定的eeprom/flash中
extern bit write_eeprom_u16(uint16 u16_data, uint16 u16_addr, uint8 u8_FAC);// 将int型变量写入指定的eeprom/flash中
externbit write_eeprom_u32(uint32 u32_data, uint16 u16_addr, uint8 u8_FAC);// 将long型变量写入指定的eeprom/flash中
externbit write_eeprom_str(uint16 u16_addr, uint16 u16_len, uint8 *u8_data, uint8 u8_FAC); // 将char*指向的字符串写入指定的eeprom/flash中
extern void read_eeprom_str(uint16 u16_addr, uint16 u16_len, uint8 *u8_data, uint8 u8_FAC); // 从eeprom/flash中读出字符串, 存储在char*指向的RAM中
extern void erase_eeprom(uint8 u8_XPAGE, uint8 u8_XPAGE1, uint8 u8_FAC);// 擦除eeprom/flash的指定页,要求page连续送两遍,以提高抗干扰性(注:适用标准型号flash page=1k)
extern bitread_eeprom_like(uint8 u8_XPAGE, uint8 u8_len, uint8 *u8_data, uint8 u8_FAC); // 从指定页循环使用的eeprom/flash空间内,读出长度为u8_LEN的字符串
extern bit write_eeprom_like(uint8 u8_XPAGE, uint8 u8_len, uint8 *u8_data, uint8 u8_FAC); // 从指定页循环使用的eeprom/flash空间内,写入长度为u8_LEN的字符串
// 入口要求: u8_FAC=1 指定eeprom时,u8_len > 1;u8_FAC=0 指定flash时,u8_len > 4 (注:适用标准型号flash page=1k)
// 返回:C=0 --> 读写成功,C=1 --> u8_len长度不符规定或末成功写入
这是早期我写的 eeprom/flash 程序(中颖51), 能指定页循环, 使用的eeprom/flash空间内,读/写长度为u8_LEN的字符串
大家如有兴趣, 我抽空改写成 STC51 专用.
很实用的资料,谢谢分享 LAOXU 发表于 2024-3-19 17:27
externuint8read_eeprom_u8 (uint16 u16_addr, uint8 u8_FAC); // 从指定的eeprom/flash地址中读出 ...
很有兴趣 大佬 求指导 我正困惑在这个环节我需要存放大批量的数据或者说事件{:5_272:}