【环境专家之智能手表】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
对于NFC的IIC地址以及寄存器分配如下图2所示:
图2
这次设计主要使用EEPROM,其中EEPROM的0~5存储地址已经被手表绑定ID占用了,6~15存储地址被名字所占用,剩余的空间给其他数据使用。关于NFC的功能,这里暂时没有去研究,后面要是有时间就会去研究一下。
3.设计
首先需要分析有多少数据,同时需要存储多少天的数据,下面是我定义存储数据的结构体:
- #pragma pack(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字节对齐,存储数据的时候一定要注意字节对齐这个问题,相信很多人都在这里栽过跟头。
这次设计半小时存储一次数据,那么一天就是48条数据,同时存储到EEPROM中还需要有一个标志,表示这个48条数据属于哪天的,这里就使用时间戳进行标记,一天数据的结构体如下所示:
- #pragma pack(1)
- typedef struct one_day_data
- {
- uint32_t data_time;
- single_data_t data[48];
- }one_day_data_t;
- #pragma pack()
同样需要强制1字节对齐,希望大家存储到Flash、EEPROM和蓝牙传输的时候,都强制1字节对齐,避免出现无效字节位。
设计存储14天数据,那么需要使用到的字节数为【(48*8 + 4) * 14】等于5432字节,小于8192字节,所以还是够存储的!
这里还需要记录当前存储到第几天,比如我开辟了14天的存储空间,但是我存到第15天了,那么我就需要把第一天的数据删除,把第15天的数据覆盖上去,这样轮询存储,所以定义了一个变量记录当前最后一天需要存储到14天数据中的那一块区域,当然这个变量也需要存储到EEPROM中。最终存储区域的分配如下表所示:
图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);
- today_zero = time_to_today_zero(time_utc);
-
- if(watch_data_rec_day(today_zero, &today_data) != 0)
- {
- 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);
- }
-
- 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);
- }
-
-
- 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);
- }
也实现了存储一整天的数据,供特殊情况下使用。
然后就是从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);
- if(data_day == day_cnt)
- break;
- }
-
- 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);
- 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;
- I2CEeprom_Write(WATCH_DATA_DAY_CNT, (uint8_t*)&watch_data_cnt, sizeof(uint8_t), &eeprom);
- I2CEeprom_Write(WATCH_DATA_DAY_CNT + watch_data_cnt * WATCH_DATA_DAY_LENGTH, (uint8_t*)&today_data, sizeof(one_day_data_t), &eeprom);
- }
测试一下结果,使用1S存储一次数据,开机之后,设置一下时间,等待一分钟,重启,通过调试看到启动时从Flash中读取到的数据如下图4所示:
图4
4.总结
存储数据比较繁琐的就是这个数据结构,如何进行存储,如何读取,只要这些问题解决了,其实存储数据就比较简单,下一篇就是与APP通信数据,让数据可视化!
|