Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

Zephyr SPI API

v2.8.x – v2.7.0

The nRF Connect SDK contains the Zephyr SPI API to interface with the SPI peripheral, and we will use it in the exercises in this lesson.

The choice of the API to use with external peripherals depends on the needs and requirements of the application and the capabilities of the peripheral device. Similarly, it is recommended to use a sensor API to communicate with the sensor and some graphics libraries (like LVGL and others) to communicate with a TFT screen. Nonetheless, the emphasis of this lesson is on learning and utilizing raw SPI transactions, which those APIs ultimately connect to for the SPI communication with the peripheral. Therefore, for our exercises, we will rely on the Zephyr SPI API, which will provide a solid understanding of using SPI in Zephyr on Nordic chips.

Enabling driver

Enable the SPI driver by adding the following Kconfig to the prj.conf file:

CONFIG_SPI=y
Kconfig

In the source code, we include the header file of the SPI API.

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

Changing the devicetree

First, we must add the SPI slave device on the SPI node in the devicetree using an overlay file. Overlay files define which SPI controller we are using, the device bindings, status, and the configurations to be used for MOSI, MISO and SCLK pins. As per bindings, we can specify other properties for the slave, like max-clock speed. We also specify which driver to use for this device in the compatible property. A basic overlay is shown below that uses the spi1 controller, nordic-spi bindings, is active (status okay), defines a CS pin on gpio0 with the active-low flag and uses the default pin configuration of spi1 for MISO, MOSI and SCKL. The pin configuration for the active and sleep mode (spi1_default and spi1_sleep respectively) can be configured using pinctrl and is not shown here for conciseness. You will find a complete overlay with pinctrl defined in the hands-on exercises.

A general SPI device, gendev, is added as a subnode of the spi1 controller in the code snippet below.

&spi1 {
        compatible = "nordic,nrf-spi";
        status = "okay";
        cs-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
        pinctrl-0 = <&spi1_default>;
        pinctrl-1 = <&spi1_sleep>;
        pinctrl-names = "default", "sleep";
        gendev: gendev@0 {
                compatible = "vnd,spi-device";
                reg = <0>;
                spi-max-frequency = <1600000>;
                label = "GenDev";
        };
};
Devicetree

Initializing the device

The SPI API has an API-specific struct spi_dt_spec, with the following signature

This structure contains the device pointer for the SPI device, const struct device *bus, and the slave specific configuration spi_config config.

struct spi_config has the following signature

  • frequency:The clock-frequency for SPI communication.
  • operation: Operation flags, refer to the API documentation for different flags defined and their bit positions.
  • slave: The number of the slave device on the bus.
  • cs:The GPIO chip-select line.

To retrieve this structure, we will use the API-specific function SPI_DT_SPEC_GET(), which has the following signature

In the following code snippet, we retrieve the device structure for the gendev SPI slave that we added in the overlay file, with the SPI operation SPI_WORD_SET(8) and SPI_TRANSFER_MSB, which here is to say that the spi-word-size is 8-bit and the MSB should be transferred first (that is how the data over SPI lines should be interpreted).

#define SPIOP      SPI_WORD_SET(8) | SPI_TRANSFER_MSB

struct spi_dt_spec spispec = SPI_DT_SPEC_GET(DT_NODELABEL(gendev), SPIOP, 0);
C

Lastly, we will check if the SPI device is ready using the API-specific function spi_is_ready_dt(), which has the following signature

err = spi_is_ready_dt(&spispec);
if (!err) {
	LOG_ERR("Error: SPI device is not ready, err: %d", err);
	return 0;
}
C

SPI read and write

To read and write data to and from an SPI bus, we have the functions spi_read_dt(), spi_write_dt(), and spi_transceive_dt(). They are very similar, except that spi_read_dt only performs the read operation, spi_write_dt only performs the write operation, and spi_transceive_dt performs both read and write operations.

These functions have the following signatures

Notice that all functions take in the SPI-specific device structure spi_dt_spec, and one or two buffer pointers, for the transmission and the reception.

Below is an example of using the spi_transceive_dt function

uint8_t tx_buffer = 0x88;
struct spi_buf tx_spi_buf		= {.buf = (void *)&tx_buffer, .len = 1};
struct spi_buf_set tx_spi_buf_set 	= {.buffers = &tx_spi_buf, .count = 1};
struct spi_buf rx_spi_bufs 		= {.buf = data, .len = size};
struct spi_buf_set rx_spi_buf_set	= {.buffers = &rx_spi_bufs, .count = 1};


err = spi_transceive_dt(&spispec, &tx_spi_buf_set, &rx_spi_buf_set);
if (err < 0) {
	LOG_ERR("spi_transceive_dt() failed, err: %d", err);
	return err;
}
C

The same procedure can be followed if only reading or writing is required using spi_read_dt or spi_write_dt.

v2.6.2 -v2.5.2

The nRF Connect SDK contains the Zephyr SPI API to interface with the SPI peripheral, and we will use it in the exercises in this lesson.

The choice of the API to use with external peripherals depends on the needs and requirements of the application and the capabilities of the peripheral device. Similarly, it is recommended to use a sensor API to communicate with the sensor and some graphics libraries (like LVGL and others) to communicate with a TFT screen. Nonetheless, the emphasis of this lesson is on learning and utilizing raw SPI transactions, which those APIs ultimately connect to for the SPI communication with the peripheral. Therefore, for our exercises, we will rely on the Zephyr SPI API, which will provide a solid understanding of using SPI in Zephyr on Nordic chips.

Enabling driver

Enable the SPI driver by adding the following Kconfig to the prj.conf file:

CONFIG_SPI=y
Kconfig

In the source code, we include the header file of the SPI API.

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

Changing the devicetree

First, we must add the SPI slave device on the SPI node in the devicetree using an overlay file. Overlay files define which SPI controller we are using, the device bindings, status, and the configurations to be used for MOSI, MISO and SCLK pins. As per bindings, we can specify other properties for the slave, like max-clock speed. We also specify which driver to use for this device in the compatible property. A basic overlay is shown below that uses the spi1 controller, nordic-spi bindings, is active (status okay), defines a CS pin on gpio0 with the active-low flag and uses the default pin configuration of spi1 for MISO, MOSI and SCKL. The pin configuration for the active and sleep mode (spi1_default and spi1_sleep respectively) can be configured using pinctrl and is not shown here for conciseness. You will find a complete overlay with pinctrl defined in the hands-on exercises.

A general SPI device, gendev, is added as a subnode of the spi1 controller in the code snippet below.

&spi1 {
        compatible = "nordic,nrf-spi";
        status = "okay";
        cs-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>;
        pinctrl-0 = <&spi1_default>;
        pinctrl-1 = <&spi1_sleep>;
        pinctrl-names = "default", "sleep";
        gendev: gendev@0 {
                compatible = "vnd,spi-device";
                reg = <0>;
                spi-max-frequency = <1600000>;
                label = "GenDev";
        };
};
Devicetree

Initializing the device

The SPI API has an API-specific struct spi_dt_spec, with the following signature

This structure contains the device pointer for the SPI device, const struct device *bus, and the slave specific configuration spi_config config.

struct spi_config has the following signature

  • frequency:The clock-frequency for SPI communication.
  • operation: Operation flags, refer to the API documentation for different flags defined and their bit positions.
  • slave: The number of the slave device on the bus.
  • cs:The GPIO chip-select line.

To retrieve this structure, we will use the API-specific function SPI_DT_SPEC_GET(), which has the following signature

In the following code snippet, we retrieve the device structure for the gendev SPI slave that we added in the overlay file, with the SPI operation SPI_WORD_SET(8) and SPI_TRANSFER_MSB, which here is to say that the spi-word-size is 8-bit and the MSB should be transferred first (that is how the data over SPI lines should be interpreted).

#define SPIOP      SPI_WORD_SET(8) | SPI_TRANSFER_MSB

struct spi_dt_spec spispec = SPI_DT_SPEC_GET(DT_NODELABEL(gendev), SPIOP, 0);
C

Lastly, we will check if the SPI device is ready using the API-specific function spi_is_ready_dt(), which has the following signature

err = spi_is_ready_dt(&spispec);
if (!err) {
	LOG_ERR("Error: SPI device is not ready, err: %d", err);
	return 0;
}
C

SPI read and write

To read and write data to and from an SPI bus, we have the functions spi_read_dt(), spi_write_dt(), and spi_transceive_dt(). They are very similar, except that spi_read_dt only performs the read operation, spi_write_dt only performs the write operation, and spi_transceive_dt performs both read and write operations.

These functions have the following signatures

Notice that all functions take in the SPI-specific device structure spi_dt_spec, and one or two buffer pointers, for the transmission and the reception.

Below is an example of using the spi_transceive_dt function

uint8_t tx_buffer = 0x88;
struct spi_buf tx_spi_buf		= {.buf = (void *)&tx_buffer, .len = 1};
struct spi_buf_set tx_spi_buf_set 	= {.buffers = &tx_spi_buf, .count = 1};
struct spi_buf rx_spi_bufs 		= {.buf = data, .len = size};
struct spi_buf_set rx_spi_buf_set	= {.buffers = &rx_spi_bufs, .count = 1};


err = spi_transceive_dt(&spispec, &tx_spi_buf_set, &rx_spi_buf_set);
if (err < 0) {
	LOG_ERR("spi_transceive_dt() failed, err: %d", err);
	return err;
}
C

The same procedure can be followed if only reading or writing is required using spi_read_dt or spi_write_dt.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.