您的位置:首页 > 其它

治标治本,彻底解决AVR单片机EEPROM数据丢失问题

2015-09-06 09:04 323 查看
在项目中复制出来的程序,使用时可能有些地方需要修改。 

编译环境:WinAVR-20060421 + AVR Studio 4.12.498  Service Pack 4 

基本思路:每份写到EEPRM的数据,都做三个备份,每个备份的数据都做CRC16校验,只要系统运行中出错,错误地修改了EEPROM数据, 

          那么根据校验字节就知道哪个备份的数据被修改了,然后用正确的备份覆盖出错的备份,达到数据恢复的目的。 

EEPROMSave.h 文件:

/*   EEPROM管理定义   */

#define EepromPageSize        64                        //页容量定义

#define EepromPage0Addr        0x0000                        //各个页的其始地址定义 

#define EepromPage1Addr        (EepromPage0Addr + EepromPageSize) 

#define EepromPage2Addr        (EepromPage1Addr + EepromPageSize) 

#define EepromPage3Addr        (EepromPage2Addr + EepromPageSize) 

#define EepromPage4Addr        (EepromPage3Addr + EepromPageSize) 

#define EepromPage5Addr        (EepromPage4Addr + EepromPageSize) 

#define EepromPage6Addr        (EepromPage5Addr + EepromPageSize) 

#define EepromPage7Addr        (EepromPage6Addr + EepromPageSize)

/* 

最后两个字节为CRC16校验码,其余为数据

| 0 | 1 | 2 |        |.......................| 61 | 62 | 63 | 

Data              Data...................Data.....CRCH CRCL 

*/

#define VALID                                0x01 

#define INVALID                                0x00

/*-----------------------------------------------------------------------------------------*/

 

EEPROMSave.c 文件:

/******************************************************************* 

*函数名称:EepromReadByte() 

*函数功能:写一个Byte的数据进EEPROM 

*输入参数:address:地址 

*返回参数:从指定地址读出来的数据 

*编写作者:my_avr 

*编写时间:2007年8月13日 

*相关说明: 

********************************************************************/ 

unsigned char EepromReadByte(unsigned char *address) 



        unsigned char data; 

         

        data = 0; 

         

        eeprom_busy_wait(); 

        data  = eeprom_read_byte(address); 

         

        return data; 

}

/******************************************************************* 

*函数名称:EepromReadWord(); 

*函数功能:写一个Word的数据进EEPROM 

*输入参数:address:地址 

*返回参数:从指定地址读出来的数据 

*编写作者:my_avr 

*编写时间:2007年8月13日 

*相关说明: 

********************************************************************/ 

uint16_t EepromReadWord(uint16_t *address) 



        uint16_t data; 

         

        data = 0; 

         

        eeprom_busy_wait(); 

        data  = eeprom_read_word(address); 

         

        return data; 

}

/******************************************************************* 

*函数名称:EepromWriteByte() 

*函数功能:写一个Byte的数据进EEPROM 

*输入参数:address:地址;data:数据 

*返回参数:无 

*编写作者:my_avr 

*编写时间:2007年8月13日 

*相关说明: 

********************************************************************/ 

void EepromWriteByte(unsigned char *address,unsigned char data) 



        eeprom_busy_wait(); 

        eeprom_write_byte(address,data); 

}

/******************************************************************* 

*函数名称:EepromWriteWord() 

*函数功能:写一个Word的数据进EEPROM 

*输入参数:address:地址;data:数据 

*返回参数: 

*编写作者:my_avr 

*编写时间:2007年8月13日 

*相关说明: 

********************************************************************/ 

void EepromWriteWord(unsigned int *address,unsigned int data) 



        eeprom_busy_wait(); 

        eeprom_write_word(address,data); 

}

/******************************************************************* 

*函数名称:EepromWriteBlock() 

*函数功能:将缓冲区中的n个数据写进EEPROM 

*输入参数:address:地址;data:数据 

*返回参数: 

*编写作者:my_avr 

*编写时间:2007年8月13日 

*相关说明: 

********************************************************************/ 

void EepromWriteBlock(unsigned char *buff,unsigned char *address,unsigned char n) 



        unsigned char i; 

         

        for (i = 0; i < n; i++) 

        { 

                EepromWriteByte((unsigned char *)(address + i),*buff); 

                 

                buff++; 

        } 

}

/****************************************************************** 

*函数名称:unsigned char EepromCheck(unsigned char *pdata,unsigned char packsize) 

*函数功能:检查EEPROM的数据是否有效,采用CRC16校验技术。 

                  一次校验默认最后两个字节为校验码, 

                  需要注意,packsize包括数据长度和校验码字节 

*输入参数:pdata:数组指针;packsize:数据长度 

*返回参数:数据是否有效,有效:VALID,无效:INVALID 

*编写作者:my_avr 

*编写时间:2007年8月21日 

*相关说明: 

********************************************************************/ 

