The Serial Peripheral Interface (SPI) is a full-duplex, synchronous, four-wire serial communication interface. SPI devices use a master-slave architecture with a single master. Multiple slave devices are supported with individual slave select (SS) lines.

The ConnectCore platforms have several SPI interfaces. You can find more information in Hardware reference manuals and Serial Peripheral Interface (SPI).

Digi adds an API to Linux to manage these SPI interfaces as master, able to communicate with slave devices. To use this API, include the following header file:

#include <libdigiapix/spi.h>

You can configure them and communicate with other SPI slave devices.

Request an SPI

Before using an SPI, you must request that interface to ensure it is available on the system. This request creates a link between the master device and the slave device. You can request an SPI with one of the following functions:

Function Description

spi_t *ldx_spi_request(unsigned int spi_device, unsigned int spi_slave)

Requests an SPI by its device and slave number. The device number (spi_device) is the assigned Linux number to the SPI interface, and the slave number (spi_slave) is the chip select (SS) index of the slave device to communicate with.

It returns a pointer to spi_t on success, NULL on error.

spi_t *ldx_spi_request_by_alias(char const * const spi_alias)

Requests an SPI by its alias name. The SPI alias mapping must be defined in the /etc/libdigiapix.conf file under the [SPI] section. See Establish SPI aliases.

It returns a pointer to spi_t on success, NULL on error.

The requested SPI must be freed once it is no longer needed. See Free an SPI.

Both functions may fail and return NULL for the following reasons:

  • The provided device and slave numbers, or the associated device and slave numbers to the given alias do not exist.

  • The API encountered problems allocating memory to initialize the SPI. Your system may have run out of resources.

Request an SPI
[...]

/* Request an SPI using its alias */
spi_t spi1 = ldx_spi_request_by_alias("DEFAULT_SPI");

/* Request an SPI using device = 1 and slave = 0 */
spi_t spi2 = ldx_spi_request(1, 0);

printf("SPI1 uses device %d and slave %d\n", spi1->spi_device, spi1->spi_slave);
printf("SPI2 uses device %d and slave %d\n", spi2->spi_device, spi2->spi_slave);

[...]

Establish SPI aliases

To help you identify the SPI interfaces of your design, you can assign aliases to your SPI Linux IDs. Map the assigned device number, slave, and name in the /etc/libdigiapix.conf file:

  1. Add a section called [SPI], if one doesn’t already exist.

  2. Below the section name, add the list of mapped SPI interfaces following the format:

    <alias> = <spi_device>,<spi_slave>

    Where:

    • <alias> is the human-readable name for the SPI.

    • <spi_device> is the SPI device number.

    • <spi_slave> is the SPI slave index to communicate with (SS) .

Example SPI section
[SPI]

# SPI-1 on SPI board connector, slave 0.
DEFAULT_SPI = 0,0

For example, using the configuration above, you can request an SPI with the API using DEFAULT_SPI alias. See Request an SPI.

You can get the SPI device number and the slave number associated with an alias using these functions:

int ldx_spi_get_device(const char * const spi_alias)
int ldx_spi_get_slave(const char * const spi_alias)
For information on including libdigiapix.conf in your Digi Embedded Yocto images, see Define interface aliases.

List available SPI interfaces

You can determine the list of available SPI interfaces on the target with the ldx_spi_list_available_devices() API function:

int ldx_spi_list_available_devices(uint8_t **devices)

The function returns the number of available SPI interfaces, or -1 if there is an error.

You must free the devices pointer when ldx_spi_list_available_devices() returns a value greater than 0.

Get SPI device list
[...]

uint8_t *devices = NULL;
int i, devices_number = 0;

[...]

/* Retrieve the list of available SPI interfaces */
devices_number = ldx_spi_list_available_devices(&devices);

if (devices_number > 0) {
	printf("The target has %d available SPI devices:\n", devices_number);
	for (i = 0; i < devices_number; i++) {
		printf(" - SPI-%d\n", devices[i]);
	}
} else {
	printf("The target does not have any SPI interface available\n");
}
[...]

free(devices);

[...]

List available slaves of an SPI interface

Besides listing the available SPI interfaces, you can also get the number of available slaves for an specific SPI interface:

int ldx_spi_list_available_slaves(uint8_t spi_device, uint8_t **slaves)

ldx_spi_list_available_slaves function returns the number of available slaves for the given SPI interface, or -1 if there is an error.

You must free the slaves pointer when ldx_spi_list_available_slaves() returns a value greater than 0.
Get SPI device slaves list
[...]

#define DEFAULT_SPI_DEVICE  0

[...]

uint8_t *slaves = NULL;
int i, slaves_number = 0;

[...]

/* Retrieve the list of available SPI slaves for SPI Device 0*/
slaves_number = ldx_spi_list_available_slaves(DEFAULT_SPI_DEVICE, &slaves);

