NVS not compatible with MCUBOOT

Hello, 

I am working with the nrf52840 chip, and would like to store a serial number on the device that persists across boots and OTA-DFU. I have integrated the nvs sample into my code, and it seems to work, except for when I try to use a separate partition to ensure that bluetooth settings do not overwrite the serial number. I have created a separate partition in the devicetree file, and when I try to use that partition, I get build errors. If I do not need to use that partition, then it may work, but I am not sure. The reason I am doing this is is due to the suggestions of this thread:

 Problem to read back flash with NVS when concurrent use with Bluetooth 

I have also considered using the partition manager, but I am unsure how to go about this, and the documentation is very sparse on how to create these pm.yaml files. 

Basically, I need to store a 32-digit string on a section of the flash that will not be touched by OTA-DFU, and can be read at boot. I am still kind of new to nordic development, so any suggestions are appreciated!

Here is my dts file:

 &flash0 {
	 /*
	  * For more information, see:
	  * https://docs.zephyrproject.org/latest/guides/dts/legacy-macros.html#legacy-flash-partitions
	  */
	 partitions {
		 compatible = "fixed-partitions";
		 #address-cells = <1>;
		 #size-cells = <1>;
 
		 boot_partition: partition@0 {
			 label = "mcuboot";
			 reg = <0x000000000 0x0000C000>;
		 };
		 slot0_partition: partition@c000 {
			 label = "image-0";
			 reg = <0x0000C000 0x00067000>;
		 };
		 slot1_partition: partition@73000 {
			 label = "image-1";
			 reg = <0x00073000 0x00067000>;
		 };
		 scratch_partition: partition@da000 {
			 label = "image-scratch";
			 reg = <0x000da000 0x0001e000>;
		 };
 
		 /*
		  * The flash starting at 0x000f8000 and ending at
		  * 0x000fffff is reserved for use by the application.
		  */
 
		 /*
		  * Storage partition will be used by FCB/LittleFS/NVS
		  * if enabled.
		  */
		 storage_partition: partition@f8000 {
			 label = "storage";
			 reg = <0x000f8000 0x00004000>;
		 };
		 user_partition: partition@fC000{
			label = "user_storage";
			reg = <0x000fC000 0x00004000>;
		 };
	 };
 };

And here is my prj.conf file, where the CONFIG_BOOTLOADER_MCUBOOT=y is commented out as the line that causes build errors.

CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
CONFIG_BT_SMP=y
CONFIG_BT_PERIPHERAL=y
CONFIG_LOG=y
CONFIG_LOG_PRINTK=y
CONFIG_RTT_CONSOLE=y
CONFIG_BT_DIS=y
CONFIG_BT_DIS_PNP=n
CONFIG_BT_DIS_MODEL="Calisto Insight"
CONFIG_BT_DIS_MANUF="Calisto"
CONFIG_BT_DIS_SERIAL_NUMBER=y
CONFIG_BT_DIS_SERIAL_NUMBER_STR="beta_2"
CONFIG_BT_DIS_FW_REV=y
CONFIG_BT_DIS_HW_REV=y
CONFIG_BT_DIS_SW_REV=y
CONFIG_BT_DIS_FW_REV_STR="1.0.0"
CONFIG_BT_DIS_HW_REV_STR="1.0.0"
CONFIG_BT_DIS_SW_REV_STR="1.0.0"
CONFIG_BT_DEVICE_NAME="Calisto_Insight_2"
CONFIG_MCUBOOT_IMAGE_VERSION="1.0.0+0"
CONFIG_BT_BAS=y
CONFIG_ADC=y
CONFIG_ADC_ASYNC=y
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_BT_BUF_ACL_RX_SIZE=251
CONFIG_BT_BUF_ACL_TX_SIZE=251
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
CONFIG_MCUMGR=y
# CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUMGR_CMD_OS_MGMT=y
CONFIG_MCUMGR_CMD_IMG_MGMT=y
CONFIG_MCUMGR_SMP_BT=y
CONFIG_MCUMGR_SMP_BT_AUTHEN=n
CONFIG_IMG_ERASE_PROGRESSIVELY=y
CONFIG_DEBUG_OPTIMIZATIONS=y
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_DFU_TARGET=y
CONFIG_DFU_TARGET_MCUBOOT=y
CONFIG_IMG_ERASE_PROGRESSIVELY=y
CONFIG_IMG_MANAGER=y
CONFIG_MCUBOOT_IMG_MANAGER=y
CONFIG_SIGN_IMAGES=y
CONFIG_HWINFO=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_LOG_MODE_IMMEDIATE=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y

