SPI
1. 简介
SPI 全称为串行外设接口(Serial Peripheral Interface),其是一种高速的,全双工,同步通信总线。
它以主从方式工作,这种模式通常一个主设备对应一个或多个从设备,双向数据传输时需要4根线,单向数据传输时可以裁切为3根线。
SPI 总线本身由四个物理连接组成:两条数据线,一条时钟线和从选择线。
MOSI(主机输出/从机输入) – Omni3576 将数据发送到设备(传感器)的线路。
MISO(主机输入/从机输出) – 设备将数据发送到 Omni3576 的线路。
SCLK(时钟) –时钟信号线。
SS / CS(从站选择/芯片选择) –用于选择向其发送数据的设备的线路。
2. 工作方式
Linux内核用CPOL和CPHA的组合来表示当前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_3SPI的四种工作模式波形图如下:



3. SPI 子系统
在 Linux 操作系统中,SPI 子系统是一个关键的驱动框架,用于管理和控制通过 SPI 总线连接的各种外部设备,有关 SPI 子系统的更多详细信息可以在 <Linux内核源码>/Documentation/spi 目录中找到。SPI 子系统的关键组成部分:
- sysfs接口:SPI子系统通过sysfs提供了一组文件和目录,用于配置和管理 SPI 总线和 SPI 设备。这些文件和目录位于
/sys/class/spi_master和/sys/bus/spi/devices目录下,允许用户查看和修改 SPI 设备的属性。 - 设备节点:每个已连接的 SPI 设备都在
/dev目录下创建了一个设备节点,允许用户空间程序通过标准文件I/O操作与设备进行通信。通常,这些设备节点的名称是/dev/spidevX.Y,其中X表示 SPI 总线编号,Y表示 SPI 设备编号。
3.1 查看 SPI(Shell)
Luckfox Omni3576 引脚图:

在
/sys/bus/spi/devices目录中,每个 SPI 设备都有自己的文件夹。这些文件夹的名称通常包含 spi 和设备编号,例如/sys/bus/spi/devices/spi0.0表示 SPI 总线编号0的0号设备。如果您想查看系统存在的 SPI 总线,可以使用如下命令:root@luckfox:/home/luckfox# ls /sys/bus/spi/devices/
spi0.0
3.2 SPI 通讯(Python 程序)
完整代码
import spidev
import time
import sys
# 打开 SPI 设备
def spi_open(bus, device):
spi = spidev.SpiDev()
spi.open(bus, device)
return spi
# 配置 SPI 参数
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}")
# 进行 SPI 数据传输
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 延迟用于清晰的信号分析
# 主程序
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) # 使用 SPI0.0
spi_setup(spi, speed_hz)
# 发送的数据
tx_data = [0x01, 0x02, 0x03, 0x04]
# 进行传输 5 次
spi_transfer(spi, tx_data, 5)
# 关闭 SPI 设备
spi.close()运行程序:
python3 spi_test1.py 1000000实验现象:

3.3 SPI 通讯(C 程序)
完整代码:
#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;
// 检查是否提供了一个参数(除了程序名之外)
if (argc != 2) {
fprintf(stderr, "Usage: %s <spi_speed_hz>\n", argv[0]);
return 1;
}
// 将传入的参数转换为整数
speed = strtoul(argv[1], NULL, 10);
if (speed == 0) {
fprintf(stderr, "Invalid SPI speed provided.\n");
return 1;
}
// 打开SPI设备
fd = spi_open(SPI_PATH);
if (fd < 0) return 1;
// 设置SPI设备
printf("Testing at %u Hz\n", speed);
if (spi_setup(fd, speed) == -1) {
close(fd);
return 1;
}
// 进行SPI传输测试
if (spi_transfer(fd, speed) == -1) {
close(fd);
return 1;
}
// 清理和关闭
close(fd);
return 0;
}编译和运行程序
gcc spi_test.c -o spi_test
./spi_test 1000000
4. 设备树简介
SPI DTS 源文件在
kernel-6.10/arch/arm64/boot/dts/rockchip/rk3576.dtsi已经定义,我们可以直接调用。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";
};设备文件路径位于
kernel-6.1/arch/arm64/boot/dts/rockchip/luckfox-omni3576.dts,开启spi0的代码片段如下:&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>;
};
};
};