Skip to main content

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

  1. 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_3
  2. The 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:

  1. 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_master and /sys/bus/spi/devices directories, allowing users to view and modify the attributes of SPI devices.
  2. Device Nodes: Each connected SPI device has a device node created under the /dev directory, 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)

  1. Luckfox Omni3576 Pinout:

  2. In the /sys/bus/spi/devices directory, each SPI device has its own folder. The folder names typically include "spi" and the device number, for example, /sys/bus/spi/devices/spi0.0 represents 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)

  1. 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()
  2. Run the program:

    python3 spi_test1.py 1000000
  3. Experimental phenomenon:

3.3 SPI Communication (C Program)

  1. 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;
    }
  2. Compile and run the program:

    gcc spi_test.c -o spi_test
    ./spi_test 1000000

4. Device Tree Overview

  1. The SPI DTS source file has been defined inkernel-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";
    };
  2. The device file path is located inkernel-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>;
    };
    };
    };