【Linux驱动开发】第21天:SPI总线协议与SPI子系统基础理解

一、SPI总线协议基础

1. 硬件结构:4线制同步串行总线

SPI(Serial Peripheral Interface,串行外设接口)是一种全双工、同步、主从架构的串行总线,标准模式下需要4根信号线,比I2C多2根,但通信能力更强。

信号线 全称 方向 作用
SCLK Serial Clock 主→从 串行时钟,由主设备产生,控制通信节奏
MOSI Master Out, Slave In 主→从 主设备发、从设备收的数据线
MISO Master In, Slave Out 从→主 从设备发、主设备收的数据线
CS/SS Chip Select / Slave Select 主→从 片选信号,低电平有效,每个从设备独占一根,选中哪个哪个才工作

核心特点:

  • 单主多从:一条SPI总线上只能有1个主设备,可以挂多个从设备,靠独立的CS线区分从设备,没有I2C那样的"从设备地址"概念。
  • 全双工通信:两根独立数据线,发送和接收可以同时进行;而I2C只有一根数据线,是半双工,同一时间只能单向传输。
  • 同步通信:所有数据都跟着SCLK时钟走,主设备控制时钟频率,从设备被动跟随,不需要各自的时钟源。

2. 主从模式通信原理

SPI通信的本质是移位寄存器的同步移位:主设备和从设备各有一个移位寄存器,SCLK每跳变一次,两边同时移出1位数据到对方的寄存器里。

一个完整的字节传输过程:

  1. 主设备拉低对应从设备的CS线,选中该从设备
  2. 主设备产生SCLK时钟,按照设定的频率和极性跳变
  3. 每个时钟周期:
    • 主设备通过MOSI发出1位数据,同时从MISO采样1位数据
    • 从设备通过MISO发出1位数据,同时从MOSI采样1位数据
  4. 8个时钟周期后,双方完成1个字节的交换
  5. 主设备拉高CS线,结束本次通信

通俗理解:SPI通信就像两个人面对面同时交换纸条,你写一个字给我的同时,我也写一个字给你,节拍由其中一个人(主设备)统一打拍子控制。

3. 四种工作模式(CPOL + CPHA)

SPI通过两个参数配置时序,组合成4种标准模式,不同外设可能要求不同模式,驱动必须和外设匹配。

参数 全称 含义
CPOL Clock Polarity 时钟极性:SCLK空闲时的电平。0=空闲低,1=空闲高
CPHA Clock Phase 时钟相位:在第几个时钟边沿采样数据。0=第一个边沿,1=第二个边沿
  • 模式0(最常用):CPOL=0,CPHA=0 → 空闲时钟低,第一个上升沿采样数据
  • 模式1:CPOL=0,CPHA=1 → 空闲时钟低,第一个下降沿采样数据
  • 模式2:CPOL=1,CPHA=0 → 空闲时钟高,第一个下降沿采样数据
  • 模式3(次常用):CPOL=1,CPHA=1 → 空闲时钟高,第一个上升沿采样数据

二、Linux SPI子系统架构

和I2C完全对应,Linux SPI子系统也是经典的三层架构,思路和I2C高度一致,只是结构体和API名称不同。

复制代码
┌─────────────────────────────────┐
│      SPI设备驱动层(spi_driver) │
│  外设业务逻辑:Flash、屏、ADC等   │
├─────────────────────────────────┤
│        SPI核心层(spi-core)     │
│  通用API、设备管理、驱动匹配      │
├─────────────────────────────────┤
│   SPI控制器驱动层(spi_master)  │
│  硬件控制器操作:RK3399 SPI驱动   │
└─────────────────────────────────┘

1. 第一层:SPI控制器驱动层

  • 定位:最底层,直接操作SoC内置的SPI控制器硬件
  • 核心结构体struct spi_master
    • 代表一个SPI控制器(主机)
    • 包含控制器的硬件信息、时钟、中断、DMA等资源
  • 核心职责
    1. 初始化SPI控制器硬件(时钟、引脚、中断、DMA)
    2. 配置SPI模式、时钟频率、位宽等参数
    3. 实现数据传输接口,向上层提供统一的传输能力
  • RK3399对应源码drivers/spi/spi-rockchip.c

2. 第二层:SPI核心层

  • 定位:中间桥梁,对上下提供统一接口,隔离硬件和业务
  • 核心源码drivers/spi/spi.c
  • 核心职责
    1. 提供spi_master的注册/注销API
    2. 提供SPI设备驱动的注册/注销API
    3. 实现设备树匹配、设备-驱动绑定机制
    4. 封装通用传输逻辑(消息队列、同步/异步处理)
    5. 提供通用API供设备驱动调用,屏蔽硬件差异
  • 对开发者的意义:写SPI外设驱动时,只需要调用核心层API,完全不用关心底层是RK3399还是其他平台的控制器。

