目录
一、前言
在单片机程序开发中,数据的存储与读取无疑是实际开发中十分重要的一环,那么我们常见的存储方式要么是借助外部存储芯片,要么是存储在Flash。伦茨芯片提供了一种名为FS的文件系统,本质上也是存储在Flash中,但其能够更方便的帮助我们存储结构体等数据信息。
二、FS系统
为了方便应用存取内部 flash 中的数据,ST17H65/66 实现了一个轻量级文件系统,该文件 系统提供一组同步 API,支持 16bit 文件 ID,文件查找,读、写、删除、以及文件系统查 询,垃圾文件回收[--引自开发手册-文件系统]。
注意这个地方其实是有坑的,如果可以自己读写Flash的话,也是种不错的选择。
三、详细步骤
3.1 查阅SDK手册
其中,提到了FS文件系统存储的功能的相关示例代码。我们根据其提供的示例代码和操作流程,逐步完成。
3.2 修改宏定义
首先,打开宏定义,修改宏USE_FS为1。
3.3 初始化FS
在任务文件中,导入fs.h文件。
随后调用hal_fs_init()函数
hal_fs_init(0x11030000,3); // 初始化一块3*4K的空间
3.3.1 参数是什么
首先使用这个函数,我们必须知道参数是什么?给函数一个合适的参数,才能让程序能够正常运行。
通过查阅开发手册,我们可以得到如下信息:
第一个参数代表文件系统的起始地址,其起始地址为0x11005000,且起始地址必须与4096字节对齐,也就是说起始地址只能是类似0x11005000、0x11006000、0x11007000等。
第二个参数是扇区数量,综合手册其他资料来看,其一个扇区为4K,每个扇区为4096字节,与前面对应。
由于其取值范围为3~78,我们通过最小起始地址0x11005000可以推断出,其总的可以供我们使用的内存大小为78*4096 = 319488字节,也就是0x4E000,加上偏移的0x11000000可以得出,其总的内存地址范围为0x11005000 ~ 0x1104E000(仅猜测,准确值需要查阅芯片手册,但这些范围一定可用)
3.3.2 如何确定地址参数
如果大家使用过Flash想必一定知道,我们使用Flash存储数据一定会避开程序占用的Flash的地址,防止出现错误。
那么,我们现在不知道程序占用的地址,怎么才能避开呢?一种方案是选择足够靠后的地址,但这种方法不符合科学。第二种方案是通过查看程序所占用的Flash地址的范围,主动避开。
本文将使用第二种方法,教大家避开程序所占用的地址。
当大家使用伦茨的烧写程序烧写程序时,注意一条调试语句。
我们可以看到APP========0x11005000------0x1100c55c========,这说明编译后的程序所占的内存地址范围为0x11005000 ~ 0x1100C55C,为避开这个地址,我们同样也可以选择例程中的0x11030000。
3.4 写入数据
下面我们完成写入数据的程序,随后烧写运行后,再写入读数据的程序,打印查看效果。
如果刚刚你和我一起查阅了开发手册,那么你会发现,手册中提供的写入函数与认识SDK文档中的不一致。
那么这是为什么呢?
其实,这是因为文件系统的垃圾回收机制。这个功能在手册中也是有具体函数实现的。
而认识SDK文档中,使用的osal_snv_write函数,其内部包含了垃圾回收机制。同时,官方也建议我们使用包含垃圾回收机制的函数。这一点在开发手册中也有提到。
因此,我们使用osal_snv_write函数写入一串数据。
//下列代码写在任务初始化函数中
hal_fs_init(0x11030000,3); // 初始化一块3*4K的空间
uint8_t data[10]={'\0'};
sprintf((char *)data,"Hello\n");
osal_snv_write(1,sizeof(data),data);
LOG("Write OK");
烧写完毕后,看到调试信息即可。
但实际上,这样并不对!
我可以明确的告诉大家,其给出的代码很多都不能正常运行,官方在文档中给出的很多语句直接拷贝都是错误的,这是因为文档中往往只提到了关键语句,而非全部语句。
通过添加调试语句,输出初始化状态,我们可以看懂,初始化是不成功的。
因此,我通过对其例程的分析,按照如下代码可以实现功能。
fs_t fst;
hal_fs_ctx(&fst);
uint8_t status = hal_fs_init(0x1103C000,3);
if(status == SUCCESS)
LOG("Init OK");
else
LOG("Not OK=%d \n",status);
uint8_t data[10]={'\0'};
sprintf((char *)data,"Hello\n");
status = osal_snv_write(0x80,10,data);
if(status == SUCCESS)
LOG("Write OK");
else
LOG("Not OK=%d \n",status);
可以看到,现在初始化是成功的。
3.5 读取数据
通过上面的代码,我们可以看出写入的是ASCII格式的"Hello\n"字符串,下面我们更改函数,读取并打印出来。在定时任务中,读取并打印出来。
根据错误码,我们可以查到原因是打开失败。
那我们修改一下代码,每次读取之前给他初始化一下呢。
烧录,查看程序输出,发现其可以实现输出功能,但仅能输出一次。
那么这是什么原因呢?
其实经过几个小时的摸索,发现原来是因为没有垃圾回收导致的。每次读取完后,进行垃圾回收即可。代码如下:
fs_t fst;
hal_fs_ctx(&fst);
hal_fs_init(0x1103C000,3);
LOG("Reading...");
uint8_t data2[10];
LOG("Read=%d\n",osal_snv_read(0x80,10,data2));
LOG("%s",data2);
hal_fs_garbage_collect();
那么,我的代码中存在hal_fs_init函数,那每次执行任务岂不是都要初始化一遍?其实不是。根据调试信息可以看出,hal_fs_init函数仅在第一次执行,这点我们通过其函数实现也能发现。
程序执行时,如果已经初始化完成了,将不会再初始化。
那么编译新代码后查看结果。
可以看到,读取功能已经实现。
四、注意事项
若要使用上述函数并实现功能,你必须导入下面的一些文件,分别是fs.c和osal_snv.c,但这不代表你仅导入这些文件。
这其中的一些代码引入了其他的一些头文件和函数,比如crc16.c,你导入这些文件的同时务必要导入对应的头文件,以保证代码的正常运行。