Slow SPI performance with zephyr

SDK Environment: nRF Connect SDK v1.7.1

Target: Decawave DWM1001-DEV. This uses the nRF52832

A 5 byte SPI transfer is taking about 17us when using the nordic spim drivers and about 56us when using the zephyr spim drivers.

SPI CLK frequency = 8M

Would this be considered normal? The slow speed of the zephyr SPI drivers is causing problems in my application.

The zephyr drivers seem to be stable, but slow.

I set and clear a gpio testpin before and after the SPI transfer so I can measure the timing.

SPI timing with the nordic drivers

SPI timing with the zephyr drivers

"nrfx_spim.zip" uses the nordic drivers

"zephyr_spim.zip" uses the zephyr drivers

6763.nrfx_spim.zipzephyr_spim.zip

  • Hi

    The main difference can be seen in the above snapshots, is "SS" time (the time used to set chip select).

    How do you set the SS signal?

    In Zephyr, it is implemented by the software (there is no automatic assignment by the hardware), so you have two option, set/clear the the SS pin by your application before/after sending the SPI data, or you can leave it to the Zephyr to control the SS signal by a code similar to this:

    #define SPI_NODE DT_NODELABEL(spi0)
    
    const struct spi_cs_control spi_dev_cs_ctrl = {
    	.gpio_dev = DEVICE_DT_GET(DT_GPIO_CTLR(SPI_NODE, cs_gpios)),
    	.gpio_pin = DT_GPIO_PIN(SPI_NODE, cs_gpios),
    	.gpio_dt_flags = DT_GPIO_FLAGS(SPI_NODE, cs_gpios)
    };
    
    const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | 
    				SPI_TRANSFER_MSB |
    				SPI_OP_MODE_MASTER,
    	.frequency = 8000000,
    	.cs = &spi_dev_cs_ctrl,
    };

    It is what I have measured (around 3 us from when SS has toggled until when clk has switched)

    PS: ensure you are using the last version of the Zephyr, maybe there are some bugs in older versions

  • How do you set the SS signal?

    I had attached the source code for the two test cases in the first post.

    I am using Zephyr OS build v2.6.99-ncs1-1. This is the version that came with nRF Connect SDK v1.7.1

    The following is the source code for the zephyr test:

    /*
     * Copyright (c) 2016 Intel Corporation
     *
     * SPDX-License-Identifier: Apache-2.0
     */

    #include <zephyr.h>
    #include <device.h>
    #include <string.h>
    #include <drivers/gpio.h>
    #include <nrfx_spim.h>
    #include <device.h>
    #include <drivers/spi.h>
    #include <devicetree/spi.h>
    #include <assert.h>

    #define SLEEP_TIME  (100)       // ms
    #define SPI_BUF_SIZE (255)

    const struct gpio_dt_spec test_j7_pin3 = {.port = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 12, .dt_flags = GPIO_ACTIVE_HIGH};

    const struct device* spi = DEVICE_DT_GET(DT_PARENT(DT_NODELABEL(dw1000_dev)));

    const static struct spi_config spi_config_dw1000 = SPI_CONFIG_DT(DT_NODELABEL(dw1000_dev), SPI_WORD_SET(8), 0);

    uint8_t tx_buf [SPI_BUF_SIZE];
    uint8_t rx_buf [SPI_BUF_SIZE];

    void setup_spim(void)
    {
        if (!device_is_ready(spi_config_dw1000.cs->gpio_dev))
        {
          printk("ERROR: spi gpio_dev not ready");
          for(;;);
        }

        if (!device_is_ready(spi))
        {
          printk("ERROR: failed to open spi");
          for(;;);
        }
    }

    /*
    * Setup output test pins for external monitoring.
    */
    void testPin1Set()
    {
        gpio_pin_set_dt(&test_j7_pin3, 1);
    }

    void testPin1Clear()
    {
        gpio_pin_set_dt(&test_j7_pin3, 0);
    }

    void testPinsSetup()
    {
        gpio_pin_configure_dt(&test_j7_pin3, GPIO_OUTPUT_INACTIVE);
    }

    void main(void)
    {
        testPinsSetup();
        setup_spim();

        printk("NRFX SPIM sample\n");
        printk("Read DW1000 DWT_DEVICE_ID\n");

        while (1)
        {
            k_msleep(SLEEP_TIME);

            testPin1Set();

            const int length = 5;
            memset(tx_buf, 0, length);
            memset(rx_buf, 0, length);

            struct spi_buf spi_buf_tx = {.buf = tx_buf, .len = length};
            struct spi_buf spi_buf_rx = {.buf = rx_buf, .len = length};

            struct spi_buf_set spi_buf_set_tx = {.buffers = &spi_buf_tx, .count = 1};
            struct spi_buf_set spi_buf_set_rx = {.buffers = &spi_buf_rx, .count = 1};

            spi_transceive(spi, &spi_config_dw1000, &spi_buf_set_tx, &spi_buf_set_rx);

            testPin1Clear();
            // Expecting DWT_DEVICE_ID   (0xDECA0130)        //!< DW1000 MP device ID
            printk("Result = %x\n", *((uint32_t*) &rx_buf[1]));
        }
    }
  • The following might read better than my last post:

    How do you set the SS signal?

    I had attached the source code for the two test cases in the first post.

    I am using Zephyr OS build v2.6.99-ncs1-1. This is the version that came with nRF Connect SDK v1.7.1

    The following is the source code for the zephyr test:

    /*
     * Copyright (c) 2016 Intel Corporation
     *
     * SPDX-License-Identifier: Apache-2.0
     */

    #include <zephyr.h>
    #include <device.h>
    #include <string.h>
    #include <drivers/gpio.h>
    #include <nrfx_spim.h>
    #include <device.h>
    #include <drivers/spi.h>
    #include <devicetree/spi.h>
    #include <assert.h>

    #define SLEEP_TIME    (100)        // ms
    #define SPI_BUF_SIZE (255)

    const struct gpio_dt_spec test_j7_pin3 = {.port = DEVICE_DT_GET(DT_NODELABEL(gpio0)), .pin = 12, .dt_flags = GPIO_ACTIVE_HIGH};

    const struct device* spi = DEVICE_DT_GET(DT_PARENT(DT_NODELABEL(dw1000_dev)));

    const static struct spi_config spi_config_dw1000 = SPI_CONFIG_DT(DT_NODELABEL(dw1000_dev), SPI_WORD_SET(8), 0);

    uint8_t tx_buf [SPI_BUF_SIZE];
    uint8_t rx_buf [SPI_BUF_SIZE];

    void setup_spim(void)
    {
        if (!device_is_ready(spi_config_dw1000.cs->gpio_dev))
        {
          printk("ERROR: spi gpio_dev not ready");
          for(;;);
        }

        if (!device_is_ready(spi))
        {
          printk("ERROR: failed to open spi");
          for(;;);
        }
    }

    /*
    * Setup output test pins for external monitoring.
    */
    void testPin1Set()
    {
        gpio_pin_set_dt(&test_j7_pin3, 1);
    }

    void testPin1Clear()
    {
        gpio_pin_set_dt(&test_j7_pin3, 0);
    }

    void testPinsSetup()
    {
        gpio_pin_configure_dt(&test_j7_pin3, GPIO_OUTPUT_INACTIVE);
    }

    void main(void)
    {
        testPinsSetup();
        setup_spim();

        printk("NRFX SPIM sample\n");
        printk("Read DW1000 DWT_DEVICE_ID\n");

        while (1)
        {
            k_msleep(SLEEP_TIME);

            testPin1Set();

            const int length = 5;
            memset(tx_buf, 0, length);
            memset(rx_buf, 0, length);

            struct spi_buf spi_buf_tx = {.buf = tx_buf, .len = length};
            struct spi_buf spi_buf_rx = {.buf = rx_buf, .len = length};

            struct spi_buf_set spi_buf_set_tx = {.buffers = &spi_buf_tx, .count = 1};
            struct spi_buf_set spi_buf_set_rx = {.buffers = &spi_buf_rx, .count = 1};

              spi_transceive(spi, &spi_config_dw1000, &spi_buf_set_tx, &spi_buf_set_rx);

            testPin1Clear();
            // Expecting DWT_DEVICE_ID   (0xDECA0130)        //!< DW1000 MP device ID
            printk("Result = %x\n", *((uint32_t*) &rx_buf[1]));
        }
    }

  • Would you please add "<your board>.overlay", "<your board>.conf" and "prj.conf" files here? then I can run the sample in order to check it on my side.

  • Can you not see the attachments in the first post? They show up on my end.

    I will insert them into the text here as well.

    I do not have a board overlay as I copied the board definition to the project and edited that. The only change to the board "decawave_dwm1001_dev" was to add a node label to the line dw1000_dev: dw1000@0 {

    "decawave_dwm1001_dev2.dts" all the other board files are the same as in "v1.7.1\zephyr\boards\arm\decawave_dwm1001_dev"

    /*
     * Copyright (c) 2019 Stéphane D'Alu
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    /dts-v1/;
    #include <nordic/nrf52832_qfaa.dtsi>
    
    / {
    	model = "Decawave DWM1001-DEV2";
    	compatible = "decawave,dwm1001";
    
    	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;
    	};
    
    	leds {
    		compatible = "gpio-leds";
    		led0_red: led_0 {
    			gpios = <&gpio0 14 GPIO_ACTIVE_LOW>;
    			label = "Red LED 0";
    		};
    		led1_green: led_1 {
    			gpios = <&gpio0 30 GPIO_ACTIVE_LOW>;
    			label = "Green LED 1";
    		};
    		led2_red: led_2 {
    			gpios = <&gpio0 22 GPIO_ACTIVE_LOW>;
    			label = "Red LED 2";
    		};
    		led3_blue: led_3 {
    			gpios = <&gpio0 31 GPIO_ACTIVE_LOW>;
    			label = "Blue LED 3";
    		};
    	};
    
    	pwmleds {
    		compatible = "pwm-leds";
    		pwm_led0_red: pwm_led_0 {
    			pwms = <&pwm0 22>;
    			label = "Red PWM LED";
    		};
    	};
    
    	buttons {
    		compatible = "gpio-keys";
    		button0: button_0 {
    			gpios = <&gpio0 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
    			label = "Push button switch 0";
    		};
    	};
    
    	/* These aliases are provided for compatibility with samples */
    	aliases {
    		sw0        = &button0;
    		led0       = &led0_red;
    		led1       = &led1_green;
    		led2       = &led2_red;
    		led3       = &led3_blue;
    		led0-red   = &led0_red;
    		led1-green = &led1_green;
    		led2-red   = &led2_red;
    		led3-blue  = &led3_blue;
    		pwm-led0   = &pwm_led0_red;
    	};
    };
    
    &adc {
    	status = "okay";
    };
    
    &gpiote {
    	status = "okay";
    };
    
    &gpio0 {
    	status = "okay";
    };
    
    &uart0 {
    	status = "okay";
    	compatible = "nordic,nrf-uart";
    	current-speed = <115200>;
    	tx-pin  = <5>;
    	rx-pin  = <11>;
    };
    
    &i2c0 {
    	compatible = "nordic,nrf-twim";
    	status = "okay";
    	clock-frequency = <I2C_BITRATE_FAST>;
    	sda-pin = <29>;
    	scl-pin = <28>;
    
    	lis2dh12: lis2dh12@19 {
    		compatible = "st,lis2dh12", "st,lis2dh";
    		reg = <0x19>;
    		irq-gpios = <&gpio0 25 GPIO_ACTIVE_HIGH>;
    		label = "LIS2DH12-ACCEL";
    	};
    };
    
    &spi1 {
    	compatible = "nordic,nrf-spi";
    	status = "okay";
    	sck-pin  = <4>;
    	mosi-pin = <6>;
    	miso-pin = <7>;
    	cs-gpios = <&gpio0 3 0>;
    };
    
    &spi2 {
    	compatible = "nordic,nrf-spim";
    	status = "okay";
    	sck-pin  = <16>;	
    	mosi-pin = <20>;
    	miso-pin = <18>;
    	cs-gpios = <&gpio0 17 GPIO_ACTIVE_LOW>;
    
    	dw1000_dev: dw1000@0 {
    		compatible = "decawave,dw1000";
    		spi-max-frequency = <8000000>;
    		int-gpios = <&gpio0 19 GPIO_ACTIVE_HIGH>;	/* P0.19 */
    		reset-gpios = <&gpio0 24 GPIO_ACTIVE_LOW>;	/* P0.24 */
    		status = "okay";
    		label = "DW1000";
    		reg = <0>;
    	};
    };
    
    &pwm0 {
    	status = "okay";
    	ch0-pin = <22>;
    	ch0-inverted;
    };
    
    &flash0 {
    
    	partitions {
    		compatible = "fixed-partitions";
    		#address-cells = <1>;
    		#size-cells = <1>;
    
    		boot_partition: partition@0 {
    			label = "mcuboot";
    			reg = <0x00000000 0xc000>;
    		};
    		slot0_partition: partition@c000 {
    			label = "image-0";
    			reg = <0x0000C000 0x32000>;
    		};
    		slot1_partition: partition@3e000 {
    			label = "image-1";
    			reg = <0x0003E000 0x32000>;
    		};
    		scratch_partition: partition@70000 {
    			label = "image-scratch";
    			reg = <0x00070000 0xa000>;
    		};
    		storage_partition: partition@7a000 {
    			label = "storage";
    			reg = <0x0007a000 0x00006000>;
    		};
    	};
    };
    

    "prj.conf"

    CONFIG_SOC_NRF52832_ALLOW_SPIM_DESPITE_PAN_58=y

    To build the project

    west build -b decawave_dwm1001_dev2 -d build2 -- -DBOARD_ROOT=\ncs\myapps\zephyr_spim -DCONFIG_DEBUG_OPTIMIZATIONS="y" -DCONFIG_OPENOCD_SUPPORT="y"

Related