nRF9160DK 搭載の外部フラッシュメモリ(MX25R6435F)を使ってみる。


各種データの保存領域とフルモデムアップデートなどに対応できるように、
ファイルシステム(LittleFS)を利用する方法と、ドライバーを直接叩く方法の両方を調べてみた。

ncs のバージョンは v1.9.1 で検証した。

なお、デフォルトで設定されている nRF9160内部の storage_partition (アドレス 0xFA0000 サイズ 24Kbytes) の領域は使用できなくなる。

nRF9160DKの場合、nRF52840側のボードコントロールにて、ハードウエアスイッチを切り替えておく必要がある。

nRF9160DK上のnRF52840用 overlay ファイル抜粋

/ {
board-control {
    external_flash_pins_routing: switch-ext-mem-ctrl {
        compatible = "nordic,nrf9160dk-optional-routing";
        control-gpios = <&gpio0 19 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

nRF9160 の overlay ファイル抜粋

/ {
	chosen {
		nordic,pm-ext-flash = &mx25r64;
	};
};
&spi3 {
	status = "okay";
	sck-pin  = <13>;
	mosi-pin = <11>;
	miso-pin = <12>;
	cs-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
	mx25r64: mx25r64@0 {
		compatible = "jedec,spi-nor";
		reg = <0>;
		spi-max-frequency = <80000000>;
		label = "MX25R64";
		jedec-id = [c2 28 17];
		size = <67108864>;
	};
};

proj.conf 抜粋

CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_MPU_ALLOW_FLASH_WRITE=y
CONFIG_IMG_ERASE_PROGRESSIVELY=y
CONFIG_FILE_SYSTEM=y
CONFIG_FILE_SYSTEM_LITTLEFS=y
CONFIG_SPI_NOR=y
CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096

MX25R6435Fの容量は 8MBytes 。
先頭 7MBytes をドライバーで直接読み書きする領域(大きなファイルのダウンロード用)、
後方 1MByte を LittleFS 用に割り当ててみた。

proj.confなどがあるディレクトリに、pm_static.yml というファイルを以下の内容で作成しておく。

external_flash:
  address: 0x000000
  end_address: 0x6FFFFF
  region: external_flash
  size: 0x700000
littlefs_storage:
  address: 0x700000
  device: MX25R64
  region: external_flash
  size: 0x100000

LittleFS を使用する

ソースコードに下記を追加、

#include <fs/fs.h>
#include <storage/flash_map.h>
#include <fs/littlefs.h>

#define LFS_MAX_PATH_LEN 255
FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage);
static struct fs_mount_t lfs_storage_mnt = {
	.type = FS_LITTLEFS,
	.fs_data = &storage,
	.storage_dev = (void *)FLASH_AREA_ID(littlefs_storage),
	.mnt_point = "/lfs",
};

zephyerのサンプロコードの例(一部変更)

void lfs_test(void)
{
	struct fs_mount_t *mp = &lfs_storage_mnt;
	unsigned int id = (uintptr_t)mp->storage_dev;
	char fname[LFS_MAX_PATH_LEN];
	struct fs_statvfs sbuf;
	const struct flash_area *pfa;
	int rc;

	snprintf(fname, sizeof(fname), "%s/boot_count", mp->mnt_point);

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

	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)) {
		printk("Erasing flash area ... ");
		rc = flash_area_erase(pfa, 0, pfa->fa_size);
		printk("%d\n", rc);
	}

	flash_area_close(pfa);

	rc = fs_mount(mp);
	if (rc < 0) {
		printk("FAIL: mount id %u at %s: %d\n",
		       (unsigned int)mp->storage_dev, mp->mnt_point,
		       rc);
		return;
	}
	printk("%s mount: %d\n", mp->mnt_point, rc);

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

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

	struct fs_dirent dirent;

	rc = fs_stat(fname, &dirent);
	printk("%s stat: %d\n", fname, rc);
	if (rc >= 0) {
		printk("\tfn '%s' siz %u\n", dirent.name, dirent.size);
	}

	struct fs_file_t file;

	fs_file_t_init(&file);

	rc = fs_open(&file, fname, FS_O_CREATE | FS_O_RDWR);
	if (rc < 0) {
		printk("FAIL: open %s: %d\n", fname, rc);
		goto out;
	}

	uint32_t boot_count = 0;

	if (rc >= 0) {
		rc = fs_read(&file, &boot_count, sizeof(boot_count));
		printk("%s read count %u: %d\n", fname, boot_count, rc);
		rc = fs_seek(&file, 0, FS_SEEK_SET);
		printk("%s seek start: %d\n", fname, rc);

	}

	boot_count += 1;
	rc = fs_write(&file, &boot_count, sizeof(boot_count));
	printk("%s write new boot count %u: %d\n", fname,
	       boot_count, rc);

	rc = fs_close(&file);
	printk("%s close: %d\n", fname, rc);

	struct fs_dir_t dir;

	fs_dir_t_init(&dir);

	rc = fs_opendir(&dir, mp->mnt_point);
	printk("%s opendir: %d\n", mp->mnt_point, rc);

	while (rc >= 0) {
		struct fs_dirent ent = { 0 };

		rc = fs_readdir(&dir, &ent);
		if (rc < 0) {
			break;
		}
		if (ent.name[0] == 0) {
			printk("End of files\n");
			break;
		}
		printk("  %c %u %s\n",
		       (ent.type == FS_DIR_ENTRY_FILE) ? 'F' : 'D',
		       ent.size,
		       ent.name);
	}

	(void)fs_closedir(&dir);

out:
	rc = fs_unmount(mp);
	printk("%s unmount: %d\n", mp->mnt_point, rc);
}

ファイルシステムを利用せずにドライバーを直に叩く

ソースコードに下記を追加する。

#include <drivers/flash.h>
#define EXT_FLASH_DEVICE DT_LABEL(DT_INST(0, jedec_spi_nor))
static const struct device *flash_dev;

デバイスは以下で取得できる。

flash_dev = device_get_binding(EXT_FLASH_DEVICE);

読み書きは、 flash_read関数、flash_write関数等で行える。
drivers/flash.h を参照のこと。
モデムファームウエアのフルアップデート用ライブラリは上記関数を利用してデバイスを直接叩いている。

以上