找回密码
 立即注册
查看: 3282|回复: 26

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

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:513
  • 最近打卡:2025-05-01 04:31:49

24

主题

515

回帖

986

积分

荣誉版主

积分
986
发表于 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 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:513
  • 最近打卡:2025-05-01 04:31:49

24

主题

515

回帖

986

积分

荣誉版主

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

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:513
  • 最近打卡:2025-05-01 04:31:49

24

主题

515

回帖

986

积分

荣誉版主

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

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

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:513
  • 最近打卡:2025-05-01 04:31:49

24

主题

515

回帖

986

积分

荣誉版主

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

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

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

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

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:513
  • 最近打卡:2025-05-01 04:31:49

24

主题

515

回帖

986

积分

荣誉版主

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

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:386
  • 最近打卡:2025-04-30 22:13:28
已绑定手机

8

主题

137

回帖

1045

积分

金牌会员

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

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:289
  • 最近打卡:2025-04-30 17:31:36
已绑定手机

2

主题

182

回帖

1240

积分

金牌会员

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

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:312
  • 最近打卡:2025-03-11 13:20:13

54

主题

1327

回帖

5395

积分

荣誉版主

积分
5395
发表于 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 专用.

回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:391
  • 最近打卡:2025-04-30 00:26:42

0

主题

336

回帖

1490

积分

金牌会员

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

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:7
  • 最近打卡:2024-07-14 13:13:18

7

主题

20

回帖

128

积分

注册会员

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

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

使用道具 举报 送花

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

本版积分规则

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

GMT+8, 2025-5-2 00:03 , Processed in 0.121341 second(s), 110 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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