SPI

1. SPI 简介

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的串行通信总线,由摩托罗拉(Motorola)在 20 世纪 80 年代推出,广泛应用于微控制器与外设之间的通信,如传感器、存储器、显示器等。

1.1 SPI 接口基本组成

SPI 采用主从架构,通常包含 4 条信号线:

信号线

英文全称

功能描述

主出从入

MOSI(Master Out Slave In)

主设备发送数据,从设备接收数据。

主入从出

MISO(Master In Slave Out)

从设备发送数据,主设备接收数据。

时钟线

SCLK(Serial Clock)

由主设备产生,控制数据传输的时序。

片选线

CS/SS(Chip Select/Slave Select)

主设备通过拉低对应从设备的 CS 线来选中目标从设备,通常为低电平有效。

1.2 SPI 工作原理

  1. 通信启动:主设备通过拉低目标从设备的 CS 线,建立通信连接。

  2. 时钟同步:主设备通过 SCLK 线提供时钟信号,数据在时钟的上升沿或下降沿传输(由时钟极性和相位决定,即 SPI 的模式)。

  3. 数据传输:主设备通过 MOSI 发送数据,从设备通过 MISO 回应数据,实现全双工通信;数据以串行方式逐位传输,通常 MSB(最高位)优先。

  4. 通信结束:当主设备发送完所有数据后,主动拉高 CS 线,结束通信。

1.3 SPI 的主要特点

特性

优点

缺点

传输性能

传输速度快,最高可达数十 Mbps,适用于高速数据传输场景。

没有应答机制,无法确保数据传输的可靠性。

硬件成本

接口简单,硬件实现成本低,仅需少数几根线即可完成通信。

通信距离较短,通常适用于板级内的芯片间通信。

多设备支持

支持多从设备连接,主设备通过不同的 CS 线选择不同从设备。

从设备之间不能直接通信,必须通过主设备协调。

1.4 SPI 传输模式

SPI 的传输模式由两个参数决定:

  • 时钟极性(CPOL,Clock Polarity):决定 SCLK 的空闲电平(0 或 1)。

  • 时钟相位(CPHA,Clock Phase):决定数据采样是在时钟的第一个沿还是第二个沿。 两者组合形成 4 种模式: | 模式 | CPOL | CPHA | 时钟空闲电平 | 数据采样沿 | |------|------|------|--------------|------------| | 0 | 0 | 0 | 低电平 | 上升沿 | | 1 | 0 | 1 | 低电平 | 下降沿 | | 2 | 1 | 0 | 高电平 | 下降沿 | | 3 | 1 | 1 | 高电平 | 上升沿 |

1.5 SPI 常见应用场景

应用场景

示例设备/芯片

具体型号示例

存储器

EEPROM、Flash 存储器

W25Q 系列

传感器

加速度计、陀螺仪

ADXL345

显示驱动

OLED、LCD 驱动芯片

SSD1306

模数转换器(ADC)

ADC 芯片

ADS1115

外设扩展

SPI 转 UART、SPI 转 I2C 芯片

如 MAX3100(SPI 转 UART)

1.6 SPI 与其他接口对比

  • 与 I2C 的对比:

    • SPI 速度更快,但需要更多信号线(I2C 仅需 SDA、SCL 和 GND),且不支持多主设备。

    • I2C 有应答机制,传输可靠性更高,适合低速、多设备场景。

  • 与 UART 的对比:

    • SPI 是同步通信(需时钟线),UART 是异步通信(无需时钟线)。

    • SPI 支持全双工实时通信,UART 通常为半双工或全双工(需两根数据线)。

  • 与 CAN(Controller Area Network,控制器局域网络)对比:

    • SPI 是同步通信,CAN 是异步通信。

    • SPI 支持多主设备连接,CAN 仅支持一对多通信。

    • SPI 通常用于短距离通信,CAN 用于长距离通信(如汽车网络)。

2. XNIUPI SPI 接口

XNIUPI-R系列开发主机对外提供1路 SPI 接口,位于 20PIN PHD连接器,具体如下: XNIUPI-SPI接口示意图

另外在XNIUPI-R系列开发主机内部主板上的MINI-PCIE座子上还预留一路SPI,具体如下: XNIUPI-MIPI-PCIE-SPI接口示意图

可以通过如下指令查看SPI设备(以R800为例):

root@linaro-alip:/dev# ls /sys/class/spi_master
spi1  spi2  spi4
# 以上说明系统识别到三个SPI接口,分别为SPI1 SPI2 PSI4
# 其中SPI2为内部连接PMIC未暴露出来给用户,SPI4接到MINI-PCIE,SPI1接到对外的 20PIN PHD座子

3. SPI 接口使用

我们有两种方式可以将SPI接口使用起来:

  • 使用 spidev 用户空间接口(适用于测试或自定义驱动)

  • 使用内核驱动(适用于挂载特定 SPI 设备)

以下分别做简要介绍。

3.1 方式一:使用 spidev 用户空间接口

如果需要直接通过用户空间程序访问 SPI,可以启用spidev驱动。

3.1.1 步骤1:修改设备树添加 spidev 节点

&spi1{
    status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi1m1_cs0 &spi1m1_pins>;
	//num-cs = <1>;

	spidev@0 {
        compatible = "spidev";
        reg = <0>;  /* CS0 */
        spi-max-frequency = <50000000>;  /* 50MHz */
    };
};

3.1.2 步骤2:编译内核,重新烧录系统(可以只烧录boot.img)

cd <sdk>/ # 进入SDK根目录
./build.sh lunch # 选择板级配置文件
./build.sh kernel # 编译内核,生成boot.img镜像

烧录固件或分区镜像步骤请参考相关章节,此处略。

3.1.3 步骤 3:验证 spidev 节点

烧录镜像后,应看到/dev/spidev1.0节点(对应 spi1 控制器的 CS0):

root@linaro-alip:/# ls /dev/spidev*
/dev/spidev1.0

3.1.4 步骤 4:使用 spidev 进行数据传输

可以通过 C 程序或 Python 库(如spidev)访问 SPI。以下是 Python 示例:

import spidev
import time

# 打开SPI设备(spi1.0)
spi = spidev.SpiDev()
spi.open(1, 0)  # bus=1, device=0
spi.max_speed_hz = 50000000  # 设置SPI频率

# 发送和接收数据
data = [0x01, 0x02, 0x03]
response = spi.xfer2(data)
print(f"发送: {data}")
print(f"接收: {response}")

# 关闭SPI设备
spi.close()

3.2 方式二:使用内核驱动

3.2.1 步骤1:修改设备树添加设备节点

假设我们需要挂载 W25Q Flash 存储器:

&spi1{
    status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi1m1_cs0 &spi1m1_pins>;
	//num-cs = <1>;

	flash@0 {
        compatible = "winbond,w25q128";
        reg = <0>; /* CS0 */
        spi-max-frequency = <50000000>;
        #address-cells = <1>;
        #size-cells = <1>;
    };

};

3.2.2 步骤2:编译内核,重新烧录系统(可以只烧录boot.img)

cd <sdk>/ # 进入SDK根目录
./build.sh lunch # 选择板级配置文件
./build.sh kernel # 编译内核,生成boot.img镜像

烧录固件或分区镜像步骤请参考相关章节,此处略。

3.2.3 步骤 3:查看SPI设备节点

dmesg | grep -i w25q  # 根据设备名称调整关键字
ls /sys/bus/spi/devices/spi1.0  # 查看设备是否存在

3.2.4 步骤 4:测试 SPI 接口(使用 spidev)

如果只是想测试 SPI 接口是否正常工作,可以使用spidev_test工具(需先安装):

# 安装spidev-utils
apt-get install spidev-utils

# 测试SPI通信(发送0xFF并接收)
spidev_test -D /dev/spidev1.0 -s 1000000 -p "\xFF\xFF\xFF"