SPI example for nRF52833 with nRF Connect SDK 2.3.0

Dear Support Team,

I just started evaluating a nRF52833 DK board with the nRF Connect SDK 2.3.0. At this point I would like to start playing with the SPI but unfortunately I can't find any working example that I could use.

Could you please point me to a working example without using the deprecated configuration parameters? 

Thank you very much in advance and all the best,

Viktor

Parents
  • Hi Viktor,

    One of my colleagues has made an SPI sample in v2.1.0 that you can find here: https://github.com/too1/ncs-spi-master-slave-example. This is using pinctrl and should not have any deprecated configurations.
    You need to make a few changes to the sample to migrate it to v2.3.0, such as adding the zephyr/ prefix to the include files, changing zephyr.h to kernel.h, and replacing spi_transceive_async() with spi_transceive_signal(). See the diff below for which changes to make.

    diff --git a/main.c b/main_v2-3-0.c
    index 2dc4978..11c6138 100644
    --- a/main.c
    +++ b/main_v2-3-0.c
    @@ -4,11 +4,11 @@
      * SPDX-License-Identifier: Apache-2.0
      */
    
    -#include <zephyr.h>
    -#include <device.h>
    -#include <devicetree.h>
    -#include <drivers/gpio.h>
    -#include <drivers/spi.h>
    +#include <zephyr/kernel.h>
    +#include <zephyr/device.h>
    +#include <zephyr/devicetree.h>
    +#include <zephyr/drivers/gpio.h>
    +#include <zephyr/drivers/spi.h>
    
     /* 1000 msec = 1 sec */
     #define SLEEP_TIME_MS   1000
    @@ -80,7 +80,7 @@ static int spi_write_test_msg(void)
            k_poll_signal_reset(&spi_done_sig);
    
            // Start transaction
    -       int error = spi_transceive_async(spi_dev, &spi_cfg, &tx, &rx, &spi_done_sig);
    +       int error = spi_transceive_signal(spi_dev, &spi_cfg, &tx, &rx, &spi_done_sig);
            if(error != 0){
                    printk("SPI transceive error: %i\n", error);
                    return error;
    @@ -147,7 +147,7 @@ static int spi_slave_write_test_msg(void)
            k_poll_signal_reset(&spi_slave_done_sig);
    
            // Start transaction
    -       int error = spi_transceive_async(spi_slave_dev, &spi_slave_cfg, &s_tx, &s_rx, &spi_slave_done_sig);
    +       int error = spi_transceive_signal(spi_slave_dev, &spi_slave_cfg, &s_tx, &s_rx, &spi_slave_done_sig);
            if(error != 0){
                    printk("SPI slave transceive error: %i\n", error);
                    return error;

    Best regards,
    Marte

  • Hi Marte!

    Thank you very much for the quick reply!

    So, for example if I want to communicate with an external sensor (accelerometer) would this be correct config?

    Overlay:

    &pinctrl {
    	spi1_default: spi1_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
    					<NRF_PSEL(SPIM_MOSI, 0, 30)>,
    					<NRF_PSEL(SPIM_MISO, 0, 29)>;
    		};
    	};
    
    	spi1_sleep: spi1_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
    					<NRF_PSEL(SPIM_MOSI, 0, 30)>,
    					<NRF_PSEL(SPIM_MISO, 0, 29)>;
    			low-power-enable;
    		};
    	};
    };
    
    my_spi_master: &spi1 {
    	compatible = "nordic,nrf-spi";
    	status = "okay";
    	pinctrl-0 = <&spi1_default>;
    	pinctrl-1 = <&spi1_sleep>;
    	cs-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>;
    	reg_my_spi_master: spi-dev-a@0 {
    		reg = <0>;
    	};
    };

    Initialize SPI master:

    #define MY_SPI_MASTER DT_NODELABEL(my_spi_master)
    
    // SPI master functionality
    const struct device *spi_dev;
    static struct k_poll_signal spi_done_sig = K_POLL_SIGNAL_INITIALIZER(spi_done_sig);
    
    struct spi_cs_control spim_cs = {
    	.gpio = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(reg_my_spi_master)),
    	.delay = 0,
    };
    
    
    static void spi_init(void)
    {
    	spi_dev = DEVICE_DT_GET(MY_SPI_MASTER);
    	if(!device_is_ready(spi_dev)) {
    		printk("SPI master device not ready!\n");
    	}
    	if(!device_is_ready(spim_cs.gpio.port)){
    		printk("SPI master chip select device not ready!\n");
    	}
    }
    
    static const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
    				 SPI_MODE_CPOL | SPI_MODE_CPHA,
    	.frequency = 4000000,
    	.slave = 0,
    	.cs = &spim_cs,
    };
    
    
    

    Where can I find documentation of the "spi_transceive_signal" function? The IDE can't find anything (warning: implicit declaration of function 'spi_transceive_signal')

    Is it correct that I should rather use the following function to read/write registers of the accelerometer in this case?:

    int error = spi_transceive_dt(spi_dev, &tx, &rx);

    All the best,

    Viktor

  • Hi Marte!

    I managed to make the communication to the sensor work! I post the solution here so others can use it as well.

    Thanks again for the help!

    Overlay:

    &pinctrl {
    	spi1_default: spi1_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
    					<NRF_PSEL(SPIM_MOSI, 0, 30)>,
    					<NRF_PSEL(SPIM_MISO, 0, 29)>;
    		};
    	};
    
    	spi1_sleep: spi1_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
    					<NRF_PSEL(SPIM_MOSI, 0, 30)>,
    					<NRF_PSEL(SPIM_MISO, 0, 29)>;
    			low-power-enable;
    		};
    	};
    };
    
    my_spi_master: &spi1 {
    	compatible = "nordic,nrf-spi";
    	status = "okay";
    	pinctrl-0 = <&spi1_default>;
    	pinctrl-1 = <&spi1_sleep>;
    	cs-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>;
    	reg_my_spi_master: spi-dev-a@0 {
    		reg = <0>;
    	};
    };

    SPI initialization:

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>
    
    #define MY_SPI_MASTER DT_NODELABEL(my_spi_master)
    
    // SPI master functionality
    const struct device *spi_dev;
    
    struct spi_cs_control spim_cs = {
    	.gpio = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(reg_my_spi_master)),
    	.delay = 0,
    };
    
    
    static const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
    				 SPI_MODE_CPOL | SPI_MODE_CPHA,
    	.frequency = 4000000,
    	.slave = 0,
    	.cs = &spim_cs,
    };
    
    static void spi_init(void)
    {
    	spi_dev = DEVICE_DT_GET(MY_SPI_MASTER);
    	if(!device_is_ready(spi_dev)) {
    		printk("SPI master device not ready!\n");
    	}
    	if(!device_is_ready(spim_cs.gpio.port)){
    		printk("SPI master chip select device not ready!\n");
    	}
    }

    Test code that reads the accelerometer device ID:

    static int spi_test(void)
    {
    	static uint8_t tx_buffer[3];
    	static uint8_t rx_buffer[3];
    
    	const struct spi_buf tx_buf = {
    		.buf = tx_buffer,
    		.len = sizeof(tx_buffer)
    	};
    	const struct spi_buf_set tx = {
    		.buffers = &tx_buf,
    		.count = 1
    	};
    
    	struct spi_buf rx_buf = {
    		.buf = rx_buffer,
    		.len = sizeof(rx_buffer),
    	};
    	const struct spi_buf_set rx = {
    		.buffers = &rx_buf,
    		.count = 1
    	};
    
    
    	/*
    	first 2 bytes of tx specify read/write action and on which register to perform
    	last byte of rx contains the respons from the accelerometer
    	*/
    	tx_buffer[0] = XL367_REG_READ;
    	tx_buffer[1] = XL367_DEVID_AD;
    
    	int error = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
    	if(error != 0){
    		printk("SPI transceive error: %i\n", error);
    		return error;
    	}
    
    
    	if(DEVICE_ID == rx_buffer[2])
    	{
    		printk("Successfull ACC communication\n");
    	}
    	else
    	{
    		printk("Faild ACC communication\n");
    	}
    
    	return 0;
    }

Reply
  • Hi Marte!

    I managed to make the communication to the sensor work! I post the solution here so others can use it as well.

    Thanks again for the help!

    Overlay:

    &pinctrl {
    	spi1_default: spi1_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
    					<NRF_PSEL(SPIM_MOSI, 0, 30)>,
    					<NRF_PSEL(SPIM_MISO, 0, 29)>;
    		};
    	};
    
    	spi1_sleep: spi1_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
    					<NRF_PSEL(SPIM_MOSI, 0, 30)>,
    					<NRF_PSEL(SPIM_MISO, 0, 29)>;
    			low-power-enable;
    		};
    	};
    };
    
    my_spi_master: &spi1 {
    	compatible = "nordic,nrf-spi";
    	status = "okay";
    	pinctrl-0 = <&spi1_default>;
    	pinctrl-1 = <&spi1_sleep>;
    	cs-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>;
    	reg_my_spi_master: spi-dev-a@0 {
    		reg = <0>;
    	};
    };

    SPI initialization:

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>
    
    #define MY_SPI_MASTER DT_NODELABEL(my_spi_master)
    
    // SPI master functionality
    const struct device *spi_dev;
    
    struct spi_cs_control spim_cs = {
    	.gpio = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(reg_my_spi_master)),
    	.delay = 0,
    };
    
    
    static const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
    				 SPI_MODE_CPOL | SPI_MODE_CPHA,
    	.frequency = 4000000,
    	.slave = 0,
    	.cs = &spim_cs,
    };
    
    static void spi_init(void)
    {
    	spi_dev = DEVICE_DT_GET(MY_SPI_MASTER);
    	if(!device_is_ready(spi_dev)) {
    		printk("SPI master device not ready!\n");
    	}
    	if(!device_is_ready(spim_cs.gpio.port)){
    		printk("SPI master chip select device not ready!\n");
    	}
    }

    Test code that reads the accelerometer device ID:

    static int spi_test(void)
    {
    	static uint8_t tx_buffer[3];
    	static uint8_t rx_buffer[3];
    
    	const struct spi_buf tx_buf = {
    		.buf = tx_buffer,
    		.len = sizeof(tx_buffer)
    	};
    	const struct spi_buf_set tx = {
    		.buffers = &tx_buf,
    		.count = 1
    	};
    
    	struct spi_buf rx_buf = {
    		.buf = rx_buffer,
    		.len = sizeof(rx_buffer),
    	};
    	const struct spi_buf_set rx = {
    		.buffers = &rx_buf,
    		.count = 1
    	};
    
    
    	/*
    	first 2 bytes of tx specify read/write action and on which register to perform
    	last byte of rx contains the respons from the accelerometer
    	*/
    	tx_buffer[0] = XL367_REG_READ;
    	tx_buffer[1] = XL367_DEVID_AD;
    
    	int error = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
    	if(error != 0){
    		printk("SPI transceive error: %i\n", error);
    		return error;
    	}
    
    
    	if(DEVICE_ID == rx_buffer[2])
    	{
    		printk("Successfull ACC communication\n");
    	}
    	else
    	{
    		printk("Faild ACC communication\n");
    	}
    
    	return 0;
    }

