w494143467 发表于 2021-6-27 14:06

【环境专家之智能手表】Part13:各项数据存储至EEPROM

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

<p><strong><span style="font-size:20px;">1.介绍</span></strong></p>

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

<p><span style="font-size:20px;"><strong>2.N24RF64分析</strong></span></p>

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

<p class="imagemiddle" style="text-align: center;"></p>

<p style="text-align: center;"><span style="font-size:16px;">图1</span></p>

<p><span style="font-size:16px;">对于NFC的IIC地址以及寄存器分配如下图2所示:</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p style="text-align: center;"><span style="font-size:16px;">图2</span></p>

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

<p><strong><span style="font-size:20px;">3.设计</span></strong></p>

<p><span style="font-size:16px;">首先需要分析有多少数据,同时需要存储多少天的数据,下面是我定义存储数据的结构体:</span></p>

<pre>
<code>#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字节对齐</code></pre>

<p><span style="font-size:16px;">一共八个字节,为了避免字节对齐导致出错,添加上了强制1字节对齐,存储数据的时候一定要注意字节对齐这个问题,相信很多人都在这里栽过跟头。&nbsp;</span></p>

<p><span style="font-size:16px;">这次设计半小时存储一次数据,那么一天就是48条数据,同时存储到EEPROM中还需要有一个标志,表示这个48条数据属于哪天的,这里就使用时间戳进行标记,一天数据的结构体如下所示:</span></p>

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

<p><span style="font-size:16px;">同样需要强制1字节对齐,希望大家存储到Flash、EEPROM和蓝牙传输的时候,都强制1字节对齐,避免出现无效字节位。</span></p>

<p><span style="font-size:16px;">设计存储14天数据,那么需要使用到的字节数为【(48*8 + 4) * 14】等于5432字节,小于8192字节,所以还是够存储的!</span></p>

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

<p class="imagemiddle" style="text-align: center;"></p>

<p style="text-align: center;"><span style="font-size:16px;">图3</span></p>

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

<pre>
<code class="language-cpp">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(&amp;today_data, 0, sizeof(one_day_data_t));
        I2CEeprom_Read(WATCH_DATA_DAY_CNT, (uint8_t*)&amp;watch_data_cnt, sizeof(uint8_t), &amp;eeprom);        //读取Flash
        today_zero = time_to_today_zero(time_utc);

        if(watch_data_rec_day(today_zero, &amp;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*)&amp;today_data.data_time, sizeof(uint32_t), &amp;eeprom);        //写入Flash
        }

        watch_data_init_flag = 1;
}</code></pre>

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

<p><span style="font-size:16px;">获取当前时间零点的时间戳函数如下,只需要使用时间函数转换一下即可当然还有别的更方便的办法,求余86400,然后用当前时间减去求余的结果也为当前时间的0点,不过下面这个函数更容易理解。</span></p>

<pre>
<code class="language-cpp">uint32_t time_to_today_zero(uint32_t time_utc)
{
        uint32_t time_zero = 0;
        struct my_tm t;
        localtime(time_utc, &amp;t);
        t.tm_hour = 0;
        t.tm_min = 0;
        t.tm_sec = 0;
        time_zero = mktime(t);        //得到零点时间
        return time_zero;
}</code></pre>

<p><span style="font-size:16px;">然后就是存储数据,虽然EEPROM可以擦除200万次,但是也需要节省使用,所以当有一条新数据的时候,只将这一条数据进行存储,而不存储一整天的数据,具体实现如下:</span></p>

<pre>
<code class="language-cpp">//存储一天的数据
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*)&amp;today_data, sizeof(one_day_data_t), &amp;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(&amp;today_data.data, &amp;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*)&amp;today_data.data, sizeof(single_data_t), &amp;eeprom);        //写入Flash
}</code></pre>

<p><span style="font-size:16px;">也实现了存储一整天的数据,供特殊情况下使用。</span></p>

<p><span style="font-size:16px;">然后就是从EEPROM中获取数据,这里通过的是每一块数据中都有一个零点的时间戳,用来判断该区域是否是我想要的那一天数据,如果没有该数据,则需要创建一个新的数据,具体实现如下:</span></p>

<pre>
<code class="language-cpp">//读取指定天数的数据
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 &lt; WATCH_DATA_DAY_MAX;i++)
        {
                I2CEeprom_Read(WATCH_DATA_START_ADDR + i * WATCH_DATA_DAY_LENGTH, (uint8_t*)&amp;data_day, sizeof(uint32_t), &amp;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), &amp;eeprom);        //读取Flash
                return 0;        //读取成功
        }
        else
        {
                return 1;        //没有该天数据
        }
}</code></pre>

<p><span style="font-size:16px;">最后就是实现切换新的一天的接口了,先切换存储区域为下一块地址,然后存储该天的零点时间戳,为了防止是覆盖的情况,所以需要将这一块区域的其他数据都清零,最后实现接口如下:</span></p>

<pre>
<code class="language-cpp">//新的一天
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 &gt;= WATCH_DATA_DAY_MAX)        //循环存储
                watch_data_cnt = 0;
        memset(&amp;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*)&amp;watch_data_cnt, sizeof(uint8_t), &amp;eeprom);        //写入Flash
        I2CEeprom_Write(WATCH_DATA_DAY_CNT + watch_data_cnt * WATCH_DATA_DAY_LENGTH, (uint8_t*)&amp;today_data, sizeof(one_day_data_t), &amp;eeprom);        //写入Flash
}</code></pre>

<p><span style="font-size:16px;">测试一下结果,使用1S存储一次数据,开机之后,设置一下时间,等待一分钟,重启,通过调试看到启动时从Flash中读取到的数据如下图4所示:</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p style="text-align: center;"><span style="font-size:16px;">图4</span></p>

<p><strong><span style="font-size:20px;">4.总结</span></strong></p>

<p><span style="font-size:16px;">存储数据比较繁琐的就是这个数据结构,如何进行存储,如何读取,只要这些问题解决了,其实存储数据就比较简单,下一篇就是与APP通信数据,让数据可视化!</span></p>
页: [1]
查看完整版本: 【环境专家之智能手表】Part13:各项数据存储至EEPROM