一.问题引入
在实际设备中,经常有一些数据需要掉电存储,存储的方式一般会用内部flash或者外部扩展的存储芯片。不管哪种方式,在存储的时候,都有擦除与写入的过程,而这一过程是比较耗时间的(程序烧录过程类似)。以下为常见存储芯片AT24c16写入时间间隔 。
常规的写入程序如下,使用了delay();函数
//在AT24C16指定地址写入一个数据
void AT24C16_WriteOneByte(u16 writeaddr,u8 data)
{
I2C_Start();
I2C_Send_Byte(0xA0+((writeaddr/256)<<1));
I2C_Wait_Ack();
I2C_Send_Byte(writeaddr%256);
I2C_Wait_Ack();
I2C_Send_Byte(data);
I2C_Wait_Ack();
I2C_Stop();
Delay_ms(5);
}
当系统中有大量的需要掉电保存的的数据,且硬件又不支持断电时自检,维持一定时间供电以保存数据时,使用delay();函数会大大降低系统的实时性。因此本文最终的目的是消除此延时的5ms。
二.延时实现方法
从上面的写函数可以看出,需要保存一个变量时,得输入存放地址以及数据,当数据为16位时还需要分别保存数据的高8位和低8位。以保存变量count为例
u16 count;
int main()
{
count = AT24C16_ReadOneByte(1)<<8 | AT24C16_ReadOneByte(2); //变量开机初始化
while(1)
{
1.获取count值(由外部传感器)
2.保存count至AT24c16
if(last_count != count) //判断当前值是否改变
{
AT24C16_WriteOneByte(1 , count>>8);
AT24C16_WriteOneByte(2 , count & 0xff);
}
3.使用到count这个变量,比如做一些判断,做出一些处理
4.保存当前的count, last_count = count;
}
}
可以看出write函数在主循环中,这样导致主循环周期不确定,且当要存入的变量比较多时,主循环实时性大大降低。且write函数在各处交叉调用,每引入一个新变量,都要做以上重复的事:开机赋值,写入保存。
三.定时器实现方法
1. 实现思路
绿色块为存储需要保存变量的地址的数组,其中数组的最末端为定义的常量地址,用于判断数组中当前需要存储变量的个数。当获取到需要存储的个数后,便会进行判同处理,只有当变量改变后才会进行写操作,当某个变量改变时会在定时器产生的第一个5ms使能写入数据高位至第一页,在第二个使能信号到达时写入低位至第二页。
2.代码实现:
step1 . 定义一个变量地址存储数组,用户将需要保存的变量地址存入即可。
u16 a,b,c,d,e; //示例
u16 tail=0xfefe; //用于获取数组当前存储长度
u8 data_len; //用于存放当前存储变量个数
/*************需要存储的变量定义****************/
u16* data_address[256]
={
/*------------------用户区----------------------------*/
&a, // 1
&b, // 2
&c, // 3
&d, // 4
/*----------------------------------------------*/
&tail,
};
step2 .写入函数实现
/***********************写入改变的变量****************************/
void DATA_write(void)
{
u8 j;
static u8 i=1;
static u8 cnt;
static u32 run_out_oftime;
static u32 last_time;
static u8 write_en;
/*-------------------------写使能-----------------------------------*/
if(!write_en)
{
if(*data_address[i-1]!=(u16)(AT24C16_Read(i)<<8 | AT24C16_Read(i+256)))
write_en=1;
else
{
i++;
if(i>=data_len)
i=1;
} //值改变则允许写
}
/*---------------------写入E2PROM-----------------------------------*/
else
{
/*---------产生5ms运行间隔-----*/
if(!last_time)
last_time=sys_time(); //系统1ms定时时间
run_out_oftime= sys_time()-last_time;
if(run_out_oftime> 5)
{
last_time=0;
run_out_oftime=0;
cnt++; //间隔5ms加一次
}
/*-------------写入高位----------*/
if(cnt==1)
{
AT24C16_WriteByte(*data_address[i-1]>>8,i); //数据高位第一页
}
/*-------------写入低位----------*/
else if(cnt>=2)
{
AT24C16_WriteByte((u8)(*data_address[i-1]&0xff),i+256); //数据低位第二页
write_en=0;
cnt=0;
i++;
if(i>=data_len)
i=1;
}
}
}
step3 .开机初始化函数
void DATA_init(void)
{
u8 i,j;
/*---------------获取当前需要存储数的个数--------------------------*/
if(!data_len)
{
for(j=0;j<255;j++)
{
if(*data_address[j]==0xfefe)
{
data_len=j+1;
break;
}
}
}
for(i=0;i< data_len;i++)
{
*data_address[i]=(u16)(AT24C16_Read(i)<<8 | AT24C16_Read(i+256));
}
}
step4. 主函数
/*主函数*/
int main ()
{
data_init(); //开机初始化
while(1)
{
data_write();
other_deal();
}
}
四。优缺点以及后续改进方向
优点:可以看到,去掉了写函数的延时函数。当用户需要增加新的存储变量时,只需要在step1中添加,开机初始化函数以及写 函数都不需要做调整,这样方便对掉电保存模块进行封装。
缺点:当变量值改变,便会进行一次保存,会影响存储器件的寿命,为加入写保护等。主循环的周期要小于最小写入周期5ms。
改进方向:
1.掉电存储的数据一般对整个系统至关重要,因此在写入时因该检测是否写入成功
2.当写入失败(某处地址存储损坏),系统自检,自动更换写入地址
3.在干扰比较严重的环境下,写入有可能受到干扰,导致存进去的数值与实际不符,在开机初始化时可能导致不可预 料的错误,因此写入时可以在多页存储相同变量,开机初始化时全部读出做比较,取概率较大的值。
4.引入时间片轮转或者操作系统,将模块设成一个任务,写函数可更加简化