Children
  • Hi Viktor,

    That is great to hear, and thank you for sharing the solution here for others to find!

    Best regards,
    Marte

  • Hi Marte,

    Just one more question related to this topic:

    What exactly this line is doing in the overlay SPI definition?

    reg_my_spi_master: spi-dev-a@0 {
    		reg = <0>;
    	};

    What exactly spi-dev-a@0 refers to and what is reg = <0>?

    Do I have to change anything to this part when trying to enable both SPI0 and SPI1 ? Does it relate to which SPI is beeing used? 

    Thank you in advance.

  • Hi Viktor,

    Spi-dev-a@-0 is the name of the devicetree node. As an example you can look at the example DTS file in Devicetree Guide - Syntax and structure:

    /dts-v1/;
    
    / {
            a-node {
                    subnode_nodelabel: a-sub-node {
                            foo = <3>;
                    };
            };
    };

    Here, a-sub-node is the same as spi-dev-a@0 in your case.

    For SPI devices, the 'reg' property is the chip select number. One SPI node (SPI0, SPI1, etc.) does not affect the reg property of another SPI node. If, however, you have more sub-nodes under an SPI node, then you need to make sure they have different chip select numbers. For example, the Thingy53 has three different SPI sub-nodes under SPI3, which must have different chip select numbers. This is from thingy53_nrf5340_common.dts:

    &spi3 {
    	compatible = "nordic,nrf-spim";
    	status = "okay";
    	cs-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>,
    		   <&gpio1 4  GPIO_ACTIVE_LOW>,
    		   <&gpio0 24 GPIO_ACTIVE_LOW>;
    
    	pinctrl-0 = <&spi3_default>;
    	pinctrl-1 = <&spi3_sleep>;
    	pinctrl-names = "default", "sleep";
    	adxl362: spi-dev-adxl362@0 {
    		compatible = "adi,adxl362";
    		spi-max-frequency = <8000000>;
    		reg = <0>;
    		int1-gpios = <&gpio0 19 0>;
    	};
    
    	bmi270: spi-dev-bmi270@1 {
    		compatible = "bosch,bmi270";
    		status = "disabled";
    		spi-max-frequency = <8000000>;
    		reg = <1>;
    	};
    
    	nrf_radio_fem_spi: fem_spi@2 {
    		compatible = "nordic,nrf21540-fem-spi";
    		status = "disabled";
    		reg = <2>;
    		spi-max-frequency = <8000000>;
    	};
    };

    The 'reg' property is used for different things based on the device. For more information see Devicetree Guide - Important properties.

    Best regards,
    Marte

  • Hi Marte,

    Thank you very much! This is very helpful information!

    All the best,

    Viktor

  • Hi all,
    Perhaps I should open a new thread but since my problem is close to this one, I'll tap it in here.

    I tried to use this example in a clean  nRF Connect SDK 2.6.0. project on nRF52840 DK just to try and get simple spi master operation going. However, I'm running into all sorts of build error which don't make sense to me.

    My overlay is:

    &pinctrl {
    	spi1_default: spi1_default {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 1, 15)>,
    					<NRF_PSEL(SPIM_MOSI, 1, 13)>,
    					<NRF_PSEL(SPIM_MISO, 1, 14)>;
    		};
    	};
    
    	spi1_sleep: spi1_sleep {
    		group1 {
    			psels = <NRF_PSEL(SPIM_SCK, 1, 15)>,
    					<NRF_PSEL(SPIM_MOSI, 1, 13)>,
    					<NRF_PSEL(SPIM_MISO, 1, 14)>;
    			low-power-enable;
    		};
    	};
    };
    
    my_spi_master: &spi1 {
    	compatible = "nordic,nrf-spi";
    	status = "okay";
    	pinctrl-0 = <&spi1_default>;
    	pinctrl-1 = <&spi1_sleep>;
    	cs-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>;
    	reg_my_spi_master: spi-dev-a@0 {
    		reg = <0>;
    	};
    };
    &pwm0 {
    	status = "disabled";
    };
    

    main.c is:

    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>
    
    #define MY_SPI_MASTER DT_NODELABEL(my_spi_master)
    #define DEVICE_ID
    
    uint8_t TX0BUFF[3];
    uint8_t TX1BUFF[3];
    
    // SPI master functionality
    const struct device *spi_dev;
    
    struct spi_cs_control spim_cs = {
    	.gpio = SPI_CS_GPIOS_DT_SPEC_GET(DT_NODELABEL(reg_my_spi_master)),
    	.delay = 0,
    };
    
    
    static const struct spi_config spi_cfg = {
    	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
    				 SPI_MODE_CPOL | SPI_MODE_CPHA,
    	.frequency = 1600000,
    	.slave = 0,
    	.cs = &spim_cs,
    };
    
    static void spi_init(void)
    {
    	spi_dev = DEVICE_DT_GET(MY_SPI_MASTER);
    	if(!device_is_ready(spi_dev)) {
    		printk("SPI master device not ready!\n");
    	}
    	if(!device_is_ready(spim_cs.gpio.port)){
    		printk("SPI master chip select device not ready!\n");
    	}
    }
    
    
    static int spi_test(void)
    {
    	static uint8_t tx_buffer[3];
    	static uint8_t rx_buffer[3];
    
    	const struct spi_buf tx_buf = {
    		.buf = tx_buffer,
    		.len = sizeof(tx_buffer)
    	};
    	const struct spi_buf_set tx = {
    		.buffers = &tx_buf,
    		.count = 1
    	};
    
    	struct spi_buf rx_buf = {
    		.buf = rx_buffer,
    		.len = sizeof(rx_buffer),
    	};
    	const struct spi_buf_set rx = {
    		.buffers = &rx_buf,
    		.count = 1
    	};
    
    
    	/*
    	first 2 bytes of tx specify read/write action and on which register to perform
    	last byte of rx contains the respons from the accelerometer
    	*/
    	tx_buffer[0] = TX0BUFF;
    	tx_buffer[1] = TX1BUFF;
    
    	int error = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
    	if(error != 0){
    		printk("SPI transceive error: %i\n", error);
    		return error;
    	}
    
    
    	if(DEVICE_ID == rx_buffer[2])
    	{
    		printk("Successfull ACC communication\n");
    	}
    	else
    	{
    		printk("Faild ACC communication\n");
    	}
    
    	return 0;
    }
    
    
    int main(void)
    {
            return 0;
    }
    

    proj.conf:

    # Enable DK LED and Buttons library
    CONFIG_DK_LIBRARY=y
    
    # This example requires more workqueue stack
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    
    # Config logger
    CONFIG_LOG=y
    CONFIG_LOG_PRINTK=y
    
    CONFIG_SPI=y
    CONFIG_GPIO=y
    CONFIG_NRFX_SPIM1=y

    The errors I get:

    [{
        "resource": "/c:/NordicSemi/v1.9.1/zephyr/dts/bindings/pinctrl/atmel,sam0-pinctrl.yaml",
        "owner": "DeviceTree types",
        "severity": 8,
        "message": "Invalid type definition: Map keys must be unique at line 37, column 1:\n\n\r\nproperties:\n^\n",
        "startLineNumber": 1,
        "startColumn": 1,
        "endLineNumber": 1,
        "endColumn": 1
    }]

    [{
        "resource": "/c:/NordicSemi/v1.9.1/zephyr/dts/bindings/spi/st,stm32-spi-subghz.yaml",
        "owner": "DeviceTree types",
        "severity": 8,
        "message": "Invalid type definition: Map keys must be unique at line 10, column 1:\n\n\r\ninclude:\n^\n",
        "startLineNumber": 1,
        "startColumn": 1,
        "endLineNumber": 1,
        "endColumn": 1
    }]

    [{
    "resource": "/c:/nordicsemi/v1.9.1/zephyr/kconfig",
    "owner": "kconfig",
    "severity": 8,
    "message": "C:\\nordicsemi\\v2.6.0\\nrf\\subsys\\nfc\\lib\\kconfig:89: error: couldn't parse 'default y if $(dt_has_compat,$(DT_COMPAT_NORDIC_NRF_EGU)) || \t\t $(dt_has_compat,$(DT_COMPAT_NORDIC_NRF_SWI))': macro expanded to blank string",
    "startLineNumber": 8,
    "startColumn": 1,
    "endLineNumber": 8,
    "endColumn": 100000
    }]

    [{
        "resource": "/c:/nordicsemi/v2.6.0/zephyr/kconfig",
        "owner": "kconfig",
        "severity": 8,
        "message": "C:\\nordicsemi\\v2.6.0\\zephyr\\soc\\arm\\nxp_imx\\rt5xx\\kconfig.defconfig.series:36: error: couldn't parse 'default $(dt_node_reg_addr_hex,$(DT_CHOSEN_FLASH_PARENT),1) \t\tif $(dt_node_has_compat,$(DT_CHOSEN_FLASH_PARENT),$(DT_COMPAT_FLEXSPI))': macro expanded to blank string",
        "startLineNumber": 8,
        "startColumn": 1,
        "endLineNumber": 8,
        "endColumn": 100000
    }]

    [{
        "resource": "/c:/Users/pekka.lemmetyinen/spi_simple/src/main.c",
        "owner": "C/C++: IntelliSense",
        "code": "29",
        "severity": 8,
        "message": "expected an expression",
        "source": "C/C++",
        "startLineNumber": 80,
        "startColumn": 15,
        "endLineNumber": 80,
        "endColumn": 17
    },{
        "resource": "/c:/Users/pekka.lemmetyinen/spi_simple/src/main.c",
        "owner": "Kconfig options",
        "code": "missing_kconfig_param",
        "severity": 4,
        "message": "The GPIO drivers must be enabled by CONFIG_GPIO to be included in the build",
        "startLineNumber": 4,
        "startColumn": 1,
        "endLineNumber": 4,
        "endColumn": 33
    },{
        "resource": "/c:/Users/pekka.lemmetyinen/spi_simple/src/main.c",
        "owner": "Kconfig options",
        "code": "missing_kconfig_param",
        "severity": 4,
        "message": "The SPI drivers must be enabled by CONFIG_SPI to be included in the build",
        "startLineNumber": 5,
        "startColumn": 1,
        "endLineNumber": 5,
        "endColumn": 32
    }]

    [{
        "resource": "/c:/NordicSemi/v2.6.0/zephyr/modules/hal_nordic/nrfx/Kconfig",
        "owner": "nrf-connect",
        "severity": 4,
        "message": "NRFX_SPIM1 (defined at C:/NordicSemi/v2.6.0/zephyr/modules/hal_nordic\\nrfx/Kconfig:286,",
        "source": "kconfig",
        "startLineNumber": 286,
        "startColumn": 1,
        "endLineNumber": 286,
        "endColumn": 2147483647
    }]

    1. I don't get what this has to do with Atmel SAM or ST STM32 devices or their configs?

    2. I've configured CONFIGURE_SPI and GPIO in prpj.conf. VS Code is flagging that the drivers for

    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>

    needs to be configured. But I have done in prj.conf...?!?

    Bit of hand holding required, please, so that I would get the spi side up and running.

    Kind regards

    Peks1

Related