SPI Communication
In this chapter, we will learn how to communicate with external devices using the SPI bus at the application layer.
Sample program : Code.zip
1. SPI Subsystem
In the Linux operating system, the SPI subsystem is a crucial driver framework used to manage and control various external devices connected via the SPI bus. For more detailed information about the SPI subsystem, you can refer to the <Linux kernel source>/Documentation/spi
directory. Key components of the SPI subsystem include:
- sysfs Interface: The SPI subsystem provides a set of files and directories through sysfs for configuring and managing SPI buses and SPI devices. These files and directories are typically located under
/sys/class/spi_master
and/sys/bus/spi/devices
, allowing users to view and modify the properties of SPI devices. - Device Nodes: Each connected SPI device creates a device node under the
/dev
directory, enabling communication with the device from user-space programs using standard file I/O operations. Typically, these device nodes have names like/dev/spidevX.Y
, where X represents the SPI bus number, and Y represents the SPI device number.
2. Viewing SPI (Shell)
2.1 Pinout
To enable I2C, refer to the PWM section.
LuckFox Pico Diagram:
LuckFox Pico Mini A/B Diagram:
LuckFox Pico Plus Diagram:
2.2 Viewing Devices
In the /sys/bus/spi/devices
directory, each SPI device has its own folder. These folders typically include "spi" and the device number in their names. For example, /sys/bus/spi/devices/spi0.0
represents device 0 on SPI bus 0. To view the available SPI buses in the system, you can use the following command:
# ls /sys/bus/spi/devices/
spi2.0 spi0.0
3. SPI Communication (Python Program)
3.1 Complete Code
With the following program, SPI communication can be implemented.
import spidev
def main():
tx_buffer = [ord(char) for char in "hello world!"]
rx_buffer = [0] * len(tx_buffer)
try:
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
rx_buffer = spi.xfer2(tx_buffer[:])
print("tx_buffer:\n\r", ''.join(map(chr, tx_buffer)))
print("rx_buffer:\n\r", ''.join(map(chr, rx_buffer)))
except Exception as e:
print(f"An error occurred: {e}")
finally:
if spi:
spi.close()
if __name__ == "__main__":
main()
3.2 Open SPI Device
This code uses the SpiDev
class from the spidev
library to create an SPI object. By calling the open
method, the SPI bus and device number are specified, which are SPI bus 0 and device 0 here. The maximum transfer speed of the SPI is set to 1,000,000 Hz, i.e., 1 MHz. This step is to configure the basic parameters of the SPI device for subsequent data transmission.
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
3.3 Data Transmission and Reception
This code uses the xfer2
method for SPI data transfer. In this example, tx_buffer
is the data to be sent, and rx_buffer
will store the received data. Note that to ensure the original value of tx_buffer
remains unchanged, a copy of tx_buffer
is passed to xfer2
, i.e., tx_buffer[:]
. Finally, the print
statement prints the sent and received data as strings for user inspection.
rx_buffer = spi.xfer2(tx_buffer[:])
print("tx_buffer:\n\r", ''.join(map(chr, tx_buffer)))
print("rx_buffer:\n\r", ''.join(map(chr, rx_buffer)))
3.4 Run the Program
Use the vi tool to open the file, paste the code, and save it.
# nanoo i2c.py
Run the program.
# python3 i2c.py
Experimental Observations
Connect the MOSI and MISO of SPI and run the program:
4. SPI Communication (C Program)
4.1 ioctl Function
When writing application programs, the ioctl
function is used to configure SPI-related settings. Its function prototype is as follows:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
When using the ioctl
function for SPI communication, commonly used request parameters include:
SPI_IOC_RD_MODE
: Used to read the current mode settings of SPI communication. This request parameter reads mode information into an integer variable for checking the current polarity and phase settings of SPI communication.SPI_IOC_WR_MODE
: Used to set the mode of SPI communication. You need to provide an integer value, typically composed of two binary digits, to represent the polarity and phase of SPI communication.SPI_IOC_RD_BITS_PER_WORD
: Used to read the number of bits per data word. This request parameter reads the bit count information into an integer variable.SPI_IOC_WR_BITS_PER_WORD
: Used to set the number of bits per data word. You need to provide an integer value to specify the number of bits per data word to be sent and received.SPI_IOC_RD_MAX_SPEED_HZ
: Used to read the maximum speed of the SPI bus. This request parameter reads the speed information into an integer variable to check the maximum transfer speed of the current SPI bus.SPI_IOC_WR_MAX_SPEED_HZ
: Used to set the maximum speed of the SPI bus. You need to provide an integer value to specify the maximum transfer speed to be used.SPI_IOC_MESSAGE(N)
: Used to perform read and write operations for SPI transfers. This request parameter requires a pointer to an array ofstruct spi_ioc_transfer
elements, with each element describing an SPI transfer operation, allowing multiple operations to be executed.
4.2 Sample Program
The following program demonstrates SPI communication.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
#define SPI_DEVICE_PATH "/dev/spidev0.0"
int main() {
int spi_file;
uint8_t tx_buffer[50] = "hello world!";
uint8_t rx_buffer[50];
// Open the SPI device
if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1;
}
// Configure SPI mode and bits per word
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1;
}
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1;
}
// Perform SPI transfer
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(tx_buffer),
.delay_usecs = 0,
.speed_hz = 1000000, // SPI speed in Hz
.bits_per_word = 8,
};
if (ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("Failed to perform SPI transfer");
close(spi_file);
return -1;
}
/* Print tx_buffer and rx_buffer*/
printf("\rtx_buffer: \n %s\n ", tx_buffer);
printf("\rrx_buffer: \n %s\n ", rx_buffer);
// Close the SPI device
close(spi_file);
return 0;
}
4.3 File Paths
This line of code defines a macro used to store the path to the SPI device file.
#define SPI_DEVICE_PATH "/dev/spidev0.0"
4.4 Opening the SPI Device
This section of code attempts to open the specified SPI device file.
// Open the SPI device
if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1;
}
4.5 Configuring SPI
This code is used to configure the SPI communication mode as SPI Mode 0 (clock polarity 0, clock phase 0) and set the word size to 8 bits to ensure the correctness and consistency of SPI communication.
// Configure SPI mode and bits per word
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1;
}
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1;
}
It defines a spi_ioc_transfer
structure variable named transfer
for configuring the parameters of the SPI transfer. It specifies the data buffers for the transfer, the length of the transfer, the delay, the SPI speed in Hertz, and the word size. This structure will be passed to the SPI_IOC_MESSAGE ioctl
function to perform the SPI transfer.
// Perform SPI transfer
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(tx_buffer),
.delay_usecs = 0,
.speed_hz = 1000000, // SPI speed in Hz
.bits_per_word = 8,
};
4.6 Sending Data
This code uses the ioctl
function to perform the SPI transfer. It specifies the number of transfers as 1 using the SPI_IOC_MESSAGE(1)
macro, with the third argument being the address of the transfer
structure variable configured earlier. If the SPI transfer fails, an error message will be printed, and the SPI device file descriptor will be closed.
if (ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("Failed to perform SPI transfer");
close(spi_file);
return -1;
}
4.7 Cross-Compilation
Specify the Cross-Compilation Tool
First, you need to add the path to the cross-compilation tool to the system's
PATH
environment variable so that you can use the cross-compilation tool from anywhere. You can add the following line to your shell configuration file (usually~/.bashrc
or~/.bash_profile
or~/.zshrc
, depending on your shell). Note that the path afterPATH=
should point to the directory where the cross-compilation tool is located.gcc path
<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
Open the shell configuration file.
nano ~/.bashrc
Add the path of the cross-compilation tool to the system's PATH environment variable. Replace
<SDK Directory>
with your own SDK path, such as/home/luckfox/luckfox-pico/
.export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
Reload the shell configuration file to apply the changes:
source ~/.bashrc
Compile the Program Using the Cross-Compilation Tool
arm-rockchip830-linux-uclibcgnueabihf-gcc spi.c -o spi
After successful cross-compilation, an executable file that can run on the development board will be generated in the current directory.
# ls
spi spi.c
4.8 Running the Program
File Transfer
First, transfer the
spi
program from the virtual machine to Windows, and then transfer it to the development board via TFTP or ADB. Here are the steps to transfer the file from Windows to the development board using ADB:adb push path_to_file destination_on_development_board
eg: (Transferring the `spi` file from the current directory to the root directory of the development board)
adb push spi /Running the Program
Modify the permissions of the
spi
file and then run the program:# chmod 777 spi
# ./spiExperimental Observations
Connect SPI's MOSI and MISO and run the program: