Post

ENG | Zephyr RTOS: SPI Flash

Battle with overlay and prj.conf files continues.

ENG | Zephyr RTOS: SPI Flash

This article explores SPI flash configuration in Zephyr RTOS, a task that should take 30 minutes but may as well ruin your entire weekend.

If you’re here, you’ve probably also stared at error messages like undefined reference to __device_dts_ord_34 and wondered if you should have become a garbage collector instead. Welcome to the club.

Internal flash on XIAO nRF52840

Surprise. XIAO nRF52840 has internal flash, you can take zephyr/samples/drivers/spi_flash and it will work. Good starting point. Here is console output of slightly modified example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Connected to MicroPython at COM11
Use Ctrl-] or Ctrl-x to exit this shell
*** Booting Zephyr OS build v4.1.0-5418-g98ba754013b0 ***
Flash size: 2097152 bytes (2.00 MB)
Write block size: 4 bytes
p25q16h@0 SPI flash testing
==========================

Perform test on single sector
Test 1: Flash erase
Flash erase succeeded!

Test 2: Flash write
Attempting to write 4 bytes
Data read matches data written. Good!!

Just for curiosity, relevant files are in zephyr/boards/seeed/xiao_ble/ and contain string qspi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&qspi {
    status = "okay";
    pinctrl-0 = <&qspi_default>;
    pinctrl-1 = <&qspi_sleep>;
    pinctrl-names = "default", "sleep";
    p25q16h: p25q16h@0 {
        compatible = "nordic,qspi-nor";
        reg = <0>;
        sck-frequency = <104000000>;
        quad-enable-requirements = "S2B1v1";
        jedec-id = [85 60 15];
        sfdp-bfp = [
            e5 20 f1 ff  ff ff ff 00  44 eb 08 6b  08 3b 80 bb
            ee ff ff ff  ff ff 00 ff  ff ff 00 ff  0c 20 0f 52
            10 d8 08 81
        ];
        size = <16777216>;
        has-dpd;
        t-enter-dpd = <3000>;
        t-exit-dpd = <8000>;
    };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    qspi_default: qspi_default {
        group1 {
            psels = <NRF_PSEL(QSPI_SCK, 0, 21)>,
                <NRF_PSEL(QSPI_IO0, 0, 20)>,
                <NRF_PSEL(QSPI_IO1, 0, 24)>,
                <NRF_PSEL(QSPI_IO2, 0, 22)>,
                <NRF_PSEL(QSPI_IO3, 0, 23)>,
                <NRF_PSEL(QSPI_CSN, 0, 25)>;
        };
    };

    qspi_sleep: qspi_sleep {
        group1 {
            psels = <NRF_PSEL(QSPI_SCK, 0, 21)>,
                <NRF_PSEL(QSPI_IO0, 0, 20)>,
                <NRF_PSEL(QSPI_IO1, 0, 24)>,
                <NRF_PSEL(QSPI_IO2, 0, 22)>,
                <NRF_PSEL(QSPI_IO3, 0, 23)>;
            low-power-enable;
        };
        group2 {
            psels = <NRF_PSEL(QSPI_CSN, 0, 25)>;
            low-power-enable;
            bias-pull-up;
        };
    };

Easy!

XIAO RP2040 and External flash

Physical connection

XIAO RP2040FlashNote
D0 / P26 / Left 1CSChip select
GND / Right 2GND 
3V3 / Right 3VCC 
D10 / MOSI / P3 / Right 4DIMaster Out Slave In -> Data In
D9 / MISO / P4 / Right 5DOMaster In Slave Out <- Data Out
D8 / SCK / P2 / Right 6CLKClock

Getting started with SPI

Hmm. This will be harder (actually much harder). There is also internal flash, but I assume it’s meant for storing firmware and I don’t want to mess with it (for now). Let’s connect external one to standard SPI pins and pin D0 for chip select.

There is relevant Raspberry Pi Pico SPI documentation and files for default SPI configuration for XIAO RP2040 which can be modified for Raspberry Pi Pico.

1
2
3
4
5
6
&spi0 {
    status = "okay";
    pinctrl-0 = <&spi0_default>;
    pinctrl-names = "default";
    clock-frequency = <DT_FREQ_M(8)>;
};
1
2
3
4
5
6
7
8
9
10
11
12
    spi0_default: spi0_default {
        group1 {
            pinmux = <SPI0_TX_P3>;
        };
        group2 {
            pinmux = <SPI0_RX_P4>;
            input-enable;
        };
        group3 {
            pinmux = <SPI0_SCK_P2>;
        };
    };

Curiously, this pinctrl part is completely different from XIAO nRF52840 example above.

Creating auxiliary files

Why not to get inspiration from files above and Raspberry Pi Pico SPI Zephyr documentation ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
&spi0 {
    // Array of CSEL pins
    cs-gpios = <&gpio0 26 GPIO_ACTIVE_LOW>;
    // Devices corresponding to CSEL pins
    status = "okay";
    w25q32: w25q32@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;
        spi-max-frequency = <8000000>;
        status = "okay";
        /*
        size = <0x400000>;
        has-dpd;
        t-enter-dpd = <3000>;
        t-exit-dpd = <30000>;
        */
    };
};
1
2
3
4
5
CONFIG_STDOUT_CONSOLE=y
CONFIG_FLASH=y
CONFIG_SPI=y
CONFIG_SPI_NOR=y
CONFIG_CBPRINTF_FP_SUPPORT=y # Support for printf("%f", ...);

The file above is not final

Mysterious compile error: jedec-id

