找回密码
 立即注册
查看: 600|回复: 9

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

[复制链接]
  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 152 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1281

    积分

    荣誉版主

    积分
    1281
    发表于 2024-1-2 14:22:22 | 显示全部楼层 |阅读模式
    重磅开源:具有均衡磨损算法的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_MHZ  30


    //#define  IAP_BASE        0xfe0000        //STC32G12K128,  定义EEPROM用MOV访问时的基地址.

    #define   IAP_BASE  0xffc000        //STC32G8K64,  定义EEPROM用MOV访问时的基地址. EEPROM长度16k
    #define   EEPROM_LENGTH  16384   //定义EEPROM长度(字节数),与下载程序时选择的EEPROM长度一致, 其中前8k用于模拟1k容量的EEPROM





    u8 xdata EP_Buff[64] ;  
    u8  xdata  CLUST_point_buff[16] ;


    //========================================================================
    // 函数: 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()
    {
            u8  i, 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[i] = j;
                        b_tmp = 1;
                        break;
                    }
                }

                if(b_tmp == 0)               
                {
                    if(j == 0)
                    {
                        CLUST_point_buff[i] = 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)
    {
        u8  i = 0;

        IAP_CONTR = 0x80; //使能IAP                //打开IAP 功能,
        IAP_TPS = Main_Fosc_MHZ ; //设置Flash 操作等待时间
        IAP_CMD = 2;        //设置IAP写命令,
        do
        {
            IAP_DATA = ~EP_Buff[i];                 
            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;
        u8  sector = addr / 64; //addr >> 6

        addr32 = (((addr & 0x03c0) * 8) | (addr & 0x3f) | IAP_BASE) + 64 * CLUST_point_buff[sector];

        return (~(*(u8 far *)addr32));
    }

    void eeprom_put_char( u16 addr, char new_value )
    {
        u8  i;
        u32 addr32;

        u8  sector ;
        u8  sector_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[sector]) ;
        addr32 = addr | IAP_BASE;
        if(tmp != 0x00)       
        {
            for(i = 0;i < 64; i ++)
            {
                EP_Buff[i] = ~(*(u8 far *)(addr32 + i));
            }

            EP_Buff[sector_per_cnt] = new_value;

            if(CLUST_point_buff[sector] == 7)       
            {
                EromEraseSector(sector);
                
                addr -= 64 * CLUST_point_buff[sector];       
                CLUST_point_buff[sector] = 0;
            }
            else
            {
                CLUST_point_buff[sector] += 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




    1 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
    回复 送花

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 152 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1281

    积分

    荣誉版主

    积分
    1281
     楼主| 发表于 2024-1-2 14:30:12 | 显示全部楼层
    可以将FLASH的10万+的寿命提升8倍,达100万次。
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 152 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1281

    积分

    荣誉版主

    积分
    1281
     楼主| 发表于 2024-1-2 14:39:59 | 显示全部楼层
    使用方法:

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

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 152 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1281

    积分

    荣誉版主

    积分
    1281
     楼主| 发表于 2024-1-3 15:03:39 | 显示全部楼层
    很遗憾地告诉大家,楼主位分享的代码有BUG。

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

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

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

    使用道具 举报

  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 152 天

    [LV.7]常住居民III

    17

    主题

    370

    回帖

    1281

    积分

    荣誉版主

    积分
    1281
     楼主| 发表于 2024-1-3 15:07:27 | 显示全部楼层
    后续占位
    回复 送花

    使用道具 举报

  • TA的每日心情
    开心
    昨天 09:46
  • 签到天数: 92 天

    [LV.6]常住居民II

    2

    主题

    56

    回帖

    370

    积分

    中级会员

    积分
    370
    发表于 2024-1-3 16:08:50 | 显示全部楼层
    这个只要存储个指针一直往后写就好了,也是均衡磨损的没有那么复杂。
    比如实际只有10个字节要保存,那么就把这10个字节往后挪就好了,挪到扇区末尾了就写到下一扇区
    所有扇区都走完了就全部擦除从头开始,这样就延长很多倍了。
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 14:49
  • 签到天数: 68 天

    [LV.6]常住居民II

    2

    主题

    88

    回帖

    532

    积分

    高级会员

    积分
    532
    发表于 2024-1-5 16:38:01 | 显示全部楼层
    我的做法是,每60秒检测一次数据有没有改动,如果有改动,则写入EEPROM,若没改动,则重新计时检测
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    10 小时前
  • 签到天数: 131 天

    [LV.7]常住居民III

    37

    主题

    867

    回帖

    3934

    积分

    荣誉版主

    积分
    3934
    发表于 2024-3-19 17:27:21 | 显示全部楼层
    extern  uint8  read_eeprom_u8 (uint16 u16_addr, uint8 u8_FAC);     // 从指定的eeprom/flash地址中读出char型变量  
    extern  uint16 read_eeprom_u16(uint16 u16_addr, uint8 u8_FAC);     // 从指定的eeprom/flash地址中读出int型变量  
    extern  uint32 read_eeprom_u32(uint16 u16_addr, uint8 u8_FAC);     // 从指定的eeprom/flash地址中读出long型变量  
    extern        bit write_eeprom_u8 (uint8  u8_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中
    extern  bit write_eeprom_u32(uint32 u32_data, uint16 u16_addr, uint8 u8_FAC);  // 将long型变量写入指定的eeprom/flash中  
    extern  bit 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        bit  read_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 专用.

    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    开心
    7 小时前
  • 签到天数: 52 天

    [LV.5]常住居民I

    0

    主题

    177

    回帖

    324

    积分

    中级会员

    积分
    324
    发表于 2024-3-21 16:33:40 | 显示全部楼层
    很实用的资料,谢谢分享
    回复 支持 反对 送花

    使用道具 举报

  • TA的每日心情
    无聊
    2024-3-22 00:13
  • 签到天数: 4 天

    [LV.2]偶尔看看I

    4

    主题

    13

    回帖

    72

    积分

    注册会员

    积分
    72
    发表于 2024-3-25 23:00:40 | 显示全部楼层
    LAOXU 发表于 2024-3-19 17:27
    extern  uint8  read_eeprom_u8 (uint16 u16_addr, uint8 u8_FAC);     // 从指定的eeprom/flash地址中读出 ...

    很有兴趣 大佬 求指导 我正困惑在这个环节  我需要存放大批量的数据或者说事件
    回复 支持 反对 送花

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

    GMT+8, 2024-4-27 17:00 , Processed in 0.071374 second(s), 65 queries .

    Powered by Discuz! X3.5

    © 2001-2024 Discuz! Team.

    快速回复 返回顶部 返回列表