3. 第三层:SPI设备驱动层

  • 定位:最上层,实现具体外设的业务逻辑
  • 核心结构体
    • struct spi_device:代表一个SPI从设备,包含片选号、模式、时钟频率、所属控制器等信息
    • struct spi_driver:代表一个SPI设备驱动,包含probe、remove、匹配表等
  • 常见外设:W25Q系列SPI Flash、OLED/LCD显示屏、ADC芯片、以太网网卡、CAN控制器等
  • 开发方式 :注册spi_driver,通过核心层提供的传输API和外设通信。

三、SPI核心传输机制

1. 两个核心传输结构体

SPI用两层结构描述一次传输:

结构体 作用
struct spi_transfer 单次传输单元,描述一段连续的收发数据
struct spi_message 一次完整的SPI事务,由一个或多个spi_transfer组成

特点:

  • 一个spi_message中的所有传输,共用同一个CS片选状态(CS全程保持低电平)
  • 每个spi_transfer可以单独配置长度、速度、位宽等
  • 传输完成后,CS才会拉高,结束本次事务

2. 两种传输方式

(1)同步传输:spi_sync
  • 阻塞等待传输完成,函数返回时数据已经收发完毕
  • 用法简单,适合大多数场景
  • 原型:int spi_sync(struct spi_device *spi, struct spi_message *message)
(2)异步传输:spi_async
  • 非阻塞,函数立即返回,传输完成后通过回调函数通知
  • 适合高并发、大流量场景
  • 原型:int spi_async(struct spi_device *spi, struct spi_message *message)

3. 便捷封装API

为了简化使用,核心层封装了常用的单字节、多字节读写函数:

  • spi_write():写数据
  • spi_read():读数据
  • spi_write_then_read():先写后读(最常用,比如写寄存器地址再读数据)

四、I2C vs SPI 全方位对比

1. 核心参数对比表

维度 I2C SPI
信号线数量 2根(SCL+SDA) 标准4根(SCLK+MOSI+MISO+CS),多从设备需额外CS线
寻址方式 7/10位软件地址,所有设备共用总线 硬件片选线,每个从设备独占一根CS
通信方式 半双工,同一时间只能单向传输 全双工,收发同时进行
同步方式 同步,主设备控时钟 同步,主设备控时钟
典型速率 标准100Kbps,快速400Kbps,高速3.4Mbps 几十Mbps很常见,高端可到上百Mbps
从设备数量 理论最多127个(7位地址),受总线电容限制 受CS引脚数量限制,一般几个到十几个
协议复杂度 有起始/停止、ACK应答、仲裁机制,协议复杂 无固定帧格式,无应答,无仲裁,协议简单
硬件成本 低,只需2根线,支持多设备级联 高,每个从设备多一根CS线,引脚占用多
抗干扰性 一般,开漏输出,需上拉电阻 较好,推挽输出,驱动能力强
多主支持 支持多主机,有总线仲裁 标准SPI不支持多主机

2. 使用场景差异

优先选I2C的场景
  1. 低速传感器类外设:温湿度、加速度计、陀螺仪、光感等,数据量小,对速率要求不高
  2. 板级配置类芯片:RTC时钟、EEPROM、IO扩展芯片、PMIC电源管理芯片
  3. 引脚资源紧张:MCU/SoC引脚不足,希望用最少的线挂多个设备
  4. 多设备级联:同一条总线上挂十几个低速设备,不需要额外引脚
  5. 需要多主机:系统中有多个主控需要访问同一个外设
优先选SPI的场景
  1. 高速大数据量传输:SPI Flash、LCD/OLED显示屏、摄像头、以太网网卡,需要频繁传输大量数据
  2. 高性能外设:高速ADC/DAC、FPGA通信,对速率和延迟要求高
  3. 全双工通信:需要同时收发数据的场景,比如某些工业通信芯片
  4. 协议简单灵活:外设没有标准I2C接口,自定义SPI协议更灵活
  5. 对时序要求严格:SPI时钟稳定,延迟更低,时序更可控

五、SPI设备树的核心特点

I2C vs SPI 设备树reg对比

总线 reg属性含义 示例
I2C 7位从设备物理地址 reg = <0x50>; // 地址0x50
SPI 片选通道号(第几个CS) reg = <0>; // 使用第0个片选

RK3399 SPI从设备节点示例

dts 复制代码
&spi1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&spi1_clk &spi1_mosi &spi1_miso &spi1_cs0>;

    flash@0 {
        compatible = "winbond,w25q128";
        reg = <0>;               // 使用SPI1的第0个片选
        spi-max-frequency = <50000000>; // 最大时钟50MHz
        spi-cpol;                // CPOL=1
        spi-cpha;                // CPHA=1 → 模式3
        status = "okay";
    };
};