if (slaves_number > 0) {
	printf("SPI interface %d has %d available slaves:\n", DEFAULT_SPI_DEVICE, slaves_number);
	for (i = 0; i < slaves_number; i++) {
		printf(" - SPI-%d.%d\n", DEFAULT_SPI_DEVICE, slaves[i]);
	}
} else {
	printf("SPI interface %d does not have available slaves\n", DEFAULT_SPI_DEVICE);
}

[...]

free(slaves);

[...]

Free an SPI

You must free a requested SPI interface when it is not required anymore. To do so, use the ldx_spi_free() function.

int ldx_spi_free(spi_t *spi)
Free a requested SPI interface
[...]

spi_t *spi = ...;

[...]

/* Free SPI once it is not required anymore */
ldx_spi_free(spi);

[...]

Configure SPI communication

You can configure several SPI interface parameters using the SPI API:

Configure the transfer mode

The SPI interface allows you to configure the following transfer parameters:

Clock mode

Clock mode specifies how the clock signal and the data lines are managed during the transfer. Its possible values are:

Clock mode Description

SPI_CLK_MODE_0

CPOL=0 CPHA=0 - The data must be available before the first clock signal rising. The clock idle state is zero. The data on MISO and MOSI lines must be stable while clock is high and can be changed when clock is low. The data is captured on the clock’s low-to-high transition and propagated on high-to-low clock transition.

SPI_CLK_MODE_1

CPOL=0 CPHA=1 - The first clock signal rising can be used to prepare the data. The clock idle state is zero. The data on MISO and MOSI lines must be stable while clock is low and can be changed when clock is high. The data is captured on the clock’s high-to-low transition and propagated on low-to-high clock transition.

SPI_CLK_MODE_2

CPOL=1 CPHA=0 - The data must be available before the first clock signal falling. The clock idle state is one. The data on MISO and MOSI lines must be stable while clock is low and can be changed when clock is high. The data is captured on the clock’s high-to-low transition and propagated on low-to-high clock transition.

SPI_CLK_MODE_3

CPOL=1 CPHA=1 - The first clock signal falling can be used to prepare the data. The clock idle state is one. The data on MISO and MOSI lines must be stable while clock is high and can be changed when clock is low. The data is captured on the clock’s low-to-high transition and propagated on high-to-low clock transition.

Chip select

The chip select configuration determines how this line behaves during the transfer. Its possible values are:

Chip select Description

SPI_CS_ACTIVE_LOW

The chip select is active at low level

SPI_CS_ACTIVE_HIGH

The chip select is active at high level

SPI_CS_NO_CONT

The chip select is not controlled during the transfer

Bit order

The bit order determines which bit is transferred first (MSB or LSB).

Bit order Description

SPI_BO_MSB_FIRST

The first transferred bit is the most significant one

SPI_BO_LSB_FIRST

The first transferred bit is the less significant one

All these configuration parameters are specified within a transfer configuration struct:

Transfer mode struct
/**
 * spi_transfer_cfg_t- Representation of a SPI transfer mode configuration
 *
 * @clk_mode:SPI clock mode
 * @chip_select:SPI chip select configuration
 * @bit_order:SPI bit order
 */
typedef struct {
	spi_clk_mode_t clk_mode;
	spi_cs_t chip_select;
	spi_bo_t bit_order;
} spi_transfer_cfg_t;

You can read and set the SPI transfer mode configuration using these functions:

Function Description

int ldx_spi_get_transfer_mode(spi_t *spi, spi_transfer_cfg_t *transfer_cfg)

Reads and stores the SPI transfer mode of the SPI interface.

This function returns EXIT_SUCCESS on success or SPI_MODE_ERROR if there is any error.

int ldx_spi_set_transfer_mode(spi_t *spi, spi_transfer_cfg_t *transfer_cfg)

Sets the transfer mode configuration of the SPI interface.

This function returns EXIT_SUCCESS on success or SPI_MODE_ERROR if there is any error.

Get and set the SPI transfer mode
[...]

spi_transfer_cfg_t transfer_mode = {0}
spi_t spi = ...;

/* Set the transfer mode parameters */
transfer_mode.clk_mode = SPI_CLK_MODE_0;
transfer_mode.chip_select= SPI_CS_ACTIVE_HIGH;
transfer_mode.bit_order= SPI_BO_MSB_FIRST;

/* Configure the transfer mode */
ldx_spi_set_transfer_mode(spi, &transfer_mode);

[...]

Configure bits per word

SPI transmissions often consist of 8-bit words. However, other word sizes—for example, 16-bit words—are also common. You can read and set the SPI bits-per-word value using these functions:

Function Description

spi_bpw_t ldx_spi_get_bits_per_word(spi_t *spi)

  • Retrieves the SPI bits-per-word:

    • SPI_BPW_8

    • SPI_BPW_16

  • If there is an error, the function returns SPI_BPW_ERROR.

int ldx_spi_set_bits_per_word(spi_t *spi, spi_bpw_t bpw)

  • Sets the SPI bits-per-word:

    • SPI_BPW_8

    • SPI_BPW_16

  • If there is an error, the function returns EXIT_FAILURE.

