Devicetree and nrfx SPI slave driver configuration

First - I haven't been able to find an example of how to configure and use "nordic,nrf-spis" in NCS. If one exists that would probably help.

I configure SPI like this in devicetree (moved to using pinctrl to avoid warnings on pindefinitions - and it seems smart although I haven't fully grasped it yet):

&pinctrl {
	spi0_default: spi0_default {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
				<NRF_PSEL(SPIM_MISO, 0, 14)>,
				<NRF_PSEL(SPIM_MOSI, 0, 16)>;
		};
	};	

	spi0_sleep: spi0_sleep {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 18)>,
				<NRF_PSEL(SPIM_MISO, 0, 14)>,
				<NRF_PSEL(SPIM_MOSI, 0, 16)>;
			low-power-enable;

		};
	};	
};

&spi0 {
		compatible = "nordic,nrf-spis";
		status = "okay";
		cs-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
		a: spi-dev-a@0 {
			reg = <0>;
		};
		pinctrl-0 = <&spi0_default>;
		pinctrl-1 = <&spi0_sleep>;
		pinctrl-names = "default", "sleep";
		def-char = <0xFF>;
};

However, I can not figure out how to access the DTC info from the code to set the pins in "nrfx_spis_config_t".

For the moment I repeat the PIN numbers in my code - obviously not what I want.

rgds Tage

Parents
  • The short answer is 'you're not supposed to'. 

    Zephyr's SPI device driver API is the intended access point for your 'a: spi-dev-a@0' devicetree node.

    The build system knows that the 'a: spi-dev-a@0' node is a child node of the SPI controller node 'spi0' because its declaration follows the rules of how a child node is decleared (ie. inside its parent node).

    The build system knows that the 'spi0' is an SPI controller node because its 'compatible' property is "nordic,nrf-spis". 

    The build system knows that the 'compatible' property of the 'spi0' node refers to a specific devicetree 'binding', in this case zephyr\dts\bindings\spi\nordic,nrf-spis.yaml: 

    description: Nordic nRF family SPIS (SPI slave with EasyDMA)
    
    compatible: "nordic,nrf-spis"
    
    include: nordic,nrf-spi-common.yaml
    
    properties:
        def-char:
          type: int
          required: true
          description: |
              Default character. Character clocked out when the slave was not
              provided with buffers and is ignoring the transaction.

    nordic,nrf-spis.yaml includes another binding, nordic,nrf-spi-common.yaml. Who, in turn, includes the spi-controller.yaml binding.  

    The build system now knows that the 'spi0' node is in fact an SPI controller node, and it needs a driver that has implemented the SPI controller API. In this case it is the spi_nrfx_spis.c driver. 

    If CONFIG_NRFX_SPIS and CONFIG_NRFX_SPIS0 =y the spi_nrfx_spis.c will be built and linked to the zephyr's SPI controller API functions. 

    The zephyr\drivers\spi\spi_nrfx_spis.c line 267-342 will initialize the driver in modules\hal\nordic\nrfx\drivers\src\nrfx_spis.c after the kernel has booted, with the configuration set in your DTS and DTS overlay files. 

    /*
     * Current factors requiring use of DT_NODELABEL:
     *
     * - HAL design (requirement of drv_inst_idx in nrfx_spis_t)
     * - Name-based HAL IRQ handlers, e.g. nrfx_spis_0_irq_handler
     */
    
    #define SPIS(idx) DT_NODELABEL(spi##idx)
    #define SPIS_PROP(idx, prop) DT_PROP(SPIS(idx), prop)
    
    #define SPI_NRFX_SPIS_PIN_CFG(idx)					\
    	COND_CODE_1(CONFIG_PINCTRL,					\
    		(.skip_gpio_cfg = true,					\
    		 .skip_psel_cfg = true,),				\
    		(.sck_pin    = SPIS_PROP(idx, sck_pin),			\
    		 .mosi_pin   = DT_PROP_OR(SPIS(idx), mosi_pin,		\
    					  NRFX_SPIS_PIN_NOT_USED),	\
    		 .miso_pin   = DT_PROP_OR(SPIS(idx), miso_pin,		\
    					  NRFX_SPIS_PIN_NOT_USED),	\
    		 .csn_pin    = SPIS_PROP(idx, csn_pin),			\
    		 .csn_pullup = NRF_GPIO_PIN_NOPULL,			\
    		 .miso_drive = NRF_GPIO_PIN_S0S1,))
    
    #define SPI_NRFX_SPIS_DEFINE(idx)					       \
    	NRF_DT_CHECK_PIN_ASSIGNMENTS(SPIS(idx), 0,			       \
    				     sck_pin, mosi_pin, miso_pin, csn_pin);    \
    	static void irq_connect##idx(void)				       \
    	{								       \
    		IRQ_CONNECT(DT_IRQN(SPIS(idx)), DT_IRQ(SPIS(idx), priority),   \
    			    nrfx_isr, nrfx_spis_##idx##_irq_handler, 0);       \
    	}								       \
    	static struct spi_nrfx_data spi_##idx##_data = {		       \
    		SPI_CONTEXT_INIT_LOCK(spi_##idx##_data, ctx),		       \
    		SPI_CONTEXT_INIT_SYNC(spi_##idx##_data, ctx),		       \
    	};								       \
    	IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_DEFINE(SPIS(idx))));	       \
    	static const struct spi_nrfx_config spi_##idx##z_config = {	       \
    		.spis = {						       \
    			.p_reg = (NRF_SPIS_Type *)DT_REG_ADDR(SPIS(idx)),      \
    			.drv_inst_idx = NRFX_SPIS##idx##_INST_IDX,	       \
    		},							       \
    		.config = {						       \
    			SPI_NRFX_SPIS_PIN_CFG(idx)			       \
    			.mode      = NRF_SPIS_MODE_0,			       \
    			.bit_order = NRF_SPIS_BIT_ORDER_MSB_FIRST,	       \
    			.orc       = SPIS_PROP(idx, overrun_character),	       \
    			.def       = SPIS_PROP(idx, def_char),		       \
    		},							       \
    		.irq_connect = irq_connect##idx,			       \
    		IF_ENABLED(CONFIG_PINCTRL,				       \
    			(.pcfg = PINCTRL_DT_DEV_CONFIG_GET(SPIS(idx)),))       \
    	};								       \
    	DEVICE_DT_DEFINE(SPIS(idx),					       \
    			    spi_nrfx_init,				       \
    			    NULL,					       \
    			    &spi_##idx##_data,				       \
    			    &spi_##idx##z_config,			       \
    			    POST_KERNEL,				       \
    			    CONFIG_SPI_INIT_PRIORITY,			       \
    			    &spi_nrfx_driver_api)
    
    #ifdef CONFIG_SPI_0_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(0);
    #endif
    
    #ifdef CONFIG_SPI_1_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(1);
    #endif
    
    #ifdef CONFIG_SPI_2_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(2);
    #endif
    
    #ifdef CONFIG_SPI_3_NRF_SPIS
    SPI_NRFX_SPIS_DEFINE(3);
    #endif

    To use zephyr's SPI device driver API you need to let the build system know that the devicetree node you accessing is in fact an SPI device. This is done by adding the property 'compatible = "spi-device";' to your 'a: spi-dev-a@0' node. 

    Now you can access your SPI device:

    #include <zephyr/drivers/spi.h>
    
    spi_device_callback(const struct device *dev, int result, void *data)
    {
        //Get result and data
    }
    
    void main(void)
    {
        const struct device *const spi_device = DEVICE_DT_GET(DT_NODELABEL(a));
        
        if (!device_is_ready(spi_device)) {
        		printk("%s: device not ready.\n", spi_device->name);
        		return;
        	}
        
        const struct spi_driver_api *spi_device_api = (const struct spi_driver_api *)spi_device->api;
        
        const struct spi_config spi_device_cfg;
        spi_device_cfg.operation = SPI_OP_MODE_SLAVE;
        
        uint8_t spi_device_tx_buf[128];
        struct spi_buf tx_buf;
        tx_buf.buf = &spi_device_tx_buf;
        tx_buf.len = sizeof(spi_device_tx_buf);
        const struct spi_buf_set tx_bufs;
        tx_bufs.buffers = &tx_buf;
        tx_bufs.count = 1;
        
        uint8_t spi_device_rx_buf[128];
        struct spi_buf rx_buf;
        rx_buf.buf = &spi_device_rx_buf;
        rx_buf.len = sizeof(spi_device_rx_buf);
        const struct spi_buf_set rx_bufs;
        rx_bufs.buffers = &rx_buf;
        rx_bufs.count = 1;
        
        int rc = 0;
        rc = spi_device_api.transceive_async(spi_device, &spi_device_cfg, &tx_bufs, &rx_bufs, spi_device_callback, NULL);
    }



  • Thank you Haakon - this makes a lot of sense.

    I am porting existing ncs 1.6.0 based code which makes direct use of nrfx_spis calls. The device agnostic approach you show here sure is a lot more easy on the eye!

    I will refactor our SPIS code accordingly and swing by here again and close the ticket when I have verified functionality.

Reply Children
  • If you don't have a very specific reason to use the nrfx drivers directly the zephyr device api is really useful when interfacing with devices on the various serial buses, especially when there already exists a driver for that device. 

    The NCS sdk has come a long way since 1.6.0, and I hope that you'll have the same experience developing on this platform as I have :)

    Note that the code in by previous instructions might not compile out of the box, but it should be fairly close. I find that studying other device drivers in zephyr helpful in understanding how to use or write new ones. 


Related