SPI
1. Overview
- SPI stands for Serial Peripheral Interface, a high-speed, full-duplex, synchronous communication bus.
- It operates in a master-slave configuration, where typically one master device is connected to one or more slave devices. When data is transferred bidirectionally, four lines are required, and for unidirectional data transfer, three lines can be used.
- The SPI bus itself consists of four physical connections: two data lines, a clock line, and a chip select line.
- MOSI (Master Out/Slave In) – The line where Omni3576 sends data to the device (sensor).
- MISO (Master In/Slave Out) – The line where the device sends data to Omni3576.
- SCLK (Clock) – The clock signal line.
- SS/CS (Slave Select/Chip Select) – The line used to select the device to which data will be sent.
2. Operating Modes
The Linux kernel uses the combination of CPOL and CPHA to define the four operating modes of SPI:
CPOL=0,CPHA=0 SPI_MODE_0
CPOL=0,CPHA=1 SPI_MODE_1
CPOL=1,CPHA=0 SPI_MODE_2
CPOL=1,CPHA=1 SPI_MODE_3The waveform diagrams for the four SPI operating modes are as follows:



3. SPI Subsystem
In the Linux operating system, the SPI subsystem is a critical driver framework that manages and controls various external devices connected via the SPI bus. More detailed information on the SPI subsystem can be found in 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 the SPI bus and SPI devices. These files and directories are located in the
/sys/class/spi_masterand/sys/bus/spi/devicesdirectories, allowing users to view and modify the attributes of SPI devices. - Device Nodes: Each connected SPI device has a device node created under the
/devdirectory, enabling user-space programs to communicate with the device via standard file I/O operations. Typically, these device nodes are named/dev/spidevX.Y, where X is the SPI bus number and Y is the SPI device number.
3.1 View SPI (Shell)
Luckfox Omni3576 Pinout:

In the
/sys/bus/spi/devicesdirectory, each SPI device has its own folder. The folder names typically include "spi" and the device number, for example,/sys/bus/spi/devices/spi0.0represents device 0 on SPI bus 0. To view the SPI buses present in the system, use the following command:root@luckfox:/home/luckfox# ls /sys/bus/spi/devices/
spi0.0
3.2 SPI Communication (Python Program)
Full Code:
import spidev
import time
import sys
# Open SPI device
def spi_open(bus, device):
spi = spidev.SpiDev()
spi.open(bus, device)
return spi
# Configure SPI parameters
def spi_setup(spi, speed_hz, mode=0, bits_per_word=8):
spi.max_speed_hz = speed_hz
spi.mode = mode
spi.bits_per_word = bits_per_word
print(f"SPI setup complete with speed {speed_hz} Hz, mode {mode}, bits per word {bits_per_word}")
# Perform SPI data transfer
def spi_transfer(spi, tx_data, num_transfers):
for i in range(num_transfers):
rx_data = spi.xfer2(tx_data)
print(f"Transfer {i+1}: Sent {tx_data}, Received {rx_data}")
time.sleep(0.001) # 1ms delay for clear signal analysis
# Main program
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <spi_speed_hz>")
sys.exit(1)
speed_hz = int(sys.argv[1])
spi = spi_open(0, 0) # Use SPI0.0
spi_setup(spi, speed_hz)
# Data to send
tx_data = [0x01, 0x02, 0x03, 0x04]
# Perform 5 transfers
spi_transfer(spi, tx_data, 5)
# Close SPI device
spi.close()Run the program:
python3 spi_test1.py 1000000Experimental phenomenon:

3.3 SPI Communication (C Program)
Full Code:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#define SPI_PATH "/dev/spidev0.0"
#define DELAY_BETWEEN_TESTS_MS 500 // 500 ms delay
#define REPEAT_TRANSMISSION 5 // Number of times to repeat transmission for each frequency
int spi_open(const char* device) {
int fd = open(device, O_RDWR);
if (fd < 0) {
perror("Failed to open SPI device");
}
return fd;
}
int spi_setup(int fd, uint32_t speed) {
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(fd, SPI_IOC_WR_MODE, &mode) == -1) {
perror("Can't set SPI mode");
return -1;
}
if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) == -1) {
perror("Can't set bits per word");
return -1;
}
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) == -1) {
perror("Can't set max speed HZ");
return -1;
}
return 0;
}
int spi_transfer(int fd, uint32_t speed) {
//uint8_t tx[] = {0xAA, 0xBB, 0xCC, 0xDD}; // Test data
uint8_t tx[] = {0x01, 0x02, 0x03, 0x04};
uint8_t rx[sizeof(tx)] = {0, };
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = sizeof(tx),
.speed_hz = speed,
.bits_per_word = 8,
};
for (int i = 0; i < REPEAT_TRANSMISSION; i++) {
if (ioctl(fd, SPI_IOC_MESSAGE(1), &tr) < 0) {
perror("Failed to send SPI message");
return -1;
}
usleep(1000); // Delay between repetitions for clarity in signal analysis
}
printf("Tested at %u Hz\n", speed);
return 0;
}
int main(int argc, char *argv[]) {
int fd;
uint32_t speed;
// Check if an argument was provided (besides the program name)
if (argc != 2) {
fprintf(stderr, "Usage: %s <spi_speed_hz>\n", argv[0]);
return 1;
}
// Convert the passed argument to an integer
speed = strtoul(argv[1], NULL, 10);
if (speed == 0) {
fprintf(stderr, "Invalid SPI speed provided.\n");
return 1;
}
// Open the SPI device
fd = spi_open(SPI_PATH);
if (fd < 0) return 1;
// Set up the SPI device
printf("Testing at %u Hz\n", speed);
if (spi_setup(fd, speed) == -1) {
close(fd);
return 1;
}
// Perform a SPI transfer test
if (spi_transfer(fd, speed) == -1) {
close(fd);
return 1;
}
// Clean up and close
close(fd);
return 0;
}Compile and run the program:
gcc spi_test.c -o spi_test
./spi_test 1000000
4. Device Tree Overview
The SPI DTS source file has been defined in
kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsi,and we can call it directly.spi0: spi@2acf0000 {
compatible = "rockchip,rk3066-spi";
reg = <0x0 0x2acf0000 0x0 0x1000>;
interrupts = <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cru CLK_SPI0>, <&cru PCLK_SPI0>;
clock-names = "spiclk", "apb_pclk";
dmas = <&dmac0 14>, <&dmac0 15>;
dma-names = "tx", "rx";
pinctrl-names = "default";
pinctrl-0 = <&spi0m0_csn0 &spi0m0_csn1 &spi0m0_pins>;
num-cs = <2>;
status = "disabled";
};The device file path is located in
kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-omni3576.dts,the following snippet enablesspi0:&spi0 {
status = "okay";
pinctrl-0 = <&spi0m0_clk &spi0m0_miso &spi0m0_mosi &spi0m0_cs0>;
#address-cells = <1>;
#size-cells = <0>;
spidev@0 {
compatible = "rockchip,spidev";
spi-max-frequency = <50000000>;
reg = <0>;
};
};
&pinctrl {
spi0 {
spi0m0_clk: spi0m0-clk {
rockchip,pins = <0 RK_PC7 11 &pcfg_pull_none>;
};
spi0m0_mosi: spi0m0-mosi {
rockchip,pins = <0 RK_PD0 11 &pcfg_pull_none>;
};
spi0m0_miso: spi0m0-miso {
rockchip,pins = <0 RK_PD1 11 &pcfg_pull_none>;
};
spi0m0_cs0: spi0m0-cs0 {
rockchip,pins = <0 RK_PC6 11 &pcfg_pull_none>;
};
};
};