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_0CPOL=0,CPHA=1 SPI_MODE_1CPOL=1,CPHA=0 SPI_MODE_2CPOL=1,CPHA=1 SPI_MODE_3 -
SPI的四种工作模式波形图如下:



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 spidevimport timeimport 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_hzspi.mode = modespi.bits_per_word = bits_per_wordprint(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.0spi_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 frequencyint 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 datauint8_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>;};};};