Using SPI Flash
1. Overview
This note describes how to use the SPI Flash with the Thingy-9151-Lite platform.
2. Hardware Implementation
As documented in Emcraft Thingy-9151-Lite Platform Data Sheet , the Thingy-9151-Lite provides a 32MB SPI Flash connected to the nRF9151 MCU.
Relevant hardware implementation details are as follows:
32MB SPI Flash using the GD25WB256E device
Connected to the SPI3 controller of the nRF9151 MCU
P0.20 pin on the nRF9151 MCU controller used as a CS (Chip Select)
SPI is configured to run at 8 MHz.
3. Software Interfaces
3.1. SPI Flash Device Driver
There is a generic jedec,spi-nor
driver in Zephyr that is used to handle SPI Flash devices compatible with the JESD216 JEDEC standard. This driver operates as a middleware between an underlying driver for the SoC-specific SPI controller and the low-level Flash API.
The driver is available when the SPI_NOR
configuration parameter is enabled in the build-configuration. The level of auto-configuration is selected with the SPI_NOR_SFDP
parameter:
SPI_NOR_SFDP |
|
---|---|
| Synthesise a minimal configuration assuming 256 By page size and standard 4 KiBy and 64 KiBy erase instructions. Requires the |
| The JESD216 Basic Flash Parameters table must be provided in the |
| Read all flash device characteristics from the device at runtime. This option is the most flexible as it should provide functionality for all supported JESD216-compatible devices. |
For the full list of driver parameters available in the device tree, refer to jedec,spi-nor (on spi bus) — Zephyr Project Documentation
3.2. Support for QSPI Flash
Note that the jedec,spi-nor
driver runs on top of the SPI driver, so the DSPI and QSPI features are not supported by this solution. 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 the device tree node.
3.3. Software APIs
There are several API levels that allow access to SPI Flash.
3.3.1. Low Level Flash Access
This level can be used to access 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 the Flash API’s, please refer to Zephyr documentation Flash — Zephyr Project Documentation for details.
There are also the zephyr/samples/drivers/jesd216
and zephyr/samples/drivers/spi_flash
demos that demonstrate use of low-level API.
3.3.2. Flash Partitioning
Zephyr additionally has API’s that can be used to partition SPI Flash. The goal is to provide a facility to split a single Flash device into several named areas to be used independently and preventing out-of-bounds access.
There is a predefined Flash map that is created from the “fixed-partition” compatible entries in the DTS file. Internally, each Flash area is described with a descriptor, providing: offset, length, underlying flash driver. Those descriptors are identified by globally unique ID numbers. To obtain the ID from the device tree label for “fixed-partition” use the FIXED_PARTITION_ID()
macro.
It is also possible to create Flash partitions at the 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 Flash map — Zephyr Project Documentation for details. Note that when using multi-image builds, the Nordic SDK overrides the partitions defined in the device tree with Partition Manager .
The zephyr/samples/subsys/fs/littlefs
sample illustrates use of a fixed partition to create a file system over it. mcuboot
is another useful example.
3.3.3. High-Level Non-Volatile API
There are several high-level storage API’s provided by Zephyr that can be used over Flash (or a Flash area):
Non-Volatile Storage (NVS) — Zephyr Project Documentation can be used to store elements, represented as id-data pairs.
Flash Circular Buffer (FCB) — Zephyr Project Documentation provides an abstraction through which you can treat Flash like a FIFO.
Stream Flash — Zephyr Project Documentation 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.
File Systems — Zephyr Project Documentation allow to have several independent file systems.
See samples/fs/littlefs
, samples/subsys/usb/mass
for an example of how to use filesystem over SPI Flash.
In the nrf-app
application 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.4. Zephyr Shell Commands
The nrf-app
application comes preconfigured to use SPI Flash, with support for the relevant Zephyr shell commands enabled. The following shell commands are available:
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>
In the above, [<device>]
is an optional Flash device name (as given in the device tree). If not present, value of zephyr,flash
in the choosen
node will be used.
When specifying the <address>
and <size>
values, use an 0x
prefix for hexadecimal values. <size>
for test operations is limited by the CONFIG_FLASH_SHELL_BUFFER_SIZE
parameter and defaults to 0x4000
.
The following procedures can be used to verify raw Flash access. Note, that the low-level API is used in the example, which can destroy any available partition data:
Run a generic erase/write/read/compare test:
Run read and write measurements:
4. Configuring for a Different SPI Flash Device
This chapter explains how to configure a different Flash device in the Zephyr BSP. Specifically, the GD25WB256E
Flash device installed on the Thingy-9151-Lite platform is used as an example.
4.1. Obtaining JEDEC Info
First step when setting a new SPI Flash is to obtain information about it. To do so, we need to create a minimally required description of the Flash. Let’s start with the following overlay:
In the above, we are adding a new device newdevice@1
at address 1
(matching the CS used for the chip) and creating a label to refer to it as newspi
. The compatible
and status
fields select the driver and mark the device as present. The reg
field specifies the CS for our SPI device. spi-max-frequency
, jedec-id
and size
(in bits!) need to be obtained from the SPI Flash data sheet. spi-max-frequency
may be lower than the maximum value specified in the data sheet. size
does not have to be correct at this point, just to be correctly aligned, so the driver does not refuse the configuration.
As a next step, go to zephyr/samples/drivers/jesd216
, put the fragment above into boards/nrf9161dk_nrf9161.overlay
and build the sample using west
:
If the build finishes successfully, there will be a build_an/zephyr/zephyr.hex
file that can be run on the target. Here is some example output when running this demo:
Now, we can replace the size
line in our Flash descriptor in the overlay with the value obtained from the SFDP by this sample. We can also copy the sfdp-bfp
value from the output: it may be needed if SPI_NOR_SFDP_DEVICETREE
is used in the configuration. As the line starting with DPD:
indicates, the hardware supports DPD using the B9h
and ABh
commands and the time to wake up is 40000 ns
. The time to enter DPD can only be obtained from the data sheet (usually, it is referred to as tDP
). Add has-dpd
, t-enter-dpd
and t-exit-dpd
. Now, we can finalise the Flash description in the overlay:
4.2. Configuring for Different SPI Flash Device with QSPI support on nRF5340
The nRF5340 MCU includes a QSPI controller that allows to use faster Flash devices in dual and quad SPI mode.
Following the procedure for an SPI Flash discussed above, jedec216
provides the following output:
spi-max-frequency
, jedec-id
, sfdp-bfp
, size
, has-dpd
, t-enter-dpd
and t-exit-dpd
are determined similarly to the SPI case. However nordic,qspi-nor
requires additional parameters to enable the quad mode:
quad-enable-requirements
defines a method to enable the quad mode and can be determined from the line starting withQER:
in the output
|
|
---|---|
0 |
|
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
readoc
andwriteoc
define the command and the mode used for the 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 the both parameters above, the yellow lines indicate the values not supported by nordic,qspi-nor
.
For the device used in this test, both the 1-1-4
and 1-4-4
modes are available, with 1-4-4
being slightly faster, so it will be selected. The final device tree entry is as follows: