nRF Connect SDK SPI Device Setup And Use on nRF5340-dk

I've went through the Nordic Academy and finished the course, am now trying to continue in learning by using the SPI peripheral. There don't seem to be many examples that contain the the level of info I am looking for. I'm not trying anything fancy yet - planning on using the `arduino_spi` bus already defined in the device tree.

My overlay file is as follows:

Fullscreen
1
2
3
4
5
6
7
&arduino_spi {
spi_module: spi_module@1234 {
compatible = "spi-device";
reg = < 0x1234 >;
spi-max-frequency = < DT_FREQ_M(32) >;
};
};
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

And in main.c I am declaring and using the object as follows:

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <zephyr/kernel.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/printk.h>
#define SPI_NODE DT_NODELABEL(spi_module)
void main(void)
{
printk("Initializing driver! %s\n", CONFIG_BOARD);
static const struct spi_dt_spec spi_dev = SPI_DT_SPEC_GET(SPI_NODE, SPI_OP_MODE_MASTER, 0);
if (!device_is_ready(spi_dev.bus)) {
printk("SPI Bus is not ready!\n");
return;
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

A few direct questions:

  1. Any pointers for what I'm missing and have done wrong here?
  2. Where is the best resource to find this information in the future? I have looked through developer.nordicsemi.com and specifically through the SPI Peripheral page and looked over the API but it seems to be missing what's required versus optional or how to layout the overlay and device tree files for a successful device definition. 
  3. Say the bus to my device communicates with a few internal devices (for example, a SPI modem may have a generic read request device address, generic write request device address, and AT-command device address). Is it necessary (or beneficial, preferred, etc?) to declare those addresses in the overlay file? Or does that end up turning to some raw SPI data manualy sent across the bus?

Thanks in advance for any feedback and direction here! I appreciate it!

- Taylor

  • Hello,

    1. I'm not sure I understand what the program is supposed to do. It looks like you're wanting to use the spi bus as a master. If so, then what slave device are you using (what physical thing is "spi_module" representing)? I'm not sure that setting the compatible="spi-device" will accomplish anything. Usually you would set the compatible to something more specific like a sensor (ex. compatible="invensense,icm42605"). Then if you enabled that sensor through Kconfig, the driver for that sensor would be compiled and that driver would look for that compatible device in the device tree and initialize the device for use.

    2. It's a bit tedious, but looking through the samples for anything that uses spi is a good way to learn how other people have used it.

    3. Declaring things in the overlay file is not technically necessary (it only is if the driver for that device depends on the information to be there). It gives you a good place to quickly change things and skirts around you having to hardcode those properties in a driver. However, for the use case you're talking about (where a device had multiple addresses) the dts binding file (the file that defines which properties you can add to an overlay) does not have anywhere to put that extra information and if you didn't write the driver to expect those extra properties then any extra information in the overlay wouldn't be functional.

  • Thanks for the reply.

    I should clarify that this main.c is obviously incomplete. I'm basically looking to declare the SPI bus as a device that I could then use in the program. I have a handful of SPI modules, none are supported sensors in the zephyr database (and that's ok, I plan on building the API and manually supporting them. That's the ultimate goal of the project I'm working on, verifying and testing out each of the modules). So there is no compatible slave device, I just want raw access to the bus.

    From the examples I've looked over, I don't see how the drivers are declaring the bus to be talked over. The best reference I have is from the academy course where it explained how to use the overlay file to define the bus. 

    I will keep looking through the examples for more SPI examples in the meantime.

  • Okay, I gotcha now. I would check out the sample for the ICM42605 sensor.

    You won't have to worry at all about the bus itself outside of the driver. The driver finds the bus device with SPI_DT_SPEC_INST_GET  and attaches it to your sensor or what have you. For example, here's code from the ICM42605 driver:

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #define ICM42605_DEFINE_CONFIG(index) \
    static const struct icm42605_config icm42605_cfg_##index = { \
    .spi = SPI_DT_SPEC_INST_GET(index, \
    SPI_OP_MODE_MASTER | \
    SPI_MODE_CPOL | \
    SPI_MODE_CPHA | \
    SPI_WORD_SET(8) | \
    SPI_TRANSFER_MSB, \
    0U), \
    .gpio_int = GPIO_DT_SPEC_INST_GET(index, int_gpios), \
    .accel_hz = DT_INST_PROP(index, accel_hz), \
    .gyro_hz = DT_INST_PROP(index, gyro_hz), \
    .accel_fs = DT_INST_ENUM_IDX(index, accel_fs), \
    .gyro_fs = DT_INST_ENUM_IDX(index, gyro_fs), \
    }
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    So when you want to talk to that sensor, the driver says:

    Fullscreen
    1
    result = inv_spi_read(&cfg->spi, REG_FIFO_DATA, drv_data->fifo_data, fifo_count);
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    which you can see is using the "spi" value that was defined with SPI_DT_SPEC_INST_GET.

    All of this code should happen in the driver so you shouldn't have to worry about it in your actual application code. So if you look at the sample for that sensor (no longer in the driver) you talk with the sensor through an API and don't have to worry about the nitty gritty of spi communication:

    Fullscreen
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const char *const label = DT_LABEL(DT_INST(0, invensense_icm42605));
    const struct device *icm42605 = device_get_binding(label);
    struct sensor_value temperature;
    struct sensor_value accel[3];
    struct sensor_value gyro[3];
    int rc = sensor_sample_fetch(icm42605);
    if (rc == 0) {
    rc = sensor_channel_get(dev, SENSOR_CHAN_ACCEL_XYZ, accel);
    }
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX