1. Overview
This note describes how to use the SPI Flash.
2. Hardware Implementation
As documented in Emcraft Thingy-9151-Lite Platform Data Sheet , the Thingy-9151-Lite provides a 16MB SPI Flash connected to the nRF9151 MCU.
Relevant hardware implementation details are as follows:
16MB SPI Flash using the TBD device
Connected to the TBD SPI controller of the nRF9151 MCU
TBD pin on the nRF9151 MCU controller used as a CS (Chip Select)
SPI is configured to run at TBD MHz.
3. Software Interfaces
3.1. SPI Flash Device Driver
There is a generic jedec,spi-nor
driver in Zephyr that should be able to handle SPI flash devices compatible with JESD216 JEDEC standard. This driver operates as a middleware between underlying driver for SPI controller and low level flash API. This driver is enabled when SPI_NOR
configuration parameter is enabled. The level of auto-configuration is selected with SPI_NOR_SFDP
parameter:
SPI_NOR_SFDP | |
---|---|
SPI_NOR_SFDP_MINIMAL (default) | Synthesize a minimal configuration assuming 256 By page size and |
SPI_NOR_SFDP_DEVICETREE | The JESD216 Basic Flash Parameters table must be provided in the |
SPI_NOR_SFDP_RUNTIME | Read all flash device characteristics from the device at runtime. |
For the full list of driver parameters available in the device tree, refer to https://docs.zephyrproject.org/latest/build/dts/api/bindings/mtd/jedec%2Cspi-nor.html#std-dtcompatible-jedec-spi-nor
Note, that jedec,spi-nor
driver runs on top of SPI driver, so DSPI and QSPI features are not supported. For some Nordic chips (nRF5340 in particular), there is a nordic,qspi-nor
driver that supports operating in QSPI mode, with its own set of flash parameters. nordic,qspi-nor
is not affected by SPI_NOR_SFDP
, all required parameters need to be supplied in device tree node.
3.2. Software APIs
There are several API levels that allow access to SPI flash.
3.2.1. Low Level Flash Access
This level can be used to access the SPI Flash, as a raw Flash device.
The following calls are the core for the flash API defined in zephyr/include/zephyr/drivers/flash.h
:
__syscall const struct flash_parameters *flash_get_parameters(const struct device *dev); __syscall size_t flash_get_write_block_size(const struct device *dev); /* access functions */ __syscall int flash_erase(const struct device *dev, off_t offset, size_t size); __syscall int flash_write(const struct device *dev, off_t offset, const void *data, size_t len); __syscall int flash_read(const struct device *dev, off_t offset, void *data, size_t len); /* specific to SPI flash, if supported by driver and enabled by CONFIG_FLASH_JESD216_API */ __syscall int flash_read_jedec_id(const struct device *dev, uint8_t *id); __syscall int flash_sfdp_read(const struct device *dev, off_t offset, void *data, size_t len);
There are more to flash API, please refer to Zephyr documentation https://docs.zephyrproject.org/latest/hardware/peripherals/flash.html for details.
There are also zephyr/samples/drivers/jesd216
and zephyr/samples/drivers/spi_flash
demos that demonstrate using low-level API.
3.2.2. Flash Partitioning
Zephyr has API’s that can be used to partition SPI Flash. The goal is to provide facility to split single chip into several named areas to be used independently and preventing out of bounds access.
There is predefined flash map that is created from “fixed-partition” compatible entries in DTS file. Internally each flash area is described with a descriptor, providing offset, length, underlying flash driver. This descriptors are identified by globally unique ID numbers. To obtain ID from device tree label for “fixed-partition” use FIXED_PARTITION_ID()
macro.
It is also possible to create flash partition during runtime.
The following functions are the core of flash area API defined in zephyr/include/zephyr/storage/flash_map.h
int flash_area_open(uint8_t id, const struct flash_area **fa); void flash_area_close(const struct flash_area *fa); int flash_area_erase(const struct flash_area *fa, off_t off, size_t len); int flash_area_write(const struct flash_area *fa, off_t off, const void *src, size_t len); int flash_area_read(const struct flash_area *fa, off_t off, void *dst, size_t len);
Refer to Zephyr documentations https://docs.zephyrproject.org/latest/services/storage/flash_map/flash_map.html for details. Note, that when using multi-image builds, Nordic SDK overrides partitions defined in the device tree with Partition Manager .
zephyr/samples/subsys/fs/littlefs
sample illustrate using fixed partition to create file system over it. mcuboot
is another example.
3.2.3. High-level non-volatile API
There are several high-level storage API provided by Zephyr that can be used over flash(or flash area):
https://docs.zephyrproject.org/latest/services/storage/nvs/nvs.html can be used to store elements, represented as id-data pairs
https://docs.zephyrproject.org/latest/services/storage/fcb/fcb.html provides an abstraction through which you can treat flash like a FIFO.
https://docs.zephyrproject.org/latest/services/storage/stream/stream_flash.html module takes contiguous fragments of a stream of data (e.g. from radio packets), aggregates them into a user-provided buffer, then when the buffer fills (or stream ends) writes it to a raw flash partition.
https://docs.zephyrproject.org/latest/services/file_system/index.html allow to have several independent file systems.
See samples/fs/littlefs
, samples/subsys/usb/mass
for example how to use filesystem over SPI flash.
In the nrf-app
use the CONFIG_PM_PARTITION_REGION_LITTLEFS_EXTERNAL
, CONFIG_PM_PARTITION_REGION_SETTINGS_STORAGE_EXTERNAL
, and CONFIG_PM_PARTITION_REGION_NVS_STORAGE_EXTERNAL
to relocate corresponding partition to SPI flash.
3.3. Zephyr Shell Commands
nrf-app
comes preconfigured to use SPI flash, and support for Zephyr shell commands enabled. The following shell commands are supported:
erase [<device>] <page address> [<size>] read [<device>] <address> [<Dword count>] test [<device>] <address> <size> <repeat count> write [<device>] <address> <dword> [<dword>...] load [<device>] <address> <size> page_info [<device>] <address> read_test [<device>] <address> <size> <repeat count> write_test [<device>] <address> <size> <repeat count> erase_test [<device>] <address> <size> <repeat count> erase_write_test [<device>] <address> <size> <repeat count>
[<device>]
is optional flash device name (as given in the device tree). If not present, value of zephyr,flash
in choosen
node will be used.
When specifying <address>
and <size>
values use 0x
prefix for the hexadecimal values. <size>
for test operations is limited by the CONFIG_FLASH_SHELL_BUFFER_SIZE
and defaults to 0x4000
.
The following procedures can be used to verify raw Flash access. Note, that low-level API is used and partition data can be destroyed by this test:
Run generic erase/write/read/compare test (example for nrf5340).
uart:~$ flash test mx25r6435f@0 0 0x4000 1 Erase OK. Write OK. Verified OK. Erase-Write-Verify test done.
Run read and write measurements (example for nrf5340):
uart:~$ flash erase mx25r6435f@0 0 0x4000 Erase success. uart:~$ flash write_test mx25r6435f@0 0 0x4000 1 Loop #1 done in 192 ticks. Total: 192ms, Per loop: ~192ms, Speed: ~83.3KiBps uart:~$ flash read_test mx25r6435f@0 0 0x4000 1 Loop #1 done in 4ms. Total: 4ms, Per loop: ~4ms, Speed: ~3.9MiBps
3.4. Configuring for different SPI flash device
This chapter explains how to configure different flash device using gd25wb256
existing on nrf9161DK.
3.4.1. Get JEDEC info
First step when setting new SPI flash is to obtain information about it. To do so, we need to create minimal required description of the flash. Let’s start with the following overlay:
&arduino_spi { newspi: newdevice@1 { compatible = "jedec,spi-nor"; status = "ok"; reg = <1>; spi-max-frequency = <8000000>; jedec-id = [c8 65 19]; size = <0x400000>; }; }; / { aliases { spi-flash0 = &newspi; }; };
We’re adding new device newdevice@1
at address 1
(match CS used for the chip) and create label to refer to it newspi
. compatible
and status
fields select driver and mark device as present. reg
field specifies CS for our SPI device. spi-max-frequency
, jedec-id
and size
(in bits!) can easily be obtained from SPI flash datasheet. spi-max-frequency
may be lower than maximum value specified in datasheet and size
does not have to be correct at this point, just be correctly aligned, so driver does not refuse configuration.
Now go to zephyr/samples/drivers/jesd216
, put the fragment above into boards/nrf9161dk_nrf9161.overlay
and build sample using west
:
$ cd zephyr/samples/drivers/jesd216 $ cat >boards/nrf9161dk_nrf9161.overlay <<EOF &arduino_spi { newspi: newdevice@1 { compatible = "jedec,spi-nor"; status = "ok"; reg = <1>; spi-max-frequency = <8000000>; jedec-id = [c8 65 19]; size = <0x400000>; }; }; / { aliases { spi-flash0 = &newspi; }; }; EOF $ west build -b nrf9161dk_nrf9161 -d build_an -p
If the build finish successfully, there will be build_an/zephyr/zephyr.hex
that can be installed and run on target. Here’s example output after running:
*** Booting nRF Connect SDK v2.5.1-7-g76a2a1188200 *** newdevice@1: SFDP v 1.6 AP ff with 3 PH PH0: ff00 rev 1.6: 16 DW @ 30 Summary of BFP content: DTR Clocking not supported Addressing: 3- or 4-Byte 4-KiBy erase: uniform Support QSPI XIP Support 1-1-1 Support 1-1-2: instr 3Bh, 0 mode clocks, 8 waits Support 1-1-4: instr 6Bh, 0 mode clocks, 8 waits Support 1-2-2: instr BBh, 2 mode clocks, 2 waits Support 1-4-4: instr EBh, 2 mode clocks, 4 waits Flash density: 33554432 bytes ET1: instr 20h for 4096 By; typ 80 ms, max 800 ms ET2: instr 52h for 32768 By; typ 256 ms, max 2560 ms ET3: instr D8h for 65536 By; typ 304 ms, max 3040 ms Chip erase: typ 60928 ms, max 487424 ms Byte program: type 80 + 5 * B us, max 640 + 40 * B us Page program: typ 512 us, max 4096 us Page size: 256 By Suspend: 75h ; Resume: 7Ah DPD: Enter B9h, exit ABh ; delay 40000 ns ; poll 0x01 HOLD or RESET Disable: unsupported QER: 7 0-4-4 Mode methods: entry 0x4 ; exit 0x01 4-4-4 Mode sequences: enable 0x00 ; disable 0x0 4-byte addressing support: enter 0x01, exit 0x001 Soft Reset and Rescue Sequence support: 0x10 Status Register 1 support: 0x08 size = <268435456>; sfdp-bfp = [ e5 20 f3 ff ff ff ff 0f 44 eb 08 6b 08 3b 42 bb ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52 10 d8 00 ff 44 7a c9 fe 83 67 26 62 ec 82 18 44 7a 75 7a 75 04 c4 d5 5c 00 06 74 00 08 50 00 01 ]; PH1: ffc8 rev 1.0: 3 DW @ 90 sfdp-ffc8 = [ 00 36 50 16 9d f9 77 64 fc cb ff ff ]; PH2: ff84 rev 1.0: 2 DW @ c0 sfdp-ff84 = [ ff 0e f0 ff 21 5c dc ff ]; jedec-id = [c8 65 19];
Now, we can replace size
line in our flash descriptor in the overlay with value obtained from SFDP by this sample. We can also copy sfdp-bfp
value from output: it may be needed if SPI_NOR_SFDP_DEVICETREE
is used in the configuration. As line starting with DPD:
indicates, hardware supports DPD using B9h
and ABh
commands and time to wake up is 40000 ns
. Unfortunately time to enter DPD can only be obtained from datasheet (usually tDP). Add has-dpd
, t-enter-dpd
and t-exit-dpd
. Now we can finalize flash description in the overlay:
/* Deactivate predefined flash node for SPI flash installed on DK */ &gd25wb256 { status = "disable"; }; &arduino_spi { /* Insert node for new device */ newspi: newdevice@1 { compatible = "jedec,spi-nor"; status = "ok"; reg = <1>; spi-max-frequency = <8000000>; jedec-id = [c8 65 19]; size = <268435456>; has-dpd; t-enter-dpd = <3000>; t-exit-dpd = <40000>; sfdp-bfp = [ e5 20 f3 ff ff ff ff 0f 44 eb 08 6b 08 3b 42 bb ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52 10 d8 00 ff 44 7a c9 fe 83 67 26 62 ec 82 18 44 7a 75 7a 75 04 c4 d5 5c 00 06 74 00 08 50 00 01 ]; }; }; /* Use new device in choosen and aliases */ / { chosen { nordic,pm-ext-flash = &newspi; }; aliases { spi-flash0 = &newspi; ext-flash = &newspi; }; };
3.4.2. Configuring for different SPI flash device with QSPI support (nordic,qspi-nor
)
Following the similar procedure to SPI flash discussed above, jedec216
provides the following output:
*** Booting nRF Connect SDK v3.5.99-ncs1-1-8-g04e7e56b7fed *** mx25r6435f@0: SFDP v 1.6 AP ff with 3 PH PH0: ff00 rev 1.6: 16 DW @ 30 Summary of BFP content: DTR Clocking not supported Addressing: 3-Byte only 4-KiBy erase: uniform Support QSPI XIP Support 1-1-1 Support 1-1-2: instr 3Bh, 0 mode clocks, 8 waits Support 1-1-4: instr 6Bh, 0 mode clocks, 8 waits Support 1-2-2: instr BBh, 0 mode clocks, 4 waits Support 1-4-4: instr EBh, 2 mode clocks, 4 waits Flash density: 8388608 bytes ET1: instr 20h for 4096 By; typ 48 ms, max 384 ms ET2: instr 52h for 32768 By; typ 240 ms, max 1920 ms ET3: instr D8h for 65536 By; typ 480 ms, max 3840 ms Chip erase: typ 52000 ms, max 312000 ms Byte program: type 32 + 1 * B us, max 192 + 6 * B us Page program: typ 896 us, max 5376 us Page size: 256 By Suspend: B0h ; Resume: 30h DPD: Enter B9h, exit ABh ; delay 40000 ns ; poll 0x3d HOLD or RESET Disable: unsupported QER: 2 0-4-4 Mode methods: entry 0x9 ; exit 0x2f 4-4-4 Mode sequences: enable 0x00 ; disable 0x0 Soft Reset and Rescue Sequence support: 0x10 Status Register 1 support: 0x70 size = <67108864>; 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 48 44 30 b0 30 b0 f7 c4 d5 5c 00 be 29 ff f0 d0 ff ff ]; PH1: ffc2 rev 1.0: 4 DW @ 110 sfdp-ffc2 = [ 00 36 50 16 9d f9 c0 64 fe cf ff ff ff ff ff ff ]; PH2: ff84 rev 1.0: 2 DW @ c0 sfdp-ff84 = [ 00 00 f0 ff ff ff ff ff ]; jedec-id = [c2 28 17];
spi-max-frequency
, jedec-id
, sfdp-bfp
, size
, has-dpd
, t-enter-dpd
and t-exit-dpd
are determined similarly to SPI case. However nordic,qspi-nor
requires additional parameters to enable quad mode:
quad-enable-requirements
defines method to enable quad mode and can be determined from line starting withQER:
in the output
|
|
---|---|
0 |
|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
readoc
andwriteoc
define command and mode used during quad access:
Mode | Write instruction | Read Instruction |
|
|
---|---|---|---|---|
1-1-1 | 0x02 | 0x0B |
|
|
1-1-2 | 0xA2 | 0x3B |
|
|
1-2-2 | 0xBB |
| ||
1-1-4 | 0x32 | 0x6B |
|
|
1-4-4 | 0x38 | 0xEB |
|
|
For both parameters yellow lines indicate values not supported by nordic,qspi-nor
.
For the device used in this test, both 1-1-4 and 1-4-4 modes are available, but 1-4-4 is slightly faster, so it will be selected. Final device tree entry:
mx25r64: mx25r6435f@0 { compatible = "nordic,qspi-nor"; reg = < 0x0 >; writeoc = "pp4io"; readoc = "read4io"; quad-enable-requirements = "S1B6"; 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 = < 0x4000000 >; has-dpd; t-enter-dpd = < 10000 >; t-exit-dpd = < 35000 >; };