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 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:
-
Add a section called [SPI], if one doesn’t already exist.
-
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) .
-
[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.
[...]
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. |
[...]
#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)
[...]
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:
/**
* 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. |
[...]
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) |
|
int ldx_spi_set_bits_per_word(spi_t *spi, spi_bpw_t bpw) |
|
[...]
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) |
|
int ldx_spi_set_speed(spi_t *spi, unsigned int 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) |
|
[...]
#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) |
|
[...]
#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) |
|
tx_data and rx_data buffer length in bytes must be the value of length. |
[...]
#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 Embedded 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.