Get and set the SPI bits-per-word value
[...]

spi_t spi = ...;
/* Configure the bits-per-word to be 16 */
ldx_spi_set_bits_per_word(spi, SPI_BPW_16);

printf("SPI %d.%d uses %d bits-per-word\n", spi->spi_device, spi->spi_slave,
       ldx_spi_get_bits_per_word(spi) == SPI_BPW_8 ? 8 : 16);

[...]

Configure the maximum bus speed

Before the master communicates with the slave, you must configure the maximum speed supported by the slave device. You can read and set the SPI bus speed using these functions:

Function Description

int ldx_spi_get_speed(spi_t *spi)

  • Retrieves the SPI bus speed in Hz.

  • If there is an error, the function returns -1.

int ldx_spi_set_speed(spi_t *spi, unsigned int speed)

  • Sets the SPI bus speed in Hz.

  • The function returns EXIT_SUCCESS on success or EXIT_FAILURE if there is an error.

Get and set the SPI bus speed
[...]

spi_t spi = ...;

/* Configure the bus speed to be 4MHz (4000000Hz) */
ldx_spi_set_speed(spi, 4000000);

printf("SPI %d.%d bus speed is %dHz\n", spi->spi_device, spi->spi_slave, ldx_spi_get_speed(spi));

[...]

Communicate with the SPI interface

The API allows three types of communication with the slave device: read, write, and transfer.

Write data to the SPI interface

You can send data to the SPI slave device using the ldx_spi_write() function:

Function Description

int ldx_spi_write(spi_t *spi, uint8_t *tx_data, unsigned int length)

  • Sends data to the SPI slave device.

  • The data to write is passed in the tx_data buffer.

  • The number of bytes to write are specified with the length parameter.

  • The function returns EXIT_SUCCESS on success or EXIT_FAILURE if there is an error.

Write data to the SPI interface
[...]

#define DATA_SIZE16

[...]

int i = 0;
uint8_t tx_buffer[DATA_SIZE] = {0};
spi_t spi = ...;

/* Fill the tx buffer with random data */
for (i = 0; i < DATA_SIZE; i++) {
	tx_buffer[i] = rand() % 255;
}

printf("Writing %d bytes to SPI %d.%d...\n", DATA_SIZE, spi->spi_device, spi->spi_slave);

ldx_spi_write(spi, tx_buffer, DATA_SIZE);

[...]

Read data from the SPI interface

You can read data from the SPI slave device using the ldx_spi_read() function:

Function Description

int ldx_spi_read(spi_t *spi, uint8_t *rx_data, unsigned int length)

  • Reads data from the SPI slave device.

  • The read data will be stored in the rx_data buffer.

  • The number of bytes to read are specified with the length parameter.

  • The function returns EXIT_SUCCESS on success or EXIT_FAILURE if there is an error

Read data from the SPI interface
[...]

#define DATA_SIZE16

[...]

int i = 0;
uint8_t rx_buffer[DATA_SIZE] = {0};
spi_t spi = ...;

printf("Reading %d bytes from SPI %d.%d...\n", DATA_SIZE, spi->spi_device, spi->spi_slave);

ldx_spi_read(spi, rx_buffer, DATA_SIZE);

for (i = 0; i < DATA_SIZE; i++) {
	printf("rx_data[%d] = 0x%02x\n", i, rx_data[i]);
}

[...]

Transfer data to the SPI interface

You can simultaneously write and read data in one operation using the ldx_spi_transfer() function:

Function Description

int ldx_spi_transfer(spi_t *spi, uint8_t *tx_data, uint8_t *rx_data, unsigned int length)

  • Transfers data with the SPI slave device.

  • The data to write is passed in the tx_data buffer.

  • The read data will be stored in the rx_data buffer.

  • The number of bytes to transfer are specified with the length parameter.

  • The function returns EXIT_SUCCESS on success or EXIT_FAILURE if there is an error

tx_data and rx_data buffer length in bytes must be the value of length.
Transfer data to the SPI interface
[...]

#define DATA_SIZE16

[...]

int i = 0;
uint8_t tx_buffer[DATA_SIZE] = {0};
uint8_t rx_buffer[DATA_SIZE] = {0};
spi_t spi = ...;

/* Fill the tx buffer with random data */
for (i = 0; i < DATA_SIZE; i++) {
	tx_buffer[i] = rand() % 255;
}
printf("Transferring %d bytes with SPI %d.%d...\n", DATA_SIZE, spi->spi_device, spi->spi_slave);

ldx_spi_transfer(spi, tx_buffer, rx_buffer, DATA_SIZE);

for (i = 0; i < DATA_SIZE; i++) {
	printf("rx_data[%d] = 0x%02x\n", i, rx_data[i]);
}

[...]

SPI example

This example writes the page 0 of an external EEPROM memory with random data and then reads the data back to validate it.

You can import the example in Eclipse using the Digi Embeddded Yocto plugin. For more information, see Create a new DEY sample project. This example is included in Digi Embedded Yocto. Go to GitHub to see the application source code.