| nRF5 SDK v12.0.0 |

Choose documentation: | nRF5 SDK | S130 SoftDevice API | S132 SoftDevice API | S212 SoftDevice API | S332 SoftDevice API

All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
SPI master

The SPI master driver includes two layers: the hardware access layer (HAL) and the driver layer (DRV).

The hardware access layer provides basic APIs for accessing the registers of the SPI and SPIM peripherals. See the API documentation for the SPI HAL and SPIM HAL for details.

The driver layer provides APIs on a higher level than the HAL. See the API documentation for the SPI master driver for details.

Key features include:

- Multi-instance support.
- Common API for two types of peripherals: legacy peripherals (SPI) and peripherals using EasyDMA (SPIM). The type of peripheral that is used can be configured independently for each driver instance in `sdk_config.h`.
- Two modes of data transfer: blocking and non-blocking (with end notification done via an event handler that is provided during initialization).
- EasyDMA and legacy support (SPIM and SPI).
- Supports triggering transfer from PPI (SPIM only).
- Supports post-incrementation of buffer addresses and repeated transfers (SPIM only).

Note that peripherals using EasyDMA can work only with buffers that are placed in the Data RAM region. Under certain circumstances, compilers might choose to use a different region for data placement and, for example, place a constant buffer in the code FLASH. In such a case, the SPIM peripheral cannot be used to transfer data from the buffer.

To quickly get started, use the sample code from the SPI Master Example.

# Driver configuration

The SPI master driver can use multiple instances of the SPI/SPIM peripherals, and it provides a common API for both peripheral types. The instances of the peripherals that are to be assigned to the driver must be selected statically in `sdk_config.h`. In the same way, you configure whether a given instance uses EasyDMA (thus whether SPIM or SPI is used).

For example:

#define SPI0_ENABLED 1
#define SPI0_USE_EASY_DMA 0

For enabled instances, you must provide the default configuration settings that are used by the NRF_DRV_SPI_DEFAULT_CONFIG macro. These settings are also defined in `sdk_config.h`, for example:

#define SPI0_CONFIG_SCK_PIN 2
#define SPI0_CONFIG_MOSI_PIN 3
#define SPI0_CONFIG_MISO_PIN 4
#define SPI0_CONFIG_IRQ_PRIORITY APP_IRQ_PRIORITY_LOW

# Basic usage

Call the NRF_DRV_SPI_INSTANCE macro with the ID of the peripheral instance that you want to use to create a driver instance. For example, this code will create a driver instance that uses the SPI0 or SPIM0 peripheral (depending on the value assigned to SPI0_CONFIG_USE_EASY_DMA in `sdk_config.h`):

static const nrf_drv_spi_t m_spi_master_0 = NRF_DRV_SPI_INSTANCE(0);

Next, call nrf_drv_spi_init to initialize and configure the instance. See the nrf_drv_spi_config_t structure documentation for possible configuration options.

For example:

uint32_t err_code;
nrf_drv_spi_config_t config = NRF_DRV_SPI_DEFAULT_CONFIG(0);
config.frequency = NRF_DRV_SPI_FREQ_1M;
config.mode = NRF_DRV_SPI_MODE_3;
config.bit_order = NRF_DRV_SPI_BIT_ORDER_LSB_FIRST;
err_code = nrf_drv_spi_init(&m_spi_master_0, &config, NULL);
if (err_code != NRF_SUCCESS)
{
// Initialization failed. Take recovery action.
}

After successful initialization, you can call nrf_drv_spi_transfer to request SPI transfers.

The SPI master driver can automatically control the Slave Select signal (see nrf_drv_spi_config_t::ss_pin). This feature can be used only if there is exactly one slave that is activated by the low state of the Slave Select signal. If there are multiple slaves or the Slave Select signal must be active high, the signal must be controlled externally.

When the SPI master driver instance is no longer needed or its configuration must be changed, call nrf_drv_spi_uninit.

# Advanced usage

In non-blocking mode, you can use the nrf_drv_spi_xfer function that supports complex transfers. As parameters, nrf_drv_spi_xfer takes a pointer to a transfer descriptor (nrf_drv_spi_xfer_desc_t) and flags that enable more features.

The following sections show complex scenarios that use different transfer types. These scenarios are not supported with SPI, but require SPIM.

## Starting a transfer from PPI

You can use nrf_drv_spi_xfer to set up a transfer and start it synchronously using PPI. To do so, specify the NRF_DRV_SPI_FLAG_HOLD_XFER flag and use nrf_drv_spi_start_task_get to get the address of the task that starts the transfer.

The following example shows how to configure the driver to setup a transfer without starting it:

nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(p_tx_data, tx_length, p_rx_data, rx_length);
ret_code_t ret = nrf_drv_spi_xfer(&spi, &xfer, NRF_DRV_SPI_FLAG_HOLD_XFER);
// SPIM is now configured and ready to be started.
if (ret == NRF_SUCCESS)
{
uint32_t start_tsk_addr = nrf_drv_spi_start_task_get(&spi);
// Set up PPI to trigger the transfer.
}

## Repeated transfers

You can configure the SPI driver to perform a certain amount of PPI-triggered transfers. To do so, specify the NRF_DRV_SPI_FLAG_REPEATED_XFER flag. The driver will not put the instance into busy state in this case, because in this mode, you control when the sequence of transfers is complete. Use the END event to count the number of completed transfers. To get the address of the END event, use nrf_drv_spi_end_event_get.

Specify the NRF_DRV_SPI_FLAG_TX_POSTINC and NRF_DRV_SPI_FLAG_RX_POSTINC flags to enable post-incrementation of buffer addresses.

Additionally, you can specify the NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER flag to disable calling the user event handler after each transfer completion. If you specify this flag, transfer completion interrupts are disabled if EasyDMA and SPIM is used.

The following example shows how to set up a sequence of SPI transfers that will be triggered using PPI (for example, using RTC or TIMER compare events). A single transfer consists of a transmission of 1 byte and a reception of 3 bytes. The TX data is repeated for all transfers, but the RX data from all transfers is collected in the buffer. Therefore, NRF_DRV_SPI_FLAG_RX_POSTINC is set so that the buffer address for the received data is incremented. The end of sequence is controlled externally, and the processing of data is postponed until the end of the sequence (the user event handler is disabled).

nrf_drv_spi_xfer_desc_t xfer = NRF_DRV_SPI_XFER_TRX(p_tx_data, 1, p_rx_data, 3);
uint32_t flags = NRF_DRV_SPI_FLAG_HOLD_XFER |
NRF_DRV_SPI_FLAG_RX_POSTINC |
NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER |
NRF_DRV_SPI_FLAG_REPEATED_XFER;
ret_code_t ret = nrf_drv_spi_xfer(&spi, &xfer, flags);
// SPIM is now configured and ready to be started.
if (ret == NRF_SUCCESS)
{
uint32_t start_tsk_addr = nrf_drv_spi_start_task_get(&spi);
// Set up PPI to trigger the transfer.
uint32_t end_evt_addr = nrf_drv_spi_end_event_get(&spi);
// Set up PPI to count the number of transfers.
}

In this example, there is no interrupt from SPI unless an error condition is detected.

# Events

When a transfer is complete, which means that the requested amount of data was transferred or an error condition was detected, the driver generates an event (unless this was suppressed with the NRF_DRV_SPI_FLAG_NO_XFER_EVT_HANDLER flag). This event contains the transfer descriptor.

It is possible to start another transfer from the event handler context.

There is no context blocking in the interrupt handler. Therefore, it is assumed that the driver API will not be called from a context with higher priority than the instance interrupt, because this might cause driver failure.