nRF9160 SPI Loopback SDK 2.3.0

Overview

Hi

I have the nRF9160DK and I have had a really good experience with it so far getting MCU, Modem, LTE, GNSS, Logging and CoAP working.

The challenge is now that I have to connect another sensor development board, which needs to communicate with nRF9160 as an SPI slave. To verify that I can get SPI to work as intended, and transmit reliably I have decided to make a small loopback test. 

The plan is to activate SPI3 as master and SPI2 as slave, then configure SPI3 MISO <---> SPI2 MOSI and vice versa, and last SPI3 SCK <---> SPI2 SCK to the same pin. I leave Chip-select out as there's only one slave. Not sure if this is alright?

My problems are:

  • Is the described idea even possible?
    • If not then how would you suggest achieving this SPI verification?
  • In my current test logging through default, UART via USB doesn't work.
    • Commenting out two lines with 'spi_read_dt()' and 'spi_write_dt()' makes logging work again for some reason.
  • Is my prj.conf correct for this SPI configuration?
  • Is my devicetree overlay file correct for this SPI configuration?
    • In general, my understanding of devicetrees struggles, especially the use of 'pinctrl' over the deprecated miso-pin, mosi-pin etc.

Code explanation

The program starts by getting the device pointers to spi2 and spi3, and then specifying a global spi spec pointer, so that it can be used in the thread reading the spi2. It defines a semaphore to make sure that the echo thread doesn't read before the spi2 spec is set up properly.

The function 'echo()' is the entry point of the thread which just, after taking the semaphore, reads from the spi2 into a local buffer and prints the received buffer to the terminal.

'Main()' makes sure to set up spi2 and spi3 with the same configuration except for master and slave modes. Then it initializes the LEDs, so LED1 and LED2 can visualize where we are in the code. Then we're checking that spi2 and spi3 is ready(spi2 already a pointer and spi3 isn't). We're now turning the first LED on, set up the tx buffer and then wait for a second, so that the LED can properly light up before we try to write to spi3. If an error occurs we light up LED4, and give it around 5 secs so it's clearly visible.

What happens

It starts up, but doesn't print anything to the serial terminal. Then the first LED lights up for about 1 second, and this cycle will continue forever.

Files

2860.prj.conf

/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/devicetree.h>
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>

#include <dk_buttons_and_leds.h>

#define MAIN_LOOP_SLEEP 30000
#define SPI_TEST_MESSAGE "Hello from SPI"

// Logging
LOG_MODULE_REGISTER(SPITest, LOG_LEVEL_DBG);

// Retrieve SPI Nodes
static const struct device* dev_spi2 = DEVICE_DT_GET(DT_NODELABEL(spi2));
static const struct device* dev_spi3 = DEVICE_DT_GET(DT_NODELABEL(spi3));
static struct spi_dt_spec* spi2;

// Define semaphores
K_SEM_DEFINE(sem_spi, 0, 1);

// Prototypes
void echo();

// Define SPI echo thread
K_THREAD_DEFINE(spi_echo, 1024, echo, NULL, NULL, NULL, 7, 0, 10);

void echo()
{
	printk("Echo thread started\n");
	k_sem_take(&sem_spi, K_FOREVER);
	printk("Echo thread taken semaphore\n");

	uint8_t buf[128];
	struct spi_buf rx_buf = {
		.buf = buf,
		.len = sizeof(buf)
	};
	struct spi_buf_set rx_buf_set = {
		.buffers = &rx_buf,
		.count = 1
	};

	spi_read_dt((const struct spi_dt_spec*)spi2, &rx_buf_set);

	printk("Received: %s\n", buf);
}

