377|0

1986

帖子

3

资源

版主

【环境专家之智能手表】Part13:各项数据存储至EEPROM [复制链接]

本帖最后由 w494143467 于 2021-6-27 15:56 编辑

1.介绍

既然是手表,那一定要有存储数据的功能,板载了一个【N24RF64】芯片,可以当做EEPRPM使用同时还可以当作NFC标签使用,NFC标签支持的协议有【ISO15693和ISO18000】,之前只接触过ISO15693,有时间了解一下ISO18000,有外部存储芯片,就不用芯片内部的Flash存储数据了,岂不美哉!

2.N24RF64分析

首先参看【N24RF64】数据手册,发现这个芯片的EEPROM擦除的次数可达200万次,平均一天擦除50次,可以使用一百年的时间。这个EEPROM的存储大小为64Kb,这里的【b】是小位,转换为大B则是8KB,也就是可以存储8192个字节的数据。这个芯片除了EEPROM还有NFC标签的功能,既然有NFC标签的功能,那芯片中还存在存储NFC标签信息的区域,内存分配如下图1所示:

1.png

图1

对于NFC的IIC地址以及寄存器分配如下图2所示:

2.png

图2

这次设计主要使用EEPROM,其中EEPROM的0~5存储地址已经被手表绑定ID占用了,6~15存储地址被名字所占用,剩余的空间给其他数据使用。关于NFC的功能,这里暂时没有去研究,后面要是有时间就会去研究一下。

3.设计

首先需要分析有多少数据,同时需要存储多少天的数据,下面是我定义存储数据的结构体:

#pragma pack(1) //让编译器对这个结构作1字节对齐
typedef struct single_data
{
	uint8_t act;	//活动
	uint8_t temp;	//温度
	uint8_t hum;	//湿度
	uint8_t lum;	//光照
	uint32_t pre;	//气压
}single_data_t;
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐

一共八个字节,为了避免字节对齐导致出错,添加上了强制1字节对齐,存储数据的时候一定要注意字节对齐这个问题,相信很多人都在这里栽过跟头。 

这次设计半小时存储一次数据,那么一天就是48条数据,同时存储到EEPROM中还需要有一个标志,表示这个48条数据属于哪天的,这里就使用时间戳进行标记,一天数据的结构体如下所示:

#pragma pack(1) //让编译器对这个结构作1字节对齐
typedef struct one_day_data
{
	uint32_t data_time;		//数据时间,某一天
	single_data_t data[48];	//一天的数据,半小时存储一次
}one_day_data_t;
#pragma pack() //取消1字节对齐,恢复为默认4字节对齐

同样需要强制1字节对齐,希望大家存储到Flash、EEPROM和蓝牙传输的时候,都强制1字节对齐,避免出现无效字节位。

设计存储14天数据,那么需要使用到的字节数为【(48*8 + 4) * 14】等于5432字节,小于8192字节,所以还是够存储的!

这里还需要记录当前存储到第几天,比如我开辟了14天的存储空间,但是我存到第15天了,那么我就需要把第一天的数据删除,把第15天的数据覆盖上去,这样轮询存储,所以定义了一个变量记录当前最后一天需要存储到14天数据中的那一块区域,当然这个变量也需要存储到EEPROM中。最终存储区域的分配如下表所示:

3.png

图3

数据方面好了,接下来就是接口了,之前说过,没有初始化时间则不进行数据存储,所以定义一个初始化函数,在设置时间的时候进行初始化,数据存储也是根据【watch_data_init_flag】标志位来判断是否需要存储,具体代码如下:

one_day_data_t today_data;	//今天的数据
uint8_t	watch_data_cnt = 0;	//数据存储到第几个缓存
uint8_t watch_data_init_flag = 0;	//初始化标志位

//该函数应该在设置时间的时候调用
void watch_data_init(uint32_t time_utc)
{
	uint32_t today_zero = 0;
	if(watch_data_init_flag)
		return;
	memset(&today_data, 0, sizeof(one_day_data_t));
	I2CEeprom_Read(WATCH_DATA_DAY_CNT, (uint8_t*)&watch_data_cnt, sizeof(uint8_t), &eeprom);	//读取Flash
	today_zero = time_to_today_zero(time_utc);

	if(watch_data_rec_day(today_zero, &today_data) != 0)	//获取Flash中今天的数据,如果没有,则赋值给今日数据的结构体时间
	{
		today_data.data_time = today_zero;
		I2CEeprom_Write(WATCH_DATA_START_ADDR + watch_data_cnt * WATCH_DATA_DAY_LENGTH, (uint8_t*)&today_data.data_time, sizeof(uint32_t), &eeprom);	//写入Flash
	}

	watch_data_init_flag = 1;
}

首先通过【watch_data_init_flag】判断是否已经初始化过,然后将存储今日数据的结构体清零,读取当前存储到第几块区域,然后通过【time_to_today_zero】获取当前时间处于零点时的时间戳,再去通过【watch_data_rec_day】函数去Flash中查找这一天的数据,如果找到了则数据就存储到【today_data】结构体中,如果没有,则创建一个时间存储到数据块中。这里逻辑比较多,所以大家需要花点时间去理解一下。

获取当前时间零点的时间戳函数如下,只需要使用时间函数转换一下即可当然还有别的更方便的办法,求余86400,然后用当前时间减去求余的结果也为当前时间的0点,不过下面这个函数更容易理解。

uint32_t time_to_today_zero(uint32_t time_utc)
{
	uint32_t time_zero = 0;
	struct my_tm t;
	localtime(time_utc, &t);
	t.tm_hour = 0;
	t.tm_min = 0;
	t.tm_sec = 0;
	time_zero = mktime(t);	//得到零点时间
	return time_zero;
}

然后就是存储数据,虽然EEPROM可以擦除200万次,但是也需要节省使用,所以当有一条新数据的时候,只将这一条数据进行存储,而不存储一整天的数据,具体实现如下:

//存储一天的数据
void watch_data_today_save(void)
{
	if(watch_data_init_flag == 0)	//初始化成功才能存储
		return;
	I2CEeprom_Write(WATCH_DATA_START_ADDR + watch_data_cnt * WATCH_DATA_DAY_LENGTH, (uint8_t*)&today_data, sizeof(one_day_data_t), &eeprom);	//写入Flash
}

//存储一条数据
void watch_data_today_cnt_save(uint8_t today_cnt, single_data_t single_data)
{
	if(watch_data_init_flag == 0)	//初始化成功才能存储
		return;
	printf("save:%d\r\n", today_cnt);
	memcpy(&today_data.data[today_cnt], &single_data, sizeof(single_data_t));	//拷贝信息
	I2CEeprom_Write(WATCH_DATA_START_ADDR + watch_data_cnt * WATCH_DATA_DAY_LENGTH + 4 + today_cnt * 4, (uint8_t*)&today_data.data[today_cnt], sizeof(single_data_t), &eeprom);	//写入Flash
}

也实现了存储一整天的数据,供特殊情况下使用。

然后就是从EEPROM中获取数据,这里通过的是每一块数据中都有一个零点的时间戳,用来判断该区域是否是我想要的那一天数据,如果没有该数据,则需要创建一个新的数据,具体实现如下:

//读取指定天数的数据
uint8_t watch_data_rec_day(uint32_t day_cnt, one_day_data_t *day_data)
{
	uint8_t i = 0;
	uint32_t data_day = 0;
	for(i = 0;i < WATCH_DATA_DAY_MAX;i++)
	{
		I2CEeprom_Read(WATCH_DATA_START_ADDR + i * WATCH_DATA_DAY_LENGTH, (uint8_t*)&data_day, sizeof(uint32_t), &eeprom);	//读取Flash
		if(data_day == day_cnt)	//判断天数是否正确
			break;
	}
	//从Flash读取要查询的某天数据
	if(i != WATCH_DATA_DAY_MAX)
	{
		I2CEeprom_Read(WATCH_DATA_START_ADDR + i * WATCH_DATA_DAY_LENGTH, (uint8_t*)day_data, sizeof(one_day_data_t), &eeprom);	//读取Flash
		return 0;	//读取成功
	}
	else
	{
		return 1;	//没有该天数据
	}
}

最后就是实现切换新的一天的接口了,先切换存储区域为下一块地址,然后存储该天的零点时间戳,为了防止是覆盖的情况,所以需要将这一块区域的其他数据都清零,最后实现接口如下:

//新的一天
void watch_new_day(uint32_t today_utc)
{
	uint32_t today_zero;
	if(watch_data_init_flag == 0)	//初始化成功才能存储
		return;
	watch_data_cnt++;
	if(watch_data_cnt >= WATCH_DATA_DAY_MAX)	//循环存储
		watch_data_cnt = 0;
	memset(&today_data, 0, sizeof(one_day_data_t));
	today_zero = time_to_today_zero(today_utc);
	today_data.data_time = today_zero;	//保存当天0点时间
	I2CEeprom_Write(WATCH_DATA_DAY_CNT, (uint8_t*)&watch_data_cnt, sizeof(uint8_t), &eeprom);	//写入Flash
	I2CEeprom_Write(WATCH_DATA_DAY_CNT + watch_data_cnt * WATCH_DATA_DAY_LENGTH, (uint8_t*)&today_data, sizeof(one_day_data_t), &eeprom);	//写入Flash
}

测试一下结果,使用1S存储一次数据,开机之后,设置一下时间,等待一分钟,重启,通过调试看到启动时从Flash中读取到的数据如下图4所示:

4.png

图4

4.总结

存储数据比较繁琐的就是这个数据结构,如何进行存储,如何读取,只要这些问题解决了,其实存储数据就比较简单,下一篇就是与APP通信数据,让数据可视化!


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

最新文章 更多>>
    关闭
    站长推荐上一条 1/10 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2021 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表