CONFIG_NVS=y
CONFIG_NVS_LOG_LEVEL_DBG=y
CONFIG_REBOOT=y
CONFIG_MPU_ALLOW_FLASH_WRITE=y

Here is the part of my main() that sets up and reads the flash:

#include <zephyr/zephyr.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/device.h>
#include <string.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>
#include <zephyr/fs/nvs.h>

static struct nvs_fs fs;

#define STORAGE_NODE_LABEL user_storage

/* 1000 msec = 1 sec */
#define SLEEP_TIME   100
/* maximum reboot counts, make high enough to trigger sector change (buffer */
/* rotation). */
#define MAX_REBOOT 400

#define ADDRESS_ID 1
#define KEY_ID 2
#define RBT_CNT_ID 3
#define STRING_ID 4
#define LONG_ID 5


void main(void)
{
	int rc = 0, cnt = 0, cnt_his = 0;
	char buf[16];
	uint8_t key[8], longarray[128];
	uint32_t reboot_counter = 0U, reboot_counter_his;
	struct flash_pages_info info;

	/* define the nvs file system by settings with:
	 *	sector_size equal to the pagesize,
	 *	3 sectors
	 *	starting at FLASH_AREA_OFFSET(STORAGE_NODE_LABEL)
	 */
	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;
	}
	fs.offset = FLASH_AREA_OFFSET(STORAGE_NODE_LABEL);
	rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info);
	if (rc) {
		printk("Unable to get page info\n");
		return;
	}
	fs.sector_size = info.size;
	fs.sector_count = 3U;

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

	/* ADDRESS_ID is used to store an address, lets see if we can
	 * read it from flash, since we don't know the size read the
	 * maximum possible
	 */
	rc = nvs_read(&fs, ADDRESS_ID, &buf, sizeof(buf));
	if (rc > 0) { /* item was found, show it */
		printk("Id: %d, Address: %s\n", ADDRESS_ID, buf);
	} else   {/* item was not found, add it */
		strcpy(buf, "192.168.1.5");
		printk("No address found, adding %s at id %d\n", buf,
		       ADDRESS_ID);
		(void)nvs_write(&fs, ADDRESS_ID, &buf, strlen(buf)+1);
	}

Thanks for any suggestions/help you might be able to provide!

Best, 

