各位单片机程序猿们,在单片机程序设计的时候,经常碰到一些数据的掉电存储问题。往往这些数据量又不是很大,但是操作起来特别麻烦。每次变更数据都得调用存储函数进行读写操作。今天总结一下近几天的思路,对普通单片机的的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);
}
哈哈,这样子,你的应用程序是不是很简洁呀。喜欢那就尝试下吧!