跳到主要内容

SPI

1. 简介

  • SPI 全称为串行外设接口(Serial Peripheral Interface),其是一种高速的,全双工,同步通信总线。

  • 它以主从方式工作,这种模式通常一个主设备对应一个或多个从设备,双向数据传输时需要4根线,单向数据传输时可以裁切为3根线。

  • SPI 总线本身由四个物理连接组成:两条数据线,一条时钟线和从选择线。

    • MOSI(主机输出/从机输入) – Omni3576 将数据发送到设备(传感器)的线路。

    • MISO(主机输入/从机输出) – 设备将数据发送到 Omni3576 的线路。

    • SCLK(时钟) –时钟信号线。

    • SS / CS(从站选择/芯片选择) –用于选择向其发送数据的设备的线路。

2. 工作方式

  1. 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_3
  2. SPI的四种工作模式波形图如下:


3. SPI 子系统

在 Linux 操作系统中,SPI 子系统是一个关键的驱动框架,用于管理和控制通过 SPI 总线连接的各种外部设备,有关 SPI 子系统的更多详细信息可以在 <Linux内核源码>/Documentation/spi 目录中找到。SPI 子系统的关键组成部分:

  1. sysfs接口:SPI子系统通过sysfs提供了一组文件和目录,用于配置和管理 SPI 总线和 SPI 设备。这些文件和目录位于 /sys/class/spi_master/sys/bus/spi/devices 目录下,允许用户查看和修改 SPI 设备的属性。
  2. 设备节点:每个已连接的 SPI 设备都在 /dev 目录下创建了一个设备节点,允许用户空间程序通过标准文件I/O操作与设备进行通信。通常,这些设备节点的名称是 /dev/spidevX.Y,其中X表示 SPI 总线编号,Y表示 SPI 设备编号。

3.1 查看 SPI(Shell)

  1. Luckfox Omni3576 引脚图:

  2. /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 程序)

  1. 完整代码

    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()
  2. 运行程序:

    python3 spi_test1.py 1000000
  3. 实验现象:

3.3 SPI 通讯(C 程序)

  1. 完整代码:

    #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;
    }
  2. 编译和运行程序

    gcc spi_test.c -o spi_test
    ./spi_test 1000000

4. 设备树简介

  1. 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";
    };
  2. 设备文件路径位于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>;
    };
    };
    };