Problems writing/reading struct to NVS

I've been slowly adding NVS functionality to my code.  Has been working fine, up until today when I attempted to write a struct I have for storing historical data to flash.

This is the struct:

struct log_data {
	// data structure to store individual strike events
	uint8_t	strike_level;  // SML = 1, MED = 2 or LRG = 4
	uint16_t	total_strike_count;  // Total number of strikes recorded to date
	struct bt_cts_current_time time_detected;  //Time stamp for each strike
};

I then have an array of this struct.  At this point, MAX_STRIKE_RECORDS = 10, but ultimately I want to see this at 250.

struct log_data lsr_strike_log[MAX_STRIKE_RECORDS];

In my code to read the data in from Flash, I have the following:

	uint32_t rc = 0;
	rc = nvs_read(&fs, HISTORY_ID, &lsr_strike_log, sizeof(lsr_strike_log));
	printk("sizeof = %u\n", sizeof(lsr_strike_log));
	printk("strike history rc = %u\n", rc);

The return value from nvs_read() is supposed to be the amount of data read in, if everything works as expected.  In my case, this should be equal to sizeof(lsr_strike_log), which is 160 bytes.  If the return value is larger than this, it apparently means there is more data to read in.  This is what I get from my two printk() statements:

sizeof = 160
strike history rc = 4294967294

So, its like the nvs_read() is going outside my defined flash area, or something - I'm not sure, hence why I'm posting here to see if someone can help me understand what's going on better.
My flash is initialised as per the following, and I don't get any of the error messages output via UART, so it appears to be initialising OK.
flash_dev = FLASH_AREA_DEVICE(STORAGE_NODE_LABEL);
	if (!device_is_ready(flash_dev)) {
		printk("Flash device %s is not ready\n", flash_dev->name);
		return -EINVAL;
	}
	fs.offset = FLASH_AREA_OFFSET(storage);
	rc = flash_get_page_info_by_offs(flash_dev, fs.offset, &info);
	if (rc) 
	{
		printk("Unable to get page info\n");
		return rc;
	}
	fs.sector_size = info.size;
	fs.sector_count = 3U;

	rc = nvs_init(&fs, flash_dev->name);
	if (rc) 
	{
		printk("Flash Init failed\n");
		return rc;
	}
