Maximising littlefs flash storage space

I'm working on a project that uses BLE, NVS storage (for some serial number data and a few other variables) and littlefs, for storing log data that a client can then access over bluetooth via SMP.  I'm on nRF Connect v2.0.0 and am using an nRF52832.

But I'm coming into some problems with making maximum use of the available NVS storage for my information.  At the moment, this is how my static partitions are defined:

settings_storage:
  address: 0x7a000
  size: 0x2000
  placement:
    before: 
    - user_storage
  region: flash_primary
user_storage:
  address: 0x7c000
  size: 0x2000
  placement:
    before:
    - littlefs_storage
  region: flash_primary
littlefs_storage:
  address: 0x7e000
  size: 0x2000
  placement:
    before: 
    - end
  region: flash_primary

Ideally, I want to maximise the amount of littlefs storage, and reduce the settings and user storage to an absolute minimum.  But the user_storage needs at least 2 sectors, each of 4kB (a page) in size, even if I only need to store 200 bytes of info.  Similarly, it seems the settings area needs a similar amount as a minimum, because the moment I try and reduce that, my BLE functionality stops working.  This is only leaving me 8kB of littlefs storage.

Reading this, it seems its possible to combine the settings and user storage areas into one, which potentially would free up 8kB.  But I don't believe this has been implemented in v2.0.0, so I may need to upgrade to a newer SDK (I think its available in v2.1.2).  Question I have though is how do I actually implement this in my code?  I've just followed the nvs example to set up my user storage in my code, so it looks something like this:

static struct nvs_fs fs;

int16_t flash_initialise(void)
{
    int16_t rc;

	fs.flash_device = FLASH_AREA_DEVICE(STORAGE_NODE_LABEL);
	if (!device_is_ready(fs.flash_device)) {
			printk("Flash device %s is not ready\n", fs.flash_device->name);
		return -EINVAL;
	}

	fs.offset = FLASH_AREA_OFFSET(user_storage);
	rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info);
	if (rc !=0) 
	{
			printk("Unable to get page info\n");
		return rc = -EINVAL;
	}

	fs.sector_size = info.size;
	fs.sector_count = NVS_SECTOR_COUNT;

	if(fs.sector_size*fs.sector_count > (FLASH_AREA_SIZE(user_storage)))
	{
		printk("ERROR: Area used by NVS is larger than user storage\n");
	}

	rc = nvs_mount(&fs);
	if (rc !=0) 
	{
		printk("Flash Init failed\n");
		return rc;
	}
}

Note that STORAGE_NODE_LABEL = settings_storage.

Alternatively, this seems to be indicating its possible to combine nvs and littlefs partitions into one.  It doesn't really elaborate on how to do this though.  Is this an easier option than trying to combine settings and user storage?

Are there any other approaches I can use to reduce the amount of flash taken up by the settings and user storage, and hence maximise the amount of littlefs storage space I have?

Cheers,

Mike

Parents
  • Hi Mike, 

    If you had a look at Simon example in the case you pointed to you can see that he added a patch to the Zephy's settings library to add this function get_nvs_fs_structure() 

    It's to to get the instance of nvs used by the settings module so that you can store your own nvs data. 

    In newer SDK (my test v2.1.2) this patch has already been implemented. You can call settings_nvs_storage_get() to get the nvs instance used by settings. After that you can nvs_mount the nvs instance that you get from the function. Please have a look at the example by Simon. 
    This way you don't need to have an extra user_settings and can save some space. 

    In the second ticket that you referred to I don't think the customer merged littlefs and nvs to use one partition, but it's more about not using nvs at all and simply use littlefs to store data instead of using nvs. 

    If you need more space for your own data, have you considered to move the settings partition lower in flash for example 0x70000 instead of 0x7a000 ? Of course the trade off is that you will have smaller MCUBoot primary and secondary partitions.

  • Hi Hung,

    Thanks for input.  I did have a look through that example, but there was a fair bit going on around the flash memory stuff, and I was struggling to isolate what was relevant, and what wasn't.

    Anyway, I've made some progress.  Had to upgrade to v2.1.2, which isn't without its challenges.

    I now have the following flash initialisation, which I've effectively copied from Simons example:

    #include <drivers/flash.h>
    #include <fs/nvs.h>
    #include <storage/flash_map.h>
    #include <settings/settings_nvs.h>
    
    const struct device *flash_dev;
    static struct nvs_fs *fs;
    
    struct config_nvs 
    {
    	struct nvs_fs *cf_nvs;
    	uint16_t last_name_id;
    	const char *flash_dev_name;
    };
    
    int16_t nvs_config_init(void)
    {
       int rc = 0;
    	/*  Define the nvs file system by settings with:
    	 *	sector_size equal to the pagesize,
    	 *	2 sectors
    	 *	starting at FLASH_AREA_OFFSET(storage)
    	 */
       static struct config_nvs default_config_nvs;
       const struct flash_area *fa;
       struct flash_sector hw_flash_sector;
       uint32_t sector_cnt = 1;
       uint16_t cnt = 0;
       size_t nvs_sector_size, nvs_size = 0;
    
       rc = flash_area_open(STORAGE_NODE_LABEL, &fa);
       if (rc) 
       {
    	  	#ifdef DEBUG_NVS
    			printk("ERROR: Can't open flash area\n");
    		#endif
          	return -1;
       }
    
       rc = flash_area_get_sectors(STORAGE_NODE_LABEL, &sector_cnt, &hw_flash_sector);
       if (rc == -ENODEV) 
       {
    	  	#ifdef DEBUG_NVS
    			printk("ERROR: No device\n");
    		#endif
    		return -1;
       } 
       else if (rc != 0 && rc != -ENOMEM) 
       {
    		#ifdef DEBUG_NVS
    			printk("ERROR: No memory\n");
    		#endif
        	return -1;
       }
       else
       {
          	#ifdef DEBUG_NVS
    			printk("INFO: Sector info success\n");
    		#endif
       }
    
    	nvs_sector_size = CONFIG_SETTINGS_NVS_SECTOR_SIZE_MULT * hw_flash_sector.fs_size;
    
    	if (nvs_sector_size > UINT16_MAX) 
       {
        	#ifdef DEBUG_NVS
    			printk("ERROR: Area used by NVS is larger than available\n");
    		#endif
    		return -1;
    	}
    
    	while (cnt < CONFIG_SETTINGS_NVS_SECTOR_COUNT) 
       {
    	   nvs_size += nvs_sector_size;
    	   if (nvs_size > fa->fa_size) 
    	   {
    		 	break;
    	   }
    		cnt++;
    	}
    
    	default_config_nvs.cf_nvs = settings_nvs_storage_get();
    	default_config_nvs.flash_dev_name = fa->fa_dev->name;
    
    	#ifdef DEBUG_NVS
    		printk("INFO: Flash offset %d sector size %d cnt %d", fa->fa_off, nvs_sector_size, cnt);
    	#endif
    
    	default_config_nvs.cf_nvs->flash_device = device_get_binding(default_config_nvs.flash_dev_name);
    	if (default_config_nvs.cf_nvs->flash_device == NULL) 
       	{
    		#ifdef DEBUG_NVS
     			printk("ERROR: Flash device %s is not ready\n", default_config_nvs.flash_dev_name);
     		#endif
    		return -1;
    	}
    
    	rc = nvs_mount(default_config_nvs.cf_nvs);
    	if (rc) 
       	{
    		#ifdef DEBUG_NVS
     			printk("ERROR: Flash mount failed\n");
     		#endif
    		return -1;
       	}
        fs = default_config_nvs.cf_nvs;
    
       	// Check if data is stored in flash
    	rc = check_for_nvs_key();
    	return rc;
    }

    However, I am having issues with the call to 

    default_config_nvs.cf_nvs = settings_nvs_storage_get();
    The compiler isn't able to find the API settings_nvs_storage_get(); even though I've got settings/settings_nvs.h included.
    I'm also not clear on what I should be passing to settings_nvs_storage_get(); to get it to return the correct info.
    I may actually be on the totally wrong path here as well!
    Cheers,
    Mike
  • Hi Hung,

    Thanks - I've tried to implement this in my code, with SDK v2.1.2, but moving from v2.0.0 to v2.1.2 seems to cause my code to stop working as expected, so I'm not able to progress things.

    I'll need to do some more investigating as to what the issue is with the move from v2.0.0 to v2.1.2. before I can check if the above works for me

    Cheers,

    Mike

  • Hi Hung,

    Managed to update to NCS V2.2.0 and after much stuffing about, now have my code building.

    However, when I go to make the above changes to enable me to try and combine the settings and user storage into the one partition, I get the following build warnings:

    c:\Nordic\Development\LSR-MAX\LSR-MAX_V0_09\src\nvs_app.c:250:30: warning: passing argument 1 of 'settings_storage_get' from incompatible pointer type [-Wincompatible-pointer-types]
      250 |         settings_storage_get(&fs);
          |                              ^~~
          |                              |
          |                              struct nvs_fs **
    In file included from C:\Nordic\v2.2.0\zephyr\include\zephyr\storage\flash_map.h:261,
                     from c:\Nordic\Development\LSR-MAX\LSR-MAX_V0_09\src\nvs_app.h:17,
                     from c:\Nordic\Development\LSR-MAX\LSR-MAX_V0_09\src\nvs_app.c:14:
    C:\Nordic\v2.2.0\nrf\include\flash_map_pm.h:31:17: note: expected 'void **' but argument is of type 'struct nvs_fs **'
       31 | #define storage settings_storage
    C:\Nordic\v2.2.0\zephyr\include\zephyr\settings\settings.h:606:33: note: in expansion of macro 'storage'
      606 | int settings_storage_get(void **storage);
          |                                 ^~~~~~~
    [288/309] Linking C executable zephyr\zephyr_pre0.elf

    My code is set up as follows:

    static struct nvs_fs *fs;

    settings_load();
    settings_storage_get(&fs);
    
    rc = nvs_mount(fs);
    if (rc !=0) 
    {
    	printk("ERROR: Flash Init failed\n");
    	return rc;
    }

    If I ignore the warnings and flash my code to my device, it just goes through an endless reboot cycle (I suspect I'm trying to write/read somewhere I shouldn't)

    Any ideas on what's going wrong?

    Cheers,

    Mike

  • Hi Mike, 
    As far as I can see it's OK to have the warning as the input for settings_storage_get() expecting type void ** and we will assign it to the  nvs_fs * in the function so it's fine. The function doesn't know which backend will be used so they use void. 
    Have you tried with my example ? you can copy it to peripheral_uart project. 

  • Hi Hung,

    I've been testing your example in the peripheral_uart_project.  I can get it to work if I don't try and define a static partition (which is what I do want to do in my code eventually)

    The moment I define a static partition as follows:

    settings_storage:
      address: 0x7a000
      size: 0x6000
      placement:
        before: 
        - end
      region: flash_primary

    Then the code will crash with a return error of -45.  Stepping through things with the debugger, it looks like it comes unstuck when it makes a call to nvs_startup() in nvs.c

    At the point where it goes through and checks if sectors are open or closed in an attempt to find open sectors where it can begin writing:

    	/* all sectors are closed, this is not a nvs fs */
    	if (closed_sectors == fs->sector_count) {
    		rc = -EDEADLK;
    		goto end;
    	}

    Then it discovers closed_sectors=8 and so sets rc=-EDEADCLK and so the call to bt_enable(NULL) in main.c fails.

    Not sure why all the sectors are showing as closed, or what to do to fix the issue.

    Any ideas?

    Regards,

    Mike

  • Hi Mike, 
    Which board are you testing with ? 
    I added CONFIG_PM_SINGLE_IMAGE=y in prj.conf and then added pm_static.yml as follows and it worked fine for me. I haven't added littlefs partition though. 

    app:
      address: 0x0
      end_address: 0x7a000
      region: flash_primary
      size: 0x7a000
    settings_storage:
      address: 0x7a000
      end_address: 0x80000
      placement:
        before:
        - end
      region: flash_primary
      size: 0x6000
    sram_primary:
      address: 0x20000000
      end_address: 0x20010000
      region: sram_primary
      size: 0x10000

