I am not able to install littlefs on an external qspi flash

Hello

I'm new with zephyr environment and I want to use littlefs in order to partition an external flash over spi, and to be able to upload file over BLE by using a SMP server.

I know this topic comes up regularly. I downloaded the littlefs sample project for inspiration. Here's the error I'm getting when building :

main.c:111: undefined reference to `z_fsmp_DT_N_S_soc_S_qspi_40029000_S_mx25r6435f_0_S_partitions_S_partition_0'

I guess I misconfigured something but I can't figure out what.

My environment : 

- board : nrf52840dk

- SDK v2.9.0

- Toolchain : v2.9.1

Here is my app.overlay : 

 / {
     chosen {
         nordic,pm-ext-flash = &mx25r64;
     };

     fstab {
         compatible = "zephyr,fstab";
         lfs1: lfs1 {
             compatible = "zephyr,fstab,littlefs";
             mount-point = "/lfs1";
             partition = <&lfs1_partition>;
             /*automount;*/
             read-size = <16>;
             prog-size = <16>;
             cache-size = <64>;
             lookahead-size = <32>;
             block-cycles = <512>;
         };
     };
 };

 &mx25r64 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;

        lfs1_partition: partition@0 {
            label = "lfs1";
            reg = <0x00000000 0x00010000>;
        };
    };
};

Here is my custom dts file  : 

/dts-v1/;
#include <nordic/nrf52840_qfaa.dtsi>
#include "ngtble-pinctrl.dtsi"

/ {
	model = "Nordic NGT BLE NRF52840";
	compatible = "teledyne,ngtble";

	chosen {
		/*zephyr,console = &uart0;
		zephyr,shell-uart = &uart0;
		zephyr,uart-mcumgr = &uart0;
		zephyr,bt-mon-uart = &uart0;
		zephyr,bt-c2h-uart = &uart0;*/
		zephyr,sram = &sram0;
		zephyr,flash = &flash0;
		zephyr,code-partition = &slot0_partition;
		zephyr,ieee802154 = &ieee802154;
	};

	reserved-memory {
		#address-cells = <1>;
		#size-cells = <1>;
		ranges;
	};
};

&gpiote {
	status = "okay";
};

&gpio0 {
	status = "okay";
};

&gpio1 {
	status = "okay";
};

&uart1 {
	compatible = "nordic,nrf-uarte";
	status = "okay";
	current-speed = <9600>;
	pinctrl-0 = <&uart1_default>;
	pinctrl-1 = <&uart1_sleep>;
	pinctrl-names = "default", "sleep";

	modbus0 {
		compatible = "zephyr,modbus-serial";
		status = "okay";
	};
};

&flash0 {

	partitions {
		compatible = "fixed-partitions";
		#address-cells = <1>;
		#size-cells = <1>;

		boot_partition: partition@0 {
			label = "mcuboot";
			reg = <0x00000000 DT_SIZE_K(48)>;
		};

		slot0_partition: partition@c000 {
			label = "image-0";
			reg = <0x0000c000 DT_SIZE_K(472)>;
		};

		slot1_partition: partition@82000 {
			label = "image-1";
			reg = <0x00082000 DT_SIZE_K(472)>;
		};

		storage_partition: partition@f8000 {
			label = "storage";
			reg = <0x000f8000 DT_SIZE_K(32)>;
		};
	};
};

&spi0 {
	compatible = "nordic,nrf-spi";
	/* Cannot be used together with i2c0. */
	/* status = "okay"; */
	pinctrl-0 = <&spi0_default>;
	pinctrl-1 = <&spi0_sleep>;
	pinctrl-names = "default", "sleep";
};

&spi1 {
	compatible = "nordic,nrf-spi";
	status = "okay";
	pinctrl-0 = <&spi1_default>;
	pinctrl-1 = <&spi1_sleep>;
	pinctrl-names = "default", "sleep";
};

&spi2 {
	compatible = "nordic,nrf-spi";
	status = "disabled";
	pinctrl-0 = <&spi2_default>;
	pinctrl-1 = <&spi2_sleep>;
	pinctrl-names = "default", "sleep";
};

&qspi {
	status = "okay";
	pinctrl-0 = <&qspi_default>;
	pinctrl-1 = <&qspi_sleep>;
	pinctrl-names = "default", "sleep";
	mx25r64: mx25r6435f@0 {
		compatible = "nordic,qspi-nor";
		reg = <0>;
		/* MX25R64 supports only pp and pp4io */
		writeoc = "pp4io";
		/* MX25R64 supports all readoc options */
		readoc = "read4io";
		sck-frequency = <8000000>;
		jedec-id = [c2 28 17];
		sfdp-bfp = [
			e5 20 f1 ff  ff ff ff 03  44 eb 08 6b  08 3b 04 bb
			ee ff ff ff  ff ff 00 ff  ff ff 00 ff  0c 20 0f 52
			10 d8 00 ff  23 72 f5 00  82 ed 04 cc  44 83 68 44
			30 b0 30 b0  f7 c4 d5 5c  00 be 29 ff  f0 d0 ff ff
		];
		size = <67108864>;
		has-dpd;
		t-enter-dpd = <10000>;
		t-exit-dpd = <35000>;
        partitions {
            compatible = "fixed-partitions";
            #address-cells = <1>;
            #size-cells = <1>;
			lfs1_partition: partition@0 {
				label = "lfs1";
				reg = <0x00000000 0x00010000>;
			};
		};
	};
};

my pm_static.yml : 

app:
  address: 0xc200
  end_address: 0x85000
  region: flash_primary
  size: 0x78e00
mcuboot:
  address: 0x0
  end_address: 0xc000
  placement:
    align:
      end: 0x1000
    before:
    - mcuboot_primary
  region: flash_primary
  size: 0xc000
mcuboot_pad:
  address: 0xc000
  end_address: 0xc200
  placement:
    align:
      start: 0x1000
    before:
    - mcuboot_primary_app
  region: flash_primary
  size: 0x200
mcuboot_primary:
  address: 0xc000
  end_address: 0x85000
  orig_span: &id001
  - mcuboot_pad
  - app
  region: flash_primary
  sharers: 0x1
  size: 0x79000
  span: *id001
mcuboot_primary_app:
  address: 0xc200
  end_address: 0x85000
  orig_span: &id002
  - app
  region: flash_primary
  size: 0x78e00
  span: *id002
mcuboot_secondary:
  address: 0x85000
  end_address: 0xfe000
  placement:
    after:
    - mcuboot_primary
    align:
      start: 0x1000
  region: flash_primary
  share_size:
  - mcuboot_primary
  size: 0x79000
settings_storage:
  address: 0xfe000
  end_address: 0x100000
  placement:
    align:
      start: 0x1000
    before:
    - end
  region: flash_primary
  size: 0x2000
sram_primary:
  address: 0x20000000
  end_address: 0x20040000
  region: sram_primary
  size: 0x40000
littlefs_storage:
  address: 0x0
  device: mx25r6435f
  end_address: 0x800000
  region: external_flash
  size: 0x800000

my prj.conf file : 

#
# Copyright 2024 DPEdesign
#
# All rights reserved
#

CONFIG_NCS_SAMPLES_DEFAULTS=y
CONFIG_DK_LIBRARY=y
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048

CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_UART_CONSOLE=n

CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="FREEDOM"
CONFIG_BT_MAX_CONN=2
CONFIG_BT_SMP=y
CONFIG_BT_MAX_PAIRED=8

CONFIG_BT_GATT_AUTO_SEC_REQ=y
CONFIG_BT_FIXED_PASSKEY=y

CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y

# Enable bonding
CONFIG_BT_SETTINGS=y
CONFIG_FLASH=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_RUNTIME=y

#Enable MCUBOOT bootloader build in the application
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="priv.pem"
#Include MCUMGR and the dependencies in the build
CONFIG_NCS_SAMPLE_MCUMGR_BT_OTA_DFU=y
#CONFIG_MCUMGR_TRANSPORT_BT_AUTHEN=y

CONFIG_BT_EXT_ADV=y
CONFIG_BT_EXT_ADV_MAX_ADV_SET=2
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=100

CONFIG_MODBUS=y
CONFIG_MODBUS_ROLE_CLIENT=y

CONFIG_PARTITION_MANAGER_ENABLED=y

CONFIG_FILE_SYSTEM=y
CONFIG_FILE_SYSTEM_LITTLEFS=y

# Enable binary descriptors
#CONFIG_BINDESC=y

# Enable definition of binary descriptors
#CONFIG_BINDESC_DEFINE=y

# Enable default build time binary descriptors
#CONFIG_BINDESC_DEFINE_BUILD_TIME=y
#CONFIG_BINDESC_BUILD_DATE_TIME_STRING=y

#############################################

# Enable file system commands
CONFIG_MCUMGR_GRP_FS=y
CONFIG_MCUMGR_GRP_FS_DL_CHUNK_SIZE=200

# Enable the storage erase command.
CONFIG_MCUMGR_GRP_ZBASIC=y
CONFIG_MCUMGR_GRP_ZBASIC_STORAGE_ERASE=y
# Optionally force the file system to be recreated
#CONFIG_APP_WIPE_STORAGE=y
# fs_dirent structures are big.
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_BT_RX_STACK_SIZE=4096
CONFIG_BT_HCI_TX_STACK_SIZE=4096
# Let __ASSERT do its job
CONFIG_DEBUG=y

CONFIG_INIT_STACKS=y
CONFIG_THREAD_STACK_INFO=y
CONFIG_STACK_SENTINEL=y
CONFIG_THREAD_MONITOR=y

CONFIG_PM_SINGLE_IMAGE=y  # ou CONFIG_PM_MULTI_IMAGE si tu utilises MCUboot
CONFIG_PM_OVERRIDE_EXTERNAL_DRIVER_CHECK=y

CONFIG_PM_PARTITION_REGION_LITTLEFS_EXTERNAL=y
#CONFIG_PM_PARTITION_REGION_SETTINGS_STORAGE_EXTERNAL=y
#CONFIG_PM_PARTITION_REGION_NVS_STORAGE_EXTERNAL=y

CONFIG_SPI=n
#CONFIG_SPI_NOR=y
#CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
CONFIG_NORDIC_QSPI_NOR=y
CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096

#############################################

and here is the main.c file : 

#include <stdio.h>

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/fs/fs.h>
#include <zephyr/fs/littlefs.h>
#include <zephyr/logging/log.h>
#include <zephyr/storage/flash_map.h>
#include "ble_mgt.h"

#define CONFIG_APP_LITTLEFS_STORAGE_FLASH

LOG_MODULE_REGISTER(main);

/* Matches LFS_NAME_MAX */
#define MAX_PATH_LEN 255
#define TEST_FILE_SIZE 547

static int lsdir(const char *path)
{
	int res;
	struct fs_dir_t dirp;
	static struct fs_dirent entry;

	fs_dir_t_init(&dirp);

	/* Verify fs_opendir() */
	res = fs_opendir(&dirp, path);
	if (res) {
		LOG_ERR("Error opening dir %s [%d]\n", path, res);
		return res;
	}

	LOG_PRINTK("\nListing dir %s ...\n", path);
	for (;;) {
		/* Verify fs_readdir() */
		res = fs_readdir(&dirp, &entry);

		/* entry.name[0] == 0 means end-of-dir */
		if (res || entry.name[0] == 0) {
			if (res < 0) {
				LOG_ERR("Error reading dir [%d]\n", res);
			}
			break;
		}

		if (entry.type == FS_DIR_ENTRY_DIR) {
			LOG_PRINTK("[DIR ] %s\n", entry.name);
		} else {
			LOG_PRINTK("[FILE] %s (size = %zu)\n",
				   entry.name, entry.size);
		}
	}

	/* Verify fs_closedir() */
	fs_closedir(&dirp);

	return res;
}


#ifdef CONFIG_APP_LITTLEFS_STORAGE_FLASH
static int littlefs_flash_erase(unsigned int id)
{
	const struct flash_area *pfa;
	int rc;

	rc = flash_area_open(id, &pfa);
	if (rc < 0) {
		LOG_ERR("FAIL: unable to find flash area %u: %d\n",
			id, rc);
		return rc;
	}

	LOG_PRINTK("Area %u at 0x%x on %s for %u bytes\n",
		   id, (unsigned int)pfa->fa_off, pfa->fa_dev->name,
		   (unsigned int)pfa->fa_size);

	/* Optional wipe flash contents */
	if (IS_ENABLED(CONFIG_APP_WIPE_STORAGE)) {
		rc = flash_area_flatten(pfa, 0, pfa->fa_size);
		LOG_ERR("Erasing flash area ... %d", rc);
	}

	flash_area_close(pfa);
	return rc;
}
#define PARTITION_NODE DT_NODELABEL(lfs1)

#if DT_NODE_EXISTS(PARTITION_NODE)
FS_FSTAB_DECLARE_ENTRY(PARTITION_NODE);
#else /* PARTITION_NODE */
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage);
static struct fs_mount_t lfs_storage_mnt = {
	.type = FS_LITTLEFS,
	.fs_data = &storage,
	.storage_dev = (void *)FIXED_PARTITION_ID(storage_partition),
	.mnt_point = "/lfs1",
};
#endif /* PARTITION_NODE */

	struct fs_mount_t *mountpoint =
#if DT_NODE_EXISTS(PARTITION_NODE)
		&FS_FSTAB_ENTRY(PARTITION_NODE)
#else
		&lfs_storage_mnt
#endif
		;

static int littlefs_mount(struct fs_mount_t *mp)
{
	int rc;

	rc = littlefs_flash_erase((uintptr_t)mp->storage_dev);
	if (rc < 0) {
		return rc;
	}

	/* Do not mount if auto-mount has been enabled */
#if !DT_NODE_EXISTS(PARTITION_NODE) ||						\
	!(FSTAB_ENTRY_DT_MOUNT_FLAGS(PARTITION_NODE) & FS_MOUNT_FLAG_AUTOMOUNT)
	rc = fs_mount(mp);
	if (rc < 0) {
		LOG_PRINTK("FAIL: mount id %" PRIuPTR " at %s: %d\n",
		       (uintptr_t)mp->storage_dev, mp->mnt_point, rc);
		return rc;
	}
	LOG_PRINTK("%s mount: %d\n", mp->mnt_point, rc);
#else
	LOG_PRINTK("%s automounted\n", mp->mnt_point);
#endif

	return 0;
}
#endif /* CONFIG_APP_LITTLEFS_STORAGE_FLASH */

void show_stack_usage(struct k_thread *thread, void *user_data) {
    size_t unused = 0;
    if (k_thread_stack_space_get(thread, &unused) == 0) {
        printk("Thread %s → stack libre : %zu octets\n", k_thread_name_get(thread), unused);
    }
}

int main(void)
{
	struct fs_statvfs sbuf;
	int rc;

	LOG_PRINTK("Sample program to r/w files on littlefs\n");

	ble_init();
	rc = littlefs_mount(mountpoint);
	if (rc < 0) {
		return 0;
	}

	rc = fs_statvfs(mountpoint->mnt_point, &sbuf);
	if (rc < 0) {
		LOG_PRINTK("FAIL: statvfs: %d\n", rc);
		goto out;
	}

	LOG_PRINTK("%s: bsize = %lu ; frsize = %lu ;"
		   " blocks = %lu ; bfree = %lu\n",
		   mountpoint->mnt_point,
		   sbuf.f_bsize, sbuf.f_frsize,
		   sbuf.f_blocks, sbuf.f_bfree);

	rc = lsdir(mountpoint->mnt_point);
	if (rc < 0) {
		LOG_PRINTK("FAIL: lsdir %s: %d\n", mountpoint->mnt_point, rc);
		goto out;
	}

out:
	//rc = fs_unmount(mountpoint);
	//LOG_PRINTK("%s unmount: %d\n", mountpoint->mnt_point, rc);
	//return 0;

	for (;;) {
		//dk_set_led(CON_STATUS_LED, (++blink_status) % 2);
		k_sleep(K_MSEC(1000));		
		//k_thread_foreach(show_stack_usage, NULL);
		//printk("\r\n");
	}
}

If you have any suggestion, feel free to answer.

Thank you in advance,

Best regards,

Aurele

Parents
  • Hello

    I managed to get it to work, but for it to work, I have to force a file upload with the "Device Manager" app, and when I reset the device, I see:

    Area 0 at 0x0 on mx25r6435f@0 for 8388608 bytes
    I: LittleFS version 2.9, disk version 2.1
    I: FS at mx25r6435f@0:0x0 is 2048 0x1000-byte blocks with 512 cycles
    I: sizes: rd 16 ; pr 16 ; ca 64 ; la 32
    /lfs1 mount: 0
    /lfs1: bsize = 16 ; frsize = 4096 ; blocks = 2048 ; bfree = 2044
    
    Listing dir /lfs1 ...
    [FILE] Lorem_10.txt (size = 10)
    [FILE] Lorem_1000.txt (size = 1000)
    [FILE] boot_count (size = 1)
    [FILE] pattern.bin (size = 547)

    Otherwise, as soon as I boot for the first time or after an erase, I see the following error:

    E: FAIL: unable to find flash area 0: -19

  • Hello,

    Are you not seeing any error messages from the QSPI driver too (nrf_qpi_nor.c)? The message E: FAIL: unable to find flash area 0: -19 (where -19 corresponds to -ENODEV) indicates that the mx25r6435f device was not initialized. This issue should be unrelated to whether the SPI flash is empty or not. 

    Best regards,

    Vidar

  • Hello Aurélien,

    I guess the driver cannot read JEDEC ID : 

    Yes, for some reason the SPI flash didn't respond to the device ID read command. This causes the qspi driver init to fail and the subsequent -ENODEV error when the app tries to mount the filesystem. I've seen similar issues on custom boards where the SPI flash was powered on too late and wasn't ready by the time the QSPI initialization function was called, but not on the nRF52840 DK board. 

    I'm starting with an erase after each reboot. (

    Does this seem to mitigate the problem with the qspi flash driver initialization? I don't see how it could be related.  

    If possible, please provide a minimal version of your project that will allow me to reproduce the problem here.

    Best regards,

    Vidar

  • Yes, for some reason the SPI flash didn't respond to the device ID read command. This causes the qspi driver init to fail and the subsequent -ENODEV error when the app tries to mount the filesystem. I've seen similar issues on custom boards where the SPI flash was powered on too late and wasn't ready by the time the QSPI initialization function was called, but not on the nRF52840 DK board. 

    I m working with nrf52840DK v3.0.3 (even if i'm using a custom device tree definition board)

    Does this seem to mitigate the problem with the qspi flash driver initialization? I don't see how it could be related.  

    Actually, i have noticed that the erase could be very very long, and when I reset the device during an erase, the issue occurs !!! So, I think everything is related to this erase that does not have time to finised before the reset ...

    If possible, please provide a minimal version of your project that will allow me to reproduce the problem here.

    Sure, I will share it. Do I have to share it here, in this tread or do you have an email ? 

    Best regards,

  • Aurele said:
    Actually, i have noticed that the erase could be very very long, and when I reset the device during an erase, the issue occurs !!! So, I think everything is related to this erase that does not have time to finised before the reset ...

    I see. So you only experience the issue if you have a sudden reset during the erase operation. Maybe the SPI flash is getting into a bad state. Are you able to reproduce the same if you do a power cycle instead of resetting only the nRF? 

  • Hello Vidar

    That's a good point. You are right, I cannot reproduce the same issue when I make a power cyle reset. The issue only occurs when I make a pin reset, so it means that the SPI stays in a bad stat. I guess I should start with a reset of the external flash (mx25) but how ? Is it possible to drive the reset pin of MX25 independantly as it is already used by the QSPI driver ? 

    Regards

  • Thanks for confirming. So maybe the mx25 is just busy erasing flash pages while the nRF is rebooting, and therefore isn't able to respond to the read ID command. To confirm this, maybe you can try to call sys_reboot(0) when you receive the -ENODEV error in the application. The idea is that this will allow the device to recover, as the flash erase will be completed on the following startup.

Reply
  • Thanks for confirming. So maybe the mx25 is just busy erasing flash pages while the nRF is rebooting, and therefore isn't able to respond to the read ID command. To confirm this, maybe you can try to call sys_reboot(0) when you receive the -ENODEV error in the application. The idea is that this will allow the device to recover, as the flash erase will be completed on the following startup.

Children
No Data
Related