1
2
3
C:/dev-zephyr/zephyrproject/zephyr/include/zephyr/toolchain/gcc.h:87:36: error: static assertion failed: "jedec,spi-nor jedec-id required for non-runtime SFDP"
   87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert((EXPR), "" MSG)
      |                                    ^~~~~~~~~~~~~~

This is hidden somewhere above 200 other error lines

The Problem

Your device tree overlay is missing the jedec-id property, which is required for SPI NOR flash devices when not using runtime SFDP detection.

How to Find the Correct jedec-id

The jedec-id is a 3-byte identifier that’s specific to each flash chip. Here are common ones:

Winbond W25Q series:

  • W25Q32: [ef 40 16]
  • W25Q64: [ef 40 17]
  • W25Q128: [ef 40 18]

Macronix MX25L series:

  • MX25L3206E: [c2 20 16]
  • MX25L6406E: [c2 20 17]

Micron/ST M25P series:

  • M25P32: [20 20 16]
  • M25P64: [20 20 17]

GigaDevice GD25Q series:

  • GD25Q32: [c8 40 16]
  • GD25Q64: [c8 40 17]

Alternative: Enable Runtime SFDP

If you don’t know the exact jedec-id, you can enable runtime SFDP detection in your prj.conf:

1
CONFIG_SPI_NOR_SFDP_RUNTIME=y

This is stupidly bad error reporting, but it can be worse.

Mysterious error: __device_dts_ord_34

1
c:/users/pavel/zephyr-sdk-0.17.0/arm-zephyr-eabi/bin/../lib/gcc/arm-zephyr-eabi/12.2.0/../../../../arm-zephyr-eabi/bin/ld.bfd.exe: zephyr/drivers/flash/libdrivers__flash.a(spi_nor.c.obj):(.rodata.spi_nor_0_config+0xc): undefined reference to `__device_dts_ord_34'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
 * Generated by gen_defines.py
 *
 * DTS input file:
 *   C:/dev-zephyr/zephyrproject/my_projects/spi-flash/build/zephyr/zephyr.dts.pre
 *
 * Directories with bindings:
 *   $ZEPHYR_BASE\dts\bindings
 *
 * Node dependency ordering (ordinal and path):
 *   0   /
 *   1   /aliases
 *   2   /chosen
 *   3   /connector
 *   4   /soc

 *   33  /soc/gpio@40014000
 *   34  /soc/gpio@40014000/gpio-port@0
 */


/* Node's dependency ordinal: */
#define DT_N_S_soc_S_gpio_40014000_ORD 33
#define DT_N_S_soc_S_gpio_40014000_ORD_STR_SORTABLE 00033

/* Ordinals for what this node depends on directly: */
#define DT_N_S_soc_S_gpio_40014000_REQUIRES_ORDS \
    4, /* /soc */ \
    26, /* /soc/interrupt-controller@e000e100 */

/* Ordinals for what depends directly on this node: */
#define DT_N_S_soc_S_gpio_40014000_SUPPORTS_ORDS \
    34, /* /soc/gpio@40014000/gpio-port@0 */

Yeah. One or two hours later I realized I need to put yet another line into prj.conf.

1
CONFIG_GPIO=y

It’s in Zephyr’s Device Tree Troubleshooting Guide

Now I’m starting to hate Zephyr, cause error messages are cryptic and it’s basically impossible to guess what I need to enable in config to make stuff work.

Wait … why does everything works with XIAO nRF52840?

I turns out there are mysterious files, that enable various driver for specific board.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# SPDX-License-Identifier: Apache-2.0

CONFIG_RESET=y

# Enable UART driver
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y

# Enable console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

# Enable clock control by default
CONFIG_CLOCK_CONTROL=y

# Code partition needed to target the correct flash range
CONFIG_USE_DT_CODE_PARTITION=y

# Output UF2 by default, native bootloader supports it.
CONFIG_BUILD_OUTPUT_UF2=y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# SPDX-License-Identifier: Apache-2.0

# Enable MPU
CONFIG_ARM_MPU=y

# Enable hardware stack protection
CONFIG_HW_STACK_PROTECTION=y

# Enable GPIO
CONFIG_GPIO=y

# Enable UART driver
CONFIG_SERIAL=y

# Enable console
CONFIG_CONSOLE=y

# Build UF2 by default, supported by the Adafruit nRF52 Bootloader
CONFIG_BUILD_OUTPUT_UF2=y
CONFIG_USE_DT_CODE_PARTITION=y

Well. Great. It’s like one board has serial console over USB and other over physical pins.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(.venv) PS C:\dev-zephyr\zephyrproject\my_projects\spi-flash> west build  -b xiao_rp2040 -S cdc-acm-console -- -DCONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=y
(.venv) PS C:\dev-zephyr\zephyrproject\my_projects\spi-flash> cp .\build\zephyr\zephyr.uf2 e:
(.venv) PS C:\dev-zephyr\zephyrproject\my_projects\spi-flash> mpremote connect COM16
Connected to MicroPython at COM16
Use Ctrl-] or Ctrl-x to exit this shell
*** Booting Zephyr OS build v4.1.0-5418-g98ba754013b0 ***
Flash size: 4194304 bytes (4.00 MB)
Write block size: 1 bytes

w25q32@0 SPI flash testing
==========================

Perform test on single sector
Test 1: Flash erase
Flash erase succeeded!

Test 2: Flash write
Attempting to write 4 bytes
Data read matches data written. Good!!

TODOs:

  • How to read JEDEC id (MicroPython maybe?)
  • Final files
  • Little FS
  • Using internal Flash region on Raspberry Pi Pico
This post is licensed under CC BY 4.0 by the author.