许多嵌入式系统应用需要以持久的方式存储某种数据:校准值、设置或日志信息。对于较少的数据量,使用外部存储器或文件系统是一种过度大材小用。在许多系统中,我使用minINI以“ini-file”的方式存储键值解析,但它需要使用某种文件系统。minINI很棒,效率很高,使获取和存储数据变得非常容易。但对于简单的情况,单个闪存页面或扇区正是我所需要的。相反,直接管理该页面,为什么不在没有文件系统的情况下使用minINI?
例如,我使用上面的电路板驱动多达4个不同的步进电机和霍尔传感器。我需要存储校准偏移量和电机信息,并且每个电路板的信息可能不同。这些板通过RS-485总线连接,因此每个板都有一个唯一的地址。对于此应用程序,我需要存储键值对(例如,address=0x10)。为此,我使用minINI将数据存储在内部闪存的一页中,因此不需要文件系统。
1、概述
因为 minINI 处理文件,所以它需要某种文件系统。对于较小的系统或较小的数据量,随着代码大小和数据需求的增加,这增加了复杂性。在本文中,我将介绍一种对 minINI 库进行无文件和无文件系统适配的方法,该方法只需要一个小型且可配置的 RAM 缓冲区以及单个 FLASH 页面或扇区。这样,一个非常通用和用户友好的键值存储可用于小型嵌入式系统,而无需文件系统。
该实现可在 GitHub 上找到。
2、.ini文件
.ini文件允许我将数据分组为“部分”和“键值”对。下面是一个示例:
[motor0]
name=split-flap 0
offset=30
x=0
[motor1]
offset=32
使用minINI,我可以读/写这样的值。它具有“只读”模式,在这种模式下,我只能读取数据。
3、minINI接口
minINI 架构包括一个“粘合”接口。这样,它可以很容易地适应不同的文件系统。以下是 FatFS 的“粘合”实现:
#define INI_FILETYPE FIL
#define ini_openread(filename,file) (f_open((file), (filename), FA_READ+FA_OPEN_EXISTING) == FR_OK)
#define ini_openwrite(filename,file) (f_open((file), (filename), FA_WRITE+FA_CREATE_ALWAYS) == FR_OK)
#define ini_close(file) (f_close(file) == FR_OK)
#define ini_read(buffer,size,file) f_gets((buffer), (size),(file))
#define ini_write(buffer,file) f_puts((buffer), (file))
#define ini_remove(filename) (f_unlink(filename) == FR_OK)
#define INI_FILEPOS unsigned long//DWORD
#define ini_tell(file,pos) (*(pos) = f_tell((file)))
#define ini_seek(file,pos) (f_lseek((file), *(pos)) == FR_OK)
4、FLASH接口
我们的想法是添加一个新的“粘合”,它不需要文件系统。相反,它直接读取和写入闪存,并模仿文件系统。
这有一个限制:仅支持单个“ini文件”。该限制可以很容易地扩展到支持多个“文件”,但我真的没有看到这种需求,至少在我的应用程序中没有。
5、配置
配置是使用配置宏(请参见软件配置的不同方式)。下面是一个示例配置:
这样就可以配置闪存。数据大小宏用于限制数据大小,以减少用于写入闪存的内存缓冲区的数量。
需要从可用于链接器的闪存数量中减少/排除已使用的闪存,否则应用程序也可能使用它。以下是如何使用恩智浦MCUXpresso IDE从内存映射中减少内存:
6、命令行
该实现包括一个命令行/shell 接口。
低于只读配置的状态:
“McuMinINI”显示minINI的“核心”信息,而“ini”组用于闪存实现它。
使用命令行界面,可以检查或修改数据:
7、用法
将键值对与 minINI 结合使用非常简单:我可以查询整数、布尔值、浮点值或字符串值。下面是获取设备存储地址的示例:
#define NVMC_MININI_FILE_NAME "settings.ini" /* 'file' name used */
/* RS-485 bus settings */
#define NVMC_MININI_SECTION_RS485 "rs485"
#define NVMC_MININI_KEY_RS485_ADDR "addr" /* long value: RS-485 address */
...
*addr = McuMinINI_ini_getl(NVMC_MININI_SECTION_RS485, NVMC_MININI_KEY_RS485_ADDR, 0x1, NVMC_MININI_FILE_NAME);
我真正喜欢minINI的是我可以提供默认值(在上面的示例中0x1):如果部分/键不存在,它将返回我指定的默认值:这样默认值就不需要配置区域中的任何空间。
存储值类似于下面的示例:
McuMinINI_ini_putl(NVMC_MININI_SECTION_RS485, NVMC_MININI_KEY_RS485_ADDR, addr, NVMC_MININI_FILE_NAME);
8、内存要求
当然,每个功能都会带来一些成本。但在这种情况下,我认为它真的是最小的。
为了在闪存中存储数据,它至少需要一个扇区或闪存页。其大小取决于所使用的系统,例如1 kByte(LPC845)或2 KByte(Kinetis K22),具体取决于微控制器。大小配置为McuMinINI_CONFIG_FLASH_NVM_BLOCK_SIZE。
对于写入访问,它需要一个RAM缓冲区来备份和存储来自FLASH的数据。该金额McuMinINI_CONFIG_FLASH_NVM_MAX_DATA_SIZE配置。最小值为 64 个字节。根据要存储的数据量,128 或 256 可能更合理。对于只读访问,缓冲区量为零。根据所使用的微控制器和SDK,需要闪存驱动程序的设备句柄:例如,在Kinetis上,这需要额外的100字节。对于像LP845这样的其他产品,不需要手柄,因为ROM例程用于闪存编程。
在 Kinetis 上,只读配置会增加大约 6 KB 的代码大小(-O0,无优化),而使其读写会增加额外的 3 KB (-O0)。添加shell支持会增加额外的1 KB:因此这意味着最多需要10 KB的闪存,在启用优化后降至约6 KB。到目前为止,还不到完整的文件系统支持,很容易是30-50 KB的闪存。
9、总结
有了这个用于minINI的附加粘合端口,可以在没有文件系统的情况下使用它。这使得它适用于较小的嵌入式系统。它支持只读和读写数据存储。它使用直接闪存读/写,只有在读写配置中,它才需要一个可配置的RAM缓冲区来写入或扩展数据。这样,就可以轻松地将“键值”对存储添加到任何嵌入式系统,而闪存的成本不到10 KB。
您可以在GitHub上的McuLib中找到该实现。
链接
- minINI by Compuphase: https://github.com/compuphase/minIni
- minINI: https://www.compuphase.com/minini.htm
- Using FatFS and MinINI with the NXP LPC55S16 EVK
- FatFS, MinIni, Shell and FreeRTOS for the NXP K22FN512
- Different Ways of Software Configuration
- Configuration Data: Using the Internal FLASH instead of an external EEPROM
- McuOnEclipse Library on GitHub: https://github.com/ErichStyger/McuOnEclipseLibrary
英文原文:Key-Value pairs in FLASH Memory: file-system-less minINI | MCU on Eclipse