If I enable CONFIG_LOG=y in my proj.config file, I see the following output:
I suspect I'm doing something obvious wrong. Problem is, its not obvious to me.
Thanks and regards,
Mike
Parents
  • Thanks Haakonsh.

    If I simplify what I'm trying to write to NVS, it seems to work OK.  I changed my data structure to just a simple array:

    uint16_t lsr_strike_log[9];

    And if I write/read this to NVS, I can see that the data is being stored.  I'm still getting weird values for the return value from nvs_write() and nvs_read() though.

    This is the log info I see when writing the simple array:

    If I try and write my struct to NVS in the form I ultimately want to, I crash my DK and it keeps rebooting.

    In my code I initialse NVS before I initialise anything to do with BLE, so not sure if the BLE initialisation then changes what I've done when setting up NVS?

    Thanks and regards,

    Mike

  • Hello Mike,

    What is the current state on this issue? 

    Best regards,

    Simon

  • I played around with the NVS sample and made it use the Partition Manager (PM) instead of DTS (did this by setting CONFIG_BOOTLOADER_MCUBOOT=y, since PM will be used when child images are added).

    I was able to make it work with nrf52dk_nrf52832 and NCS v2.0.0

    nvs_prt_mngr.zip

    If the PM is enabled the macros used (FLASH_AREA_OFFSET() for example), will get info from PM instead of DTS. See 

    One thing you should be aware of, don't let NVS use the area outside the custom partition. Make sure (sector size)*(sector count) is smaller than user_storage.

    Best regards,

    Simon

  • Hi Simon,

    Thanks for sticking with me on this!

    OK, I'm getting things working, but I'm still coming unstuck when I try and push the size of my data logging array up towards the size I want it.  I'm clearly not understanding how the flash is arranged/configured. Or there is something else at play

    At the moment, I have the static partitions configured as follows:

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

    I'm outputting my flash and user info data via UART so I can check everything is OK.  This is what I get with the above configuration, and with the number of sectors set to 2.

    Sector size = 4096
    Sector count = 2
    Sector size*count = 8192
    User storage = 8192

    Now, what I'm writing to flash consists of the following:

    	(void)nvs_write(&fs, TOTAL_STRIKE_COUNT_ID, &strike_count_total, sizeof(strike_count_total));
    	(void)nvs_write(&fs, STRIKE_SML_ID, &strike_count_sml, sizeof(strike_count_sml));
    	(void)nvs_write(&fs, STRIKE_MED_ID, &strike_count_med, sizeof(strike_count_med));
    	(void)nvs_write(&fs, STRIKE_LRG_ID, &strike_count_lrg, sizeof(strike_count_lrg));
    	(void)nvs_write(&fs, LOG_DATA_ID, &lsr_strike_log, sizeof(lsr_strike_log));

    These are defined as:

    #define MAX_STRIKE_RECORDS 150
    
    uint16_t strike_count_total = 0;
    uint16_t strike_count_sml = 0;
    uint16_t strike_count_med = 0;
    uint16_t strike_count_lrg = 0;
    
    struct log_data {
    	uint8_t	strike_level;  // SML = 1, MED = 2 or LRG = 4
    	uint16_t year;
    	uint8_t month;
    	uint8_t day;
    	uint8_t hours;
    	uint8_t minutes;
    	uint8_t seconds;
    };
    
    struct log_data lsr_strike_log[MAX_STRIKE_RECORDS];
    

    What I'm trying to do is make enough flash space available to be able to store all that, but with MAX_STRIKE_RECORDS = 250.  At the moment, things go pear shaped with MAX_STRIKE_RECORDS > 195.  And I'm not sure why, nor what to do about it.  By my reckoning, I have more than enough space, but it almost seems like what I'm trying to store is consuming twice what I think it is.

    From here: https://docs.zephyrproject.org/latest/services/storage/nvs/nvs.html it looks like each of the elements being stored in flash needs:

    8 bytes of meta-data + 1 byte of id + sizeof(data).

    I have 8 elements in total (key, batch, serial, total, sml, med, lrg and log)

    Each one of my lsr_strike_log array elements seems to use up 10 bytes of data.

    So, by my calcs, the total amount of flash storage for 250 records is:

    key = 8 + 1 + 3 = 12

    batch = 8 + 1 + 4 = 13

    serial = 8 + 1 + 2 = 11

    total = 8 + 1 + 2 = 11

    sml = 8 + 1 + 2 = 11

    med = 8 + 1 + 2 = 11

    lrg = 8 + 1 + 2 = 11

    log = 8 + 1 + 250x10 = 2509

    Which gives me 2589 bytes.  I have 4096 in each sector, which seems ample to me.  But things bomb out when I go past MAX_STRIKE_RECORDS = 195.  This requires 2039 bytes in total.  Which is almost exactly half the size of one sector (4096 bytes).

    If I set MAX_STRIKE_RECORDS > 195, then when I try and write the log data to flash via:

    val = nvs_write(&fs, LOG_DATA_ID, &lsr_strike_log, sizeof(lsr_strike_log));
    if (val < 0)
    {
    	printk("Return val on log data write  = %d\n", val);
    }

    I get a return value of -28, which equates to:

    ENOSPC 28       /**< No space left on device */

    So, my questions are:

    1. Why, when it looks like I've got more than enough user storage allocated, do I run out of room?

    2. How can I increase my user storage so I have enough room for 250 log array elements?

    Cheers,

    Mike

  • I didn't get time to look to deeply into this today, but I'll just leave one comment. If I remember correctly, one sector almost always free - it is used during garbage-collection procedrue. So I think you have to subtract one sector, and that is the available area you can use.


    Down below I've copied in a reply I made more than a year ago in another ticket:

    If you look at the start of the sample zephyr\samples\subsys\nvs\src\main.c, the sector size and sector count is fed into nvs_init(). The sector size and sector count decides how much memory is allocetd for NVS and the offset decides where in flash to place the NVS partition. Other flash procedures will not (and should not) touch the NVS area.

    The available memory for the sample  zephyr\samples\subsys\nvs can be calculated in the following manner

    • ate_size = 8, sector_size = 4096 (check this reply for an explanation of these numbers (I used the 52840 during testing as well))
      sector_count = 3 (it's set here)
    • available_sectors = sector count - 1 (subtract 1, since one sector is free and used for garbage collection procedure)
    • available NVS memory = sector_size * available_sectors - ate_size * available_sectors (read this reply to understnd why we need to subtract ate_size*av. sectors)
    • available NVS memory = 4096 * 2 - 8 * 2 = 8176

    I tested this out, by adding the following code to the sample zephyr\samples\subsys\nvs:

    	rc = nvs_init(&fs, flash_dev->name);
    	if (rc) {
    		printk("Flash Init failed\n");
    		return;
    	}
    	ssize_t free_space = nvs_calc_free_space(&fs);
    	printk("Free space: %d (should be %d)\n", free_space, fs.sector_size*(fs.sector_count-1)-(8*2));
    	strcpy(buf, "abcdefghijklmnopqstuvwxyzabcdefghijklmnopqstuvwxyabcdefghijklmnopqstuvwxyzabcdefghijklmnopqstuvwxyz");
    	printk("Write %d bytes\n", strlen(buf)+1);
    	(void)nvs_write(&fs, ADDRESS_ID, &buf, strlen(buf)+1);
    	free_space = nvs_calc_free_space(&fs);
    	printk("Free space: %d\n", free_space);

    I then got the following output:

    Free space: 8176 (should be 8176)
    Write 100 bytes
    Free space: 8068

    As you can see, the output corresponds to the calculations.

    The free space after writing is not 8176-100=8076, but 8068, which is because each stored record requires 8 bytes of metadata in addition (as mentioned in the NVS documentation), which gives 8176 - 100 - 8 = 8068

    Best regards,

    Simon

  • OK, thanks for all that info.  Gotta say, you're up there with Carl on your ability to stick with a problem!! :-)

    I put in a call to that calculate free space API to see what was going on.  I put this in at the initialisation (prior to actually storing anything in flash) and then again each time I did a write to flash.

    With MAX_STRIKE_RECORDS set to 195, I'm expecting to see flash useage of 2036 bytes (this includes the 8 byte allowance for ate_size, for 1 byte for ID and 8 bytes for meta data for each thing I'm writing to flash and the actual data I'm trying to store)

    After initialisation, its telling me I have total user storage of 8192 (2 sectors at 4096 per sector).  Obviously I really only have one of these available to me, so 4096.  The nvs_calc_free_space(&fs) API is saying I have 4088, so there is a little bit of overhead I've not accounted for, but its only 6 bytes.  On track so far.

    When I do a write of all my various info to flash, as mentioned above I'm expecting to see 2036 bytes consumed, so my free space should drop down to 4088 - 8 - 2036  = 2044.  The figure I get is 2044.  So, things seem OK.  And my code is working, storing things in flash.

    Now, if I bump the value of MAX_STRIKE_RECORDS up to 200, I am expecting that the memory I will need will increase to 2089, and my free memory will drop down to 1991 (4088 - 8 - 2089 = 1991).  I'm actually seeing a result of 1996 from that API.  So close, but not exact.

    But the problem is now, that my return value from 

    val = nvs_write(&fs, LOG_DATA_ID, &lsr_strike_log, sizeof(lsr_strike_log));
    is now giving me the value -28, which corresponds to no space left on device.  So, even though I have ~ 1900 bytes of space available, its bombing out thinking there isn't enough space.
    I then tried increasing my sector count to 3 (currently 2).  This means I need 3 x 4096 = 12,288 bytes of user storage (0x3000).  So, I change my fixed partitions as follows:
    user_storage:
      address: 0x7c000
      size: 0x3000
      placement:
        before: 
        - settings_storage
      region: flash_primary
    settings_storage:
      address: 0x7f000
      size: 0x2000
      placement:
        before:
        - end
      region: flash_primary
    That causes by build to crash with the following error:
    -- Found partition manager static configuration: C:/Nordic/Development/LSR-MAX/LSR-MAX_V0_09/pm_static.yml
    Partition 'settings_storage' is not included in the dynamic resolving since it is statically defined.
    Partition manager failed: Incorrect amount of gaps found in static configuration. There must be exactly one gap in the static configuration to support placing the dynamic partitions (such as 'app'). Gaps found (2):0x0-0x7c000 0x81000-0x80000 The most common solution to this problem is to fill the smallest of these gaps with statically defined partition(s) until there is only one gap left. Alternatively re-order the already defined static partitions so that only one gap remains.
    Failed to partition region flash_primary, size of region: 524288
    Partition Configuration:
    mcuboot:
      placement:
        before:
        - mcuboot_primary
      size: 49152
    mcuboot_pad:
      placement:
        align:
          start: 4096
        before:
        - mcuboot_primary_app
      size: 512
    settings_storage:
      placement:
        before:
        - end
      size: 8192
    user_storage:
      placement:
        before:
        - settings_storage
      size: 12288
    Which seems to suggest I'm not allowed to go past memory location 0x80000.  Is that correct?
    Anyway, if I peg back my settings_storage to compensate, so my fixed partitions now look like this:
    user_storage:
      address: 0x7c000
      size: 0x3000
      placement:
        before: 
        - settings_storage
      region: flash_primary
    settings_storage:
      address: 0x7f000
      size: 0x1000
      placement:
        before:
        - end
      region: flash_primary
    Everything builds OK.  And I can now see I have 8176 bytes of flash just after its all initialised, and then after my first write to flash (I've got MAX_STRIKE_RECORDS set to 200 here), I see I have 6084 bytes free.  Seems pretty close to 8176 - 2089 = 6087.
    BUT, and this is the bit I don't understand, now my Bluetooth advertising won't work.  The call to
    err = bt_le_adv_update_data(ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); returns an error of -11.
    Maybe its got something to do with me reducing the settings_storage to much, I'm not sure.
    So, I'm a bit stuck.  I can't work out why my flash write bombs out when I've still got plenty of space, and I can't work out how to increase the available space to fix this, without impacting on something else!
    Cheers,
    Mike
  • OK, think I've answered a few of my questions:

    1. "storage" is defined in the .dts as

    storage_partition: partition@7a000 {
    			label = "storage";
    			reg = <0x0007a000 0x00006000>;
    		};

    The 0x80000 limit obviously corresponds to the offset of 0x7a000 and size of 0x6000.  I'm assuming I can't modify that to increase the storage area, as I'll run into issues with available flash?

    2. By moving the offset of user_storage to 0x7a000, I can get the extra space I would need, without running into issues with having to reduce the amount of settings_storage (which I think might be the cause of my BLE advertising issues, but I can't be sure).

    The question of why I run into storage issues when I appear to have plenty of space left is still baffling me though.  I'm assuming that the API's take care of things so that if I have 2000 bytes of data, I only need to consume 2000 bytes (or thereabouts) of flash.  I know when I was using the Cypress chips, their flash was arranged in blocks, and if I wanted to write one byte to flash, I actually needed to use up an entire row (up to 256 bytes depending upon the size of flash in the chip), unless I was able to shoe-horn all my data into a 256 byte array and then write it all to the one row.  Just thinking if the reason I get the -28 error when I've got more than 195 strike records has something to do with how the API's are arranging that data in flash

    Cheers,

    Mike

Reply
  • OK, think I've answered a few of my questions:

    1. "storage" is defined in the .dts as

    storage_partition: partition@7a000 {
    			label = "storage";
    			reg = <0x0007a000 0x00006000>;
    		};

    The 0x80000 limit obviously corresponds to the offset of 0x7a000 and size of 0x6000.  I'm assuming I can't modify that to increase the storage area, as I'll run into issues with available flash?

    2. By moving the offset of user_storage to 0x7a000, I can get the extra space I would need, without running into issues with having to reduce the amount of settings_storage (which I think might be the cause of my BLE advertising issues, but I can't be sure).

    The question of why I run into storage issues when I appear to have plenty of space left is still baffling me though.  I'm assuming that the API's take care of things so that if I have 2000 bytes of data, I only need to consume 2000 bytes (or thereabouts) of flash.  I know when I was using the Cypress chips, their flash was arranged in blocks, and if I wanted to write one byte to flash, I actually needed to use up an entire row (up to 256 bytes depending upon the size of flash in the chip), unless I was able to shoe-horn all my data into a 256 byte array and then write it all to the one row.  Just thinking if the reason I get the -28 error when I've got more than 195 strike records has something to do with how the API's are arranging that data in flash

    Cheers,

    Mike

Children
Related