Reply
  • Hi Mike, 
    Which board are you testing with ? 
    I added CONFIG_PM_SINGLE_IMAGE=y in prj.conf and then added pm_static.yml as follows and it worked fine for me. I haven't added littlefs partition though. 

    app:
      address: 0x0
      end_address: 0x7a000
      region: flash_primary
      size: 0x7a000
    settings_storage:
      address: 0x7a000
      end_address: 0x80000
      placement:
        before:
        - end
      region: flash_primary
      size: 0x6000
    sram_primary:
      address: 0x20000000
      end_address: 0x20010000
      region: sram_primary
      size: 0x10000

Children
  • Hi Hung,

    I'm using the nRF52-DK at this stage, but will ultimately port this across to my custom board that uses an nRF52832 chip.

    With the mods you described above, I'm still seeing the same issue as before (returns -45 on checking for closed sectors).

     I've tried erasing the board and then reflashing it to see if this fixes the problem, but there is no change.

    I've also tried programming onto another DK, but I'm seeing the same behaviour.

    Cheers,

    Mike

  • Hi Mike, 

    Do you see the same problem with my example as well ? (after the change I suggested)


    If you don't could you try to simplify your application to compare to my example and see what caused the error ? 
    You can send us the simplified version of your application so we can test here. 

  • Hi Hung,

    Got it all working now.  Not sure what I did wrong, but I started from scratch again with the peripheral_uart example, copied your modified main.c into the src folder, added the pm_static.yml file and the mods to the proj.conf file and its now working.

    I even tested it with a separate littlefs partition (didn't actually add any littlefs code to test this side of things) and that seemed to work as well, so I've got enough to push on with my code to see if I can get that functioning

    Cheers,

    Mike

Related