专注电子技术学习与研究
当前位置:单片机教程网 >> MCU设计实例 >> 浏览文章

单片机中的掉电存储管理

作者:佚名   来源:本站原创   点击数:  更新时间:2013年11月06日   【字体:

各位单片机程序猿们,在单片机程序设计的时候,经常碰到一些数据的掉电存储问题。往往这些数据量又不是很大,但是操作起来特别麻烦。每次变更数据都得调用存储函数进行读写操作。今天总结一下近几天的思路,对普通单片机的的NV变量的管理给出一个较为方便的操作方法。

    一般,我们的数据都由8,16,32位组成,因此,在此例中,我给出16长度数据的接口函数,旨在表明这种方法的思路。具体读者可以根据自己的使用环境,自己改进。

    首先,说明下笔者的编程习惯,笔者在编写单片机C程序的过程中,往往喜欢把程序中涉及的东西封装成类似于面向对象思想中的类。把数据结构假想成类的属性,把对相应数据结构操作的函数,假想成类的方法。这种方法在实际编程过程中,往往给自己带来很大的便利。不仅思路清晰,而且便于模块化管理自己的程序。

    现在,我们创建两个文件,分别为NV.h和NV.C,h文件作为NV管理的模块。NV.h中我们来定义NV变量的数据结构(即笔者所认为的类的属性)和声明对NV操作的函数。
#ifndef    _NV_
#define _NV_

//NV操作的状态定义
#define NV_Succeed      1

#defineNV_Failed       0
//定义16位长度的NV变量数据结构

structNV_Struct16
{
    u16 Val;        //NV16变量的值
    u16 NVAddr;     //NV16变量在存储器中的首地址
};
//声明外部调用函数
extern void NV16_Get(struct NV_Struct16 *temp);
u8 NV16_Set(struct NV_Struct16 *temp,u16 val);
#endif

    在NV_Struct16中,我们封装了一个叫做NV16的变量,其成员中有变量的值和在存储器中的首地址。在这里,只是给它定义了一个数据的结构,并没有定义实体变量,在C++或C#等面向对象程序设计方法中,这叫类的定义,并没有创建类的实体。这样做的目的,就是做到尽量把我们这个NV操作模块从我们编写的其他应用程序中抽象出来。

    那么,接下来,我们就来编写NV16变量的操作函数。在NV.C中,我们添加两个函数,一个是获取NV变量的值,另一个是修改NV变量的值。

#include"NV.h"
#include"24CXX.h"
#defineCheckTimes          4       //写入时校验次数
#defineNV8_Enable          1       //NV8使能开关        
#defineNV16_Enable         1       //NV16使能开关
#defineNV32_Enable         1       //NV32使能开关

//以下是NV_Struct16的操作函数

#ifNV16_Enable
voidNV16_Get(struct NV_Struct16 *temp)
{
    temp->Val=AT24CXX_ReadLenByte(temp->NVAddr,2);
}
u8NV16_Set(struct NV_Struct16 *temp,u16 val)
{
    u8 cnt=0;
        if(temp->Val==val)returnNV_Succeed;
    temp->Val=val;
    AT24CXX_WriteLenByte(temp->NVAddr,temp->Val,2);
    while(cnt<CheckTimes||AT24CXX_ReadLenByte(temp->NVAddr,2)!=temp->Val)
        cnt++;
    if(cnt<CheckTimes)return NV_Failed;
    else return NV_Succeed;
}

#endif
    在这个文件中,我们调用了一个24CXX.h文件中提供的24CXX的读写函数,这部分在我们移植的过程中是需要考虑的。下面贴出这个函数的原型,如果这个都不知道怎么写出来,那我也无语了。

//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr  :开始读出的地址
//返回值     :数据
//Len       :要读出数据的长度2,4
u32AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)

    u8 t;
    u32 temp=0;
    for(t=0;t<Len;t++)
    {
       temp<<=8;
        temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);                      
    }
    return temp;                                                  
}

//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr :开始写入的地址 
//DataToWrite:数据数组首地址
//Len       :要写入数据的长度2,4
voidAT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)


    u8 t;
    for(t=0;t<Len;t++)
    {
        AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
    }                                                 
}


    用以上方法来管理我们的NV变量,在程序中会大大地减少我们的工作量。下面来举一个简单的例子。记录设备开机次数,并从串口打印出来。
#include “相关头文件”
#include "NV.H"

#define    Flag   0x33                               //存储器初始化标志
#define    FlagAddr    0x00
#define    PowerOnTimesAddr   0x10        //将开机次数数据存储在0x10位置
//创建NV16非易失变量实体
struct NV_Struct16     PowerOnTimes;
struct NV_Struct16     MemFlag;

void NV_Init()
{
   //NV地址装入
    MemFlag.NVAddr=FlagAddr;
    PowerOnTimes.NVAddr=PowerOnTimesAddr;
    //检查存储器是否初始化
    if(NV16_Get(&MemFlag)!=Flag)
    {
        NV16_Set(&MemFlag,Flag);
        NV16_Set(&PowerOnTimes,0);
    }
}

void main()
{
    u16 times;
    NV_Init();
    times=NV16_Get(&PowerOnTimes);
    NV16_Set(&PowerOnTimes,times++);
    printf("%d\n",times);
    while(1);
}
        哈哈,这样子,你的应用程序是不是很简洁呀。喜欢那就尝试下吧!

关闭窗口

相关文章