unsigned char EepromCheck(unsigned char *pdata,unsigned char packsize)



        unsigned char i,j; 

        unsigned int  crc,ref_crc; 

         

        crc     = 0; 

        ref_crc = 0; 

         

        for (i = 0; i < (packsize - 2); i ++) 

        { 

                crc = crc ^ ((uint16_t) EepromReadByte(pdata) << 8); 

                 

                for (j = 0; j < 8; j++) 

                { 

                        if (crc & 0x8000) 

                        { 

                                crc = (crc << 1) ^ 0x1021; 

                        } 

                        else 

                        { 

                                crc = crc << 1; 

                        } 

                } 

                 

                pdata ++; 

        } 

         

        ref_crc  = (uint16_t) EepromReadByte(pdata); 

        ref_crc  = ref_crc<<8; 

        pdata ++; 

        ref_crc |= (uint16_t) EepromReadByte(pdata); 

         

        if (crc == ref_crc) 

        { 

                return VALID; 

        } 

        else 

        { 

                return INVALID; 

        } 

}

/******************************************************************* 

*函数名称:unsigned char CheckWriteCRC(unsigned char *pdata,unsigned char packsize) 

*函数功能:为EEPROM数据写CRC校验码 

*输入参数:pdata:数组指针;packsize:数据长度 

*返回参数:操作成功否?,成功:VALID,失败:INVALID 

*编写作者:my_avr 

*编写时间:2007年8月21日 

*相关说明: 

********************************************************************/ 

unsigned char CheckWriteCRC(unsigned char *pdata,unsigned char packsize) 



        unsigned char i,j; 

        unsigned int  crc; 

         

        crc     = 0; 

         

        for (i = 0; i < (packsize - 2); i ++) 

        { 

                crc = crc ^ ((uint16_t) EepromReadByte(pdata) << 8); 

                 

                for (j = 0; j < 8; j++) 

                { 

                        if (crc & 0x8000) 

                        { 

                                crc = (crc << 1) ^ 0x1021; 

                        } 

                        else 

                        { 

                                crc = crc << 1; 

                        } 

                } 

                 

                pdata ++; 

        } 

         

        EepromWriteByte(pdata,(uint8_t) (crc>>8)); 

        pdata ++; 

        EepromWriteByte(pdata,(uint8_t) crc); 

        pdata ++; 

         

        if (EepromCheck((pdata - packsize),packsize)) 

        { 

                return VALID; 

        } 

        else 

        { 

                return INVALID; 

        } 

}

/******************************************************************** 

*函数名称:unsigned char CheckAllPage(void) 

*函数功能:检查EEPROM数据是否有效,检查三个备份 

*输入参数:无 

*返回参数:操作成功否?,成功:VALID,失败:INVALID 

*编写作者:my_avr 

*编写时间:2007年8月21日 

*相关说明: 

********************************************************************/ 

uint8_t CheckAllPage(void) 



        if ((EepromCheck((unsigned char *)EepromPage1Add,EepromPageSize) == VALID) 

          &&(EepromCheck((unsigned char *)EepromPage2Add,EepromPageSize) == VALID) 

          &&(EepromCheck((unsigned char *)EepromPage3Add,EepromPageSize) == VALID)) 

        { 

                return VALID; 

        } 

         

        return INVALID; 

}

/******************************************************************* 

*函数名称:unsigned char DataRecover(void) 

*函数功能:检查EEPROM数据是否被破坏,如果被破坏了,作数据恢复 

*输入参数:无 

*返回参数:操作成功否?,成功:VALID,失败:INVALID 

*编写作者:my_avr 

*编写时间:2007年8月21日 

*相关说明: 

********************************************************************/ 

uint8_t DataRecover(void) 



        unsigned char i; 

        unsigned char temp; 

        unsigned char page; 

        unsigned int  invalidpage[3]; 

        unsigned int  validpage; 

         

        invalidpage[0] = 0; 

        invalidpage[1] = 0; 

        invalidpage[2] = 0; 

        validpage      = 0; 

        temp           = 0; 

        page           = 0; 

         

        if (EepromCheck((uint8_t *)EepromPage1Add,EepromPageSize) == VALID) 

        { 

                validpage = EepromPage1Add; 

        } 

        else 

        { 

                invalidpage[page] = EepromPage1Add; 

                page ++; 

        } 

         

        if (EepromCheck((uint8_t *)EepromPage2Add,EepromPageSize) == VALID) 

        { 

                validpage = EepromPage2Add; 

        } 

        else 

        { 

                invalidpage[page] = EepromPage2Add; 

                page ++; 

        } 

         

        if (EepromCheck((uint8_t *)EepromPage3Add,EepromPageSize) == VALID) 

        { 

                validpage = EepromPage3Add; 

        } 

        else 

        { 

                invalidpage[page] = EepromPage3Add; 

                page ++; 

        } 

         

        if (page == 3)                                                        //三个备份都被破坏了 

        { 

                return INVALID;                                                //数据完全无效了 

        } 

         

        while ((page--) > 0)                                                //数据恢复 

        { 

                for (i = 0; i < EepromPageSize; i ++) 

                { 

                        temp = EepromReadByte((uint8_t *) (validpage + i)); 

                        EepromWriteByte((uint8_t *) (invalidpage[page] + i),temp); 

                } 

        } 

         

        if (CheckAllPage() == VALID) 

        { 

                return VALID; 

        } 

         

        return INVALID; 

}

