Many embedded systems application need to store some kind of data in a persistent way: calibration values, settings or log information. For a smaller amount of data, using an external memory or file system is an overkill. In many system I’m using minINI to store key-value pars in in a ‘ini-file’ way, but it requires the use of a file system of some kind. minINI is great and efficient, and makes getting and storing data really easy. But for simple cases, a single FLASH memory page or sector is just all what I need. Instead managing that page directly, why not using minINI without a file system?
For example I use the above board to drive up to 4 different stepper motors with hall sensors. I need to store calibration offsets and motor information, and the information can be different for each board. The boards are connected by a RS-485 bus, so each board has a unique address. For this application I need to store the key-value pairs (e.g. address=0x10). For this, I’m using minINI which stores the data in a page of the internal FLASH memory, so no file system is needed.
Because minINI works with files, it requires some kind of file system. For smaller system or for smaller data amount this adds lots of complexity with increased code size and data needs. In this article I present an approach for a file-less and file-system-less adaption of the minINI library, which only requires a small and configurable RAM buffer plus a single FLASH page or sector. That way a very versatile and user-friendly key-value storage can be used for small embedded systems without the need for a file system.
The implementation is available on GitHub.
.ini files allow me to group data into ‘sections’ and ‘key-value’ pairs. Below is an example:
[motor0] name=split-flap 0 offset=30 x=0 [motor1] offset=32
Using minINI I can read/write such values. It features a ‘read-only’ mode in which I can only read data.
The minINI architecture includes a ‘glue’ interface. That way it can be easily adapted to different file systems. Below a ‘glue’ implementation for the 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)
The idea was to add a new ‘glue’ which does not require a file system. Instead, it directly reads and writes FLASH memory and kind of mimics a file system.
This comes with a limitation: only a single ‘ini file’ is supported. That limitation could be easily extended to support multiple ‘files’, but I really did not see the need, at least not in my applications.
Configuration is with using configuration macros (see Different Ways of Software Configuration). Below an example configuration:
That way the flash memory can be configured. The data size macro is used to limit the data size to reduce the amount of memory buffer which is used for writing the flash memory.
The used FLASH memory needs to be reduced/excluded from the amount of FLASH available for the linker, otherwise it might be used by the application too. Below is how the memory can be reduced from the memory map, using the NXP MCUXpresso IDE:
The implementation includes a command line/shell interface.
Below the status of a read-only configuration:
The ‘McuMinINI’ shows the ‘core’ information of minINI, while the ‘ini’ group is for the flash memory implementation of it.
With the command line interface, the data can be inspected or modified:
Using key-value pairs with minINI is really easy: I can query integer, boolean, floating point or string values. Below an example getting the stored address of the device:
#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);
What I really like with minINI is that I can provide a default value (0x1 in above example): if the section/key is not present, it returns the default value I have specified: that way the default values do not need any space in the configuration area.
Storing a value is like in the example below:
McuMinINI_ini_putl(NVMC_MININI_SECTION_RS485, NVMC_MININI_KEY_RS485_ADDR, addr, NVMC_MININI_FILE_NAME);
Of course every feature comes with some costs. But in this case, I think it is really minimal.
For storing the data in FLASH, it needs at least one sector or flash page. The size of this depends on the system used and is for example 1 kByte (LPC845) or 2 KByte (Kinetis K22), depending on the microcontroller. The size is configured with McuMinINI_CONFIG_FLASH_NVM_BLOCK_SIZE.
For write access it needs a RAM buffer to backup and store the data from FLASH. The amount is configured McuMinINI_CONFIG_FLASH_NVM_MAX_DATA_SIZE. The minimum amount is 64 bytes. Depending on the amount of data to be stored, 128 or 256 might be more reasonable. For read-only access the buffer amount zero. Depending in the microcontroller and SDK used, a device handle for the FLASH driver is required: for example on a Kinetis this requires an extra of 100 bytes. For others like the LP845 no handle is needed, as the ROM routines are used for flash programming.
On a Kinetis, a read-only configuration adds around 6 KByte of code size (-O0, no optimization), and making it read-write adds an extra of 3 KByte (-O0). Adding shell support adds an extra 1 KByte: so this means that a maximum of 10 KByte of FLASH memory is needed, which gets down to about 6 KByte with optimizations turned on. So far less than a full file system support which easily is 30-50 KByte of FLASH memory.
With this additional glue port for minINI it is possible to use it without a file system. This makes it suitable for smaller embedded systems. It supports a read-only and read-write data storage. It uses direct flash memory read/writes and only in read-write configuration it needs a configurable RAM buffer for writing or extending the data. With this it is easy to add ‘key-value’ pair storage to any embedded system, with the cost of less than 10 KByte of FLASH memory.
You can find the implementation in the McuLib on GitHub.
Happy flashing 🙂
- 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