Andrew

  • Hello Andrew,

    In general, we recommend using the CUSTOMER registers in  UICR for keeping permanent values such serial numbers, Bluetooth addresses, etc, as it is easier (does not require FLASH area to be allocated by the linker). It's better to use a filesystem in FLASH when you have data that needs to get updated at some point (Note: the ERASEUICR operation is blocked when APPROTECT is enabled - Access port protection behavior).

    Example of how you can write to a UICR.CUSTOMER[] register with nrfjprog:

    $ nrfjprog --memwr 0x10001080 --val 0x12345678 // Write 0x12345678 to UICR.CUSTOMER[0].

    But if you prefer to use NVS, please have a look at the example I made below. It's based on the peripheral_lbs sample from SDK 2.1.1 and the code snippets you posted. But instead of making a separate storage partition for the user data I opted to share the storage partition between the NVS instance used by BT settings and the one used by the app.

    2260.peripheral_lbs_w_nvs.zip

    Note: NVS requires a minimum of 2 sectors ( 2x 4096 bytes) for each instance, but there are not checks in my implementation to ensure this requirement is met.

    https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/services/storage/nvs/nvs.html#nvs-api

    Changes:

    --- /home/vidarbe/ncs/v2.1.1/nrf/samples/bluetooth/peripheral_lbs/prj.conf	2022-10-24 09:14:32.019870394 +0200
    +++ prj.conf	2022-10-31 14:36:06.904168035 +0100
    @@ -15,3 +15,13 @@
     CONFIG_DK_LIBRARY=y
     
     CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    +
    +CONFIG_BOOTLOADER_MCUBOOT=y
    +
    +CONFIG_NVS=y
    +CONFIG_NVS_LOG_LEVEL_DBG=y
    +# Double the size of the settings storage partition,
    +# and allocate the first half of it to BT_SETTINGS.
    +CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE=0x4000
    +CONFIG_SETTINGS_NVS_SECTOR_COUNT=2
    +
    --- /home/vidarbe/ncs/v2.1.1/nrf/samples/bluetooth/peripheral_lbs/src/main.c	2022-10-24 09:14:32.019870394 +0200
    +++ src/main.c	2022-10-31 15:12:25.290963916 +0100
    @@ -26,6 +26,13 @@
     
     #include <dk_buttons_and_leds.h>
     
    +#include <zephyr/drivers/flash.h>
    +#include <zephyr/storage/flash_map.h>
    +#include <zephyr/fs/nvs.h>
    +
    +#define STORAGE_NODE_LABEL 		storage
    +#define RBT_CNT_ID 				3
    +
     #define DEVICE_NAME             CONFIG_BT_DEVICE_NAME
     #define DEVICE_NAME_LEN         (sizeof(DEVICE_NAME) - 1)
     
    @@ -38,6 +45,8 @@
     
     #define USER_BUTTON             DK_BTN1_MSK
     
    +static struct nvs_fs fs;
    +
     static bool app_button_state;
     
     static const struct bt_data ad[] = {
    @@ -181,6 +190,67 @@
     	return err;
     }
     
    +static void nvs_test(void)
    +{
    +	int rc = 0;
    +	struct flash_pages_info info;
    +	uint32_t reboot_counter = 0U;
    +	/* define the nvs file system by settings with:
    +	 *	sector_size equal to the pagesize,
    +	 *	starting at FLASH_AREA_OFFSET(STORAGE_NODE_LABEL)
    +	 */
    +	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;
    +	}
    +
    +	fs.offset = FLASH_AREA_OFFSET(STORAGE_NODE_LABEL);
    +	printk("Storage partition start address 0x%x\n", fs.offset);
    +
    +	rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info);
    +	if (rc) {
    +		printk("Unable to get page info\n");
    +		return;
    +	}
    +
    +	fs.offset += (info.size * CONFIG_SETTINGS_NVS_SECTOR_COUNT);
    +    printk("Offset user data 0x%x\n", fs.offset);
    +
    +	fs.sector_size = info.size;
    +	printk("Sector size 0x%x\n", fs.sector_size);
    +	fs.sector_count = (CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE / info.size) -
    +					CONFIG_SETTINGS_NVS_SECTOR_COUNT;
    +	printk("Sector count 0x%x\n", fs.sector_count);
    +
    +	rc = nvs_mount(&fs);
    +	if (rc) {
    +		printk("Flash Init failed\n");
    +		return;
    +	}
    +
    +	/* RBT_CNT_ID is used to store the reboot counter, lets see
    +	 * if we can read it from flash
    +	 */
    +	rc = nvs_read(&fs, RBT_CNT_ID, &reboot_counter, sizeof(reboot_counter));
    +	if (rc > 0) { /* item was found, show it */
    +		printk("Id: %d, Reboot_counter: %d\n",
    +			RBT_CNT_ID, reboot_counter);
    +	} else   {/* item was not found, add it */
    +		printk("No Reboot counter found, adding it at id %d\n",
    +		       RBT_CNT_ID);
    +		(void)nvs_write(&fs, RBT_CNT_ID, &reboot_counter,
    +			  sizeof(reboot_counter));
    +	}
    +
    +	reboot_counter++;
    +	(void)nvs_write(
    +		&fs, RBT_CNT_ID, &reboot_counter,
    +		sizeof(reboot_counter));
    +
    +
    +}
    +
     void main(void)
     {
     	int blink_status = 0;
    @@ -241,6 +311,8 @@
     
     	printk("Advertising successfully started\n");
     
    +	nvs_test();
    +
     	for (;;) {
     		dk_set_led(RUN_STATUS_LED, (++blink_status) % 2);
     		k_sleep(K_MSEC(RUN_LED_BLINK_INTERVAL));

    Best regards,

    Vidar

Related