跳到主要内容

SPI 通信

1.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 设备编号。

2.查看SPI(Shell)

2.1 引脚分布

  • Luckfox Lyra Ultra/Ultra W 引脚图:

2.2 查看设备

/sys/bus/spi/devices 目录中,每个 SPI 设备都有自己的文件夹。这些文件夹的名称通常包含 spi 和设备编号,例如 /sys/bus/spi/devices/spi0.0 表示 SPI 总线编号0的0号设备。如果您想查看系统存在的 SPI 总线,可以使用如下命令:

# ls /sys/bus/spi/devices/
spi2.0 spi0.0

3.SPI通讯(Python程序)

3.1 完整代码

通过以下程序,可以实现SPI通讯。

import spidev

def main():
tx_buffer = [ord(char) for char in "hello world!"]
rx_buffer = [0] * len(tx_buffer)

try:
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000

rx_buffer = spi.xfer2(tx_buffer[:])
print("tx_buffer:\n\r", ''.join(map(chr, tx_buffer)))
print("rx_buffer:\n\r", ''.join(map(chr, rx_buffer)))

except Exception as e:
print(f"An error occurred: {e}")

finally:
if spi:
spi.close()

if __name__ == "__main__":
main()

3.2 打开SPI设备

这段代码使用 spidev 库中的SpiDev类创建了一个SPI对象。通过调用 open 方法,指定SPI总线和设备的编号,这里是SPI总线0的设备0。设置了SPI的最大传输速率为1,000,000 Hz,即1 MHz。这一步是为了配置SPI设备的基本参数,以便进行后续的数据传输。

spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000

3.3 数据收发

这段代码使用 xfer2 方法进行SPI数据传输。在这个例子中, tx_buffer 是要发送的数据,而 rx_buffer 将存储接收到的数据。注意,为了确保 tx_buffer 的原始值保持不变,传递给 xfer2 的是 tx_buffer 的副本,即tx_buffer[:]。最后,通过 print 语句将发送和接收的数据以字符串形式打印出来,方便用户查看。

rx_buffer = spi.xfer2(tx_buffer[:])
print("tx_buffer:\n\r", ''.join(map(chr, tx_buffer)))
print("rx_buffer:\n\r", ''.join(map(chr, rx_buffer)))

3.4 运行程序

  1. 使用 nano 工具打开文件,粘贴并保存代码

    # nano i2c.py
  2. 运行程序

    # python3 i2c.py
  3. 实验现象

    将SPI的MOSI和MISO相接,运行程序:
    image

4.SPI通讯(C程序)

4.1 ioctl函数

在编写应用程序时需要使用ioctl函数设置spi相关配置,其函数原型如下

 #include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

当使用ioctl函数进行SPI通信时,常用的request参数主要有以下几种:

  • SPI_IOC_RD_MODE:用于读取当前SPI通信的模式设置。这个请求参数将模式信息读取到一个整数变量中,以便检查当前SPI通信的极性和相位设置。
  • SPI_IOC_WR_MODE:用于设置SPI通信的模式。您需要提供一个整数值,该值通常由两位二进制数字组成,表示SPI通信的极性和相位。
  • SPI_IOC_RD_BITS_PER_WORD:用于读取每个数据字的位数。这个请求参数将位数信息读取到一个整数变量中。
  • SPI_IOC_WR_BITS_PER_WORD:用于设置每个数据字的位数。您需要提供一个整数值,以指定要发送和接收的每个数据字的位数。
  • SPI_IOC_RD_MAX_SPEED_HZ:用于读取SPI总线的最大速度。这个请求参数将速度信息读取到一个整数变量中,以便检查当前SPI总线的最大传输速度。
  • SPI_IOC_WR_MAX_SPEED_HZ:用于设置SPI总线的最大速度。您需要提供一个整数值,以指定要使用的最大传输速度。
  • SPI_IOC_MESSAGE(N):用于执行SPI传输的读写操作。这个请求参数需要一个指向 struct spi_ioc_transfer 数组的指针,每个元素描述了一个SPI传输操作,可以执行多个操作。
  • SPI_IOC_RD_LSB_FIRST:用于读取LSB(Least Significant Bit)优先设置。这个请求参数将LSB优先信息读取到一个整数变量中。
  • SPI_IOC_WR_LSB_FIRST:用于设置LSB优先设置。您需要提供一个整数值,以指定是否要使用LSB优先模式。

4.2 示例程序

通过以下程序,可以实现SPI通讯。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>

#define SPI_DEVICE_PATH "/dev/spidev0.0"

int main() {
int spi_file;
uint8_t tx_buffer[50] = "hello world!";
uint8_t rx_buffer[50];

// Open the SPI device
if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1;
}

// Configure SPI mode and bits per word
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1;
}
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1;
}

// Perform SPI transfer
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(tx_buffer),
.delay_usecs = 0,
.speed_hz = 1000000, // SPI speed in Hz
.bits_per_word = 8,
};

if (ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("Failed to perform SPI transfer");
close(spi_file);
return -1;
}

/* Print tx_buffer and rx_buffer*/
printf("\rtx_buffer: \n %s\n ", tx_buffer);
printf("\rrx_buffer: \n %s\n ", rx_buffer);

// Close the SPI device
close(spi_file);

return 0;
}

4.3 文件路径

这行代码定义了一个宏,用于存储 SPI 设备文件的路径。

#define SPI_DEVICE_PATH "/dev/spidev0.0"

4.4 打开SPI设备

这部分代码尝试打开指定的 SPI 设备文件。

// Open the SPI device
if ((spi_file = open(SPI_DEVICE_PATH, O_RDWR)) < 0) {
perror("Failed to open SPI device");
return -1;
}

4.5 配置SPI

这段代码用于配置 SPI 通信的模式为 SPI 模式0(时钟极性为0、时钟相位为0)以及每个字的位数为8位,以确保 SPI 通信的正确性和一致性。

// Configure SPI mode and bits per word
uint8_t mode = SPI_MODE_0;
uint8_t bits = 8;
if (ioctl(spi_file, SPI_IOC_WR_MODE, &mode) < 0) {
perror("Failed to set SPI mode");
close(spi_file);
return -1;
}
if (ioctl(spi_file, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {
perror("Failed to set SPI bits per word");
close(spi_file);
return -1;
}

这段代码定义了一个 spi_ioc_transfer 结构体变量 transfer,用于配置 SPI 传输的参数。它指定了传输的数据缓冲区、传输的长度、延迟时间、SPI 速度(以赫兹为单位)以及每个字的位数。这个结构体将被传递给 SPI_IOC_MESSAGE ioctl 函数,以执行 SPI 传输。

// Perform SPI transfer
struct spi_ioc_transfer transfer = {
.tx_buf = (unsigned long)tx_buffer,
.rx_buf = (unsigned long)rx_buffer,
.len = sizeof(tx_buffer),
.delay_usecs = 0,
.speed_hz = 1000000, // SPI speed in Hz
.bits_per_word = 8,
};

4.6 数据收发

这段代码使用 ioctl 函数执行 SPI 传输。它通过 SPI_IOC_MESSAGE(1) 宏指定传输的数量为1,第三个参数为上文中配置的结构体变量 transfer 的地址。如果 SPI 传输失败,将会输出错误消息并关闭 SPI 设备文件描述符。

if (ioctl(spi_file, SPI_IOC_MESSAGE(1), &transfer) < 0) {
perror("Failed to perform SPI transfer");
close(spi_file);
return -1;
}

4.7 交叉编译

  1. 交叉编译运行程序,搭建交叉编译环境请参考《程序编译》或《GPIO》部分。

  2. 运行程序。修改 spi 文件的操作权限后运行程序

    # chmod +x spi
    # ./spi
  3. 实验现象。将SPI的MOSI和MISO相接,运行程序:
    image

5. 设备树简介

  1. 设备文件路径位于kernel-6.1/arch/arm/boot/dts/rk3506g-luckfox-lyra.dts,开启spi0的代码片段如下:

    &spi0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&rm_io7_spi0_clk &rm_io6_spi0_mosi &rm_io5_spi0_miso>;
    spidev@0 {
    compatible = "rockchip,spidev";
    spi-max-frequency = <10000000>;
    reg = <0>;
    };
    };