社区闲人 发表于 2024-1-2 14:22:22

重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08

重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08



EEPROM不用先擦再写,可以直接写。但是内部的擦和写仍然不是以字节为单位的,而是以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




社区闲人 发表于 2024-1-2 14:30:12

可以将FLASH的10万+的寿命提升8倍,达100万次。

社区闲人 发表于 2024-1-2 14:39:59

使用方法:

将get_eeprom_CLUST_point() ; 函数放在大循环之前运行,对EEPROM进行初始化,
然后就直接操作eeprom_get_char()和eeprom_put_char()

社区闲人 发表于 2024-1-3 15:03:39

很遗憾地告诉大家,楼主位分享的代码有BUG。

这个BUG产生的情形是:在一个扇区的一个簇的64个字节空间中,只存储有一个字节,这次EEPROM操作要把这个字节写为0,
这个簇全为空。此时簇指针已经指向下一个簇,如果向这个新指向簇再写入数据,让这个簇不空,BUG就不会出现。
而如果不再写入数据,直接关机,就会出现BUG。

仔细回顾我十几年使用EEPROM的经验,此类bug不会出现,决定不修改楼主位的代码。如果坛友认为这个bug有隐患,请自行修改。

赠人玫瑰,手留余香。分享代码,可以让坛友指出bug,也可以督促自己更好的完善代码。让我们共同进步。

社区闲人 发表于 2024-1-3 15:07:27

后续占位

cnos 发表于 2024-1-3 16:08:50

这个只要存储个指针一直往后写就好了,也是均衡磨损的没有那么复杂。
比如实际只有10个字节要保存,那么就把这10个字节往后挪就好了,挪到扇区末尾了就写到下一扇区
所有扇区都走完了就全部擦除从头开始,这样就延长很多倍了。

李鑫发 发表于 2024-1-5 16:38:01

我的做法是,每60秒检测一次数据有没有改动,如果有改动,则写入EEPROM,若没改动,则重新计时检测

LAOXU 发表于 2024-3-19 17:27:21

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 专用.

xxxevery 发表于 2024-3-21 16:33:40

很实用的资料,谢谢分享

zhs2608190303 发表于 2024-3-25 23:00:40

LAOXU 发表于 2024-3-19 17:27
externuint8read_eeprom_u8 (uint16 u16_addr, uint8 u8_FAC);   // 从指定的eeprom/flash地址中读出 ...

很有兴趣 大佬 求指导 我正困惑在这个环节我需要存放大批量的数据或者说事件{:5_272:}
页: [1] 2 3
查看完整版本: 重磅开源:具有均衡磨损算法的FLASH模拟EEPROM函数,彻底抛弃24c02/04/08