void main(void)
{
	printk("Hello World! %s\n", CONFIG_BOARD);

	struct spi_dt_spec _spi2 = {
		.bus = dev_spi2,
		.config = {
			.frequency = 5000,
			.operation = SPI_OP_MODE_SLAVE | SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
			.slave = 0,
			.cs = NULL
		}
	};
	spi2 = &_spi2;

	const struct spi_dt_spec spi3 = {
		.bus = dev_spi3,
		.config = {
			.frequency = 5000,
			.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
			.slave = 0,
			.cs = NULL
		}
	};

	// Init LEDs
	int err = dk_leds_init();
	if (err)
	{
		printk("LEDs couldn't be initialized");
		return;
	}

	// Check if SPI is ready
	if (!spi_is_ready_dt(&spi3) && !spi_is_ready_dt(spi2))
	{
		LOG_ERR("SPI wasn't ready!");
		return;
	}

	k_sem_give(&sem_spi);
	
	printk("SPI is ready\n");
	dk_set_led_on(DK_LED1);

	printk("Trying to write something\n");

	const struct spi_buf tx_buf = {
		.buf = SPI_TEST_MESSAGE,
		.len = sizeof(SPI_TEST_MESSAGE)
	};
	const struct spi_buf_set tx_buf_set = {
		.buffers = &tx_buf,
		.count = 1
	};

	k_msleep(1000); // Make sure LED1 is visible before crash
	err = spi_write_dt(&spi3, &tx_buf_set);
	if (err)
	{
		printk("An error happened during transceive, code %d", errno);
		dk_set_led_on(DK_LED4);
		return;
	}
	dk_set_led_on(DK_LED2);

	printk("Done writing to SPI\n");

	while(1)
	{
		k_msleep(MAIN_LOOP_SLEEP);
	}
}

  • Hello,

    I have the nRF9160DK and I have had a really good experience with it so far getting MCU, Modem, LTE, GNSS, Logging and CoAP working.

    I am happy to hear that you have had a good experience with the kit so far.

    The chip select line must be controlled dynamically be the SPIM to allow the SPIS to operate. If it is always asserted, the CPU will not be able to update the DMA buffer, see Semaphore operation

    Is the described idea even possible?

    Yes. A colleague of mine has made a SPI loopback example which he has made available on Github: https://github.com/too1/ncs-spi-master-slave-example.  

    In my current test logging through default, UART via USB doesn't work.
    • Commenting out two lines with 'spi_read_dt()' and 'spi_write_dt()' makes logging work again for some reason.

    Without these lines commented, please try to build with CONFIG_RESET_ON_FATAL_ERROR=n and see if you get a crashlog on UART.

    • Is my devicetree overlay file correct for this SPI configuration?
      • In general, my understanding of devicetrees struggles, especially the use of 'pinctrl' over the deprecated miso-pin, mosi-pin etc.

    It looks to be OK. I was not able to spot any errors. But I am not sure about the pinout, are they not connected to the flash on your board (External memory)? 

    Best regards,

    Vidar

  • Hi Vidar, thanks for the answer! :)

    Below is a screenshot of my serial terminal running baud 9600 as specified in the overlay for uart0. I flashed the same program to the board, but with CONFIG_RESET_ON_FATAL_ERROR=n. On initial reset the first two question marks, and then on another manual reset with reset button, then the other two came. For some reason it's like it turns off the uart logging backend?

    Regarding the DTS overlay pinout - I've just now read about the external memory, but I'm not using this, and I just planned to use that SPI interface in my loopback test. I guess that the external memory is turned off by default, so I'm free to use those SPI registers for communication with something else than the external memory?

    I've looked at your colleagues example, and I can see that I've done some things right, but there's also a couple of things I don't understand in his devicetree. Below is a screenshot of a small part of his devicetree, here I don't understand why SPIS_CSN pin number(14) doesn't match 'cs-gpios' pin number(28). Is it because he connect pin 14 and 28 with a physical wire?

    Lastly, I dont understand the 'reg_my_spi_master'. What does it do, and why is it necessary. I see it in his code, but unfortunately I still don't get it? :)

    Picture of my new devicetree overlay below. I've added the SPIS_CSN to the spi2 pinctrl, and added 'cs-gpios' to the spi3 with the same pin number as SPIS_CSN, because I intend to not use any wires. If that's possible of course.

    I think I need to get the logging to work, and then be sure my setup with no wires and spi2 and spi3 sharing pins is possible, before I update all the code and try again.

    Hope my answer makes sense Relaxed

  • Hello,

    Unfortunatly it's not possible for pins to overlap functionality, even though I see the idea in this case. So you do need to use two sets of gpios for spi master and slave in this case (so a total of 8pins).

    Ref reason behind the 'spi-dev-a@0' { reg = <0>; } 

    See:
    https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/build/dts/intro.html#unit-address-examples 

    "An index representing the peripheral’s chip select line number. (If there is no chip select line, 0 is used.)"

    Also see:
    https://github.com/nrfconnect/sdk-zephyr/blob/v3.2.99-ncs2/include/zephyr/devicetree/spi.h#L83 

    Best regards,
    Kenneth

Related