使用方法(三个备份): 

1、定义一个数组:EEPROMData[EepromPageSize-2] ,数组定义为EepromPageSize-2是为了给每个备份留2个字节的校验 

2、要保存数据时,先把数据放到数组中,然后调用EepromWriteBlock()函数,把这个数组的数据写进EEPROM,三个备份要写三次。 

3、写完了之后,调用CheckWriteCRC()函数,该函数会计算出当前备份的CRC16检验数据并写到EEPROM备份的尾部,有多少个备份就要调用多少次。 

4、至此,数据的备份工作已经完成。 

5、校验数据(一般在复位后运行),执行CheckAllPage()函数,若通过了,则EEPROM数据没有问题,否则要运行DataRecover()函数,对损坏的备份进行修复

[ 本帖最后由 飞龙 于 2008-12-2 09:20 编辑 ]

 

 

个人觉得意义不大,我之前也做过相关实验,因为一旦EEPROM出问题,根本就不是因为被错误改写,因为错误的改写必须有正确的时序,也就是说EEPROM出问题的时候一般都是硬件,主要是电压波动导致的,出问题的时候数据被毁好大一片,你这种办法可以有所改善,但是不可能是根本的,其实把BOD打开EEPROM很少会出问题

我曾经做过类似的试验: 

RAM数据备份,每个数据存三个不连续的地方,当RAM的数据被强干扰改变时(一般是不容易改变的),按三中取二判断,结果要么是三个数据都不同,要么三个数据都一样但不是正确的数据,后来再多备份几个也一样。 

EEPROM数据被改写,有以下几种原因: 

1,BOD没打开时,由于电源电压不稳或比较低时,程序乱跑,运行了写EEPROM的程序,这时即使是备份10份,校验也是正确的,因为是你自己写的; 

2,MCU受到干扰,导致程序跑飞,运行了写EEPROM的程序,结果同1; 

3,MCU受到干扰或电源不稳,直接导致EEPROM数据被改写?(没碰到过,也无法验证); 

…… 

所以我认为如果程序完全没有问题,楼主这样做一点意义都没有,最多只是因为心理作用会自我感觉程序比较健壮;如果程序有BUG,按楼主这样做有可能会减少出错的机会甚至避免出错,但这是治标不治本。

 

 

应用AVR芯片内部EEPROM写入(或写入后读出)出错问题,下面有AVR的芯片手册有相关介绍(来自mega8中文翻译文档): 

    防止EEPROM 数据丢失 

    若电源电压过低,CPU 和EEPROM 有可能工作不正常,造成EEPROM 数据的毁坏( 丢失)。这种情况在使用独立的EEPROM 器件时也会遇到。因而需要使用相同的保护方案。 

    由于电压过低造成EEPROM 数据损坏有两种可能:一是电压低于EEPROM 写操作所需要的最低电压;二是CPU 本身已经无法正常工作。 

    EEPROM 数据损坏的问题可以通过以下方法解决: 

当电压过低时保持AVR RESET 信号为低。这可以通过使能芯片的掉电检测电路BOD 来实现。如果BOD 电平无法满足要求则可以使用外部复位电路。若写操作过程当中发生了复位,只要电压足够高,写操作仍将正常结束。 

    以上官方文档介绍主要提示的是电压过低发生的异常而造成读写错误,并未涉及到其它问题。 

    个人总结,在实际应用过程中有以下问题造成数据读写错误: 

    1.程序受到干扰(或程序存在BUG)造成写入EEPROM的数据本身就是错误的; 

    2.EEPROM写入次数过多(这个问题在频繁写入时会遇到),造成无法写入的; 

    3.再提电压问题:由于电压过低,造成写入的数据实际未写入或写入错误; 

    EEPROM写入错误问题是不可避免的,因此就应有相关的归避措施和恢复措施: 

    从硬件方面来说:加入BOD措施是必要的,同时芯片的电源滤波也有较高要求,芯片的复位电路、晶振(及芯片晶振设置位,指单片机的工作频率,这对EEPROM读写有影响)也应仔细处理,以提高抗干扰;当然,一个设计优良的线路板对抗干扰有很大帮助; 

    从软件方面来说:可以有以下方式进行控制: 

    1.在写入EEPROM前,需对写入的EEPROM数据进行验证措施,若不正常则不写入; 

    2.EEPROM写入后再读出(即较验),写前数据比较,应一致,否则可能为EEPROM无法再写入,这时可能要更换存储区地址; 

    3.楼主的解决方案有比较好的效果,但是我14楼提出的问题:太占EERPOM存储空间了,可以精简一下会更好;因为AVR内部的EEROM区有限,若存在大量存储数据情况下,则有可能选用高阶的芯片而造成成本上升; 

    4.数据读出时有验证,并存在恢复措施,以使数据错误降到最低。

 

 

不错的讨论!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: