一、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位数据到对方的寄存器里。
一个完整的字节传输过程:
- 主设备拉低对应从设备的CS线,选中该从设备
- 主设备产生SCLK时钟,按照设定的频率和极性跳变
- 每个时钟周期:
- 主设备通过MOSI发出1位数据,同时从MISO采样1位数据
- 从设备通过MISO发出1位数据,同时从MOSI采样1位数据
- 8个时钟周期后,双方完成1个字节的交换
- 主设备拉高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等资源
- 核心职责:
- 初始化SPI控制器硬件(时钟、引脚、中断、DMA)
- 配置SPI模式、时钟频率、位宽等参数
- 实现数据传输接口,向上层提供统一的传输能力
- RK3399对应源码:
drivers/spi/spi-rockchip.c
2. 第二层:SPI核心层
- 定位:中间桥梁,对上下提供统一接口,隔离硬件和业务
- 核心源码:
drivers/spi/spi.c - 核心职责:
- 提供
spi_master的注册/注销API - 提供SPI设备驱动的注册/注销API
- 实现设备树匹配、设备-驱动绑定机制
- 封装通用传输逻辑(消息队列、同步/异步处理)
- 提供通用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的场景
- 低速传感器类外设:温湿度、加速度计、陀螺仪、光感等,数据量小,对速率要求不高
- 板级配置类芯片:RTC时钟、EEPROM、IO扩展芯片、PMIC电源管理芯片
- 引脚资源紧张:MCU/SoC引脚不足,希望用最少的线挂多个设备
- 多设备级联:同一条总线上挂十几个低速设备,不需要额外引脚
- 需要多主机:系统中有多个主控需要访问同一个外设
优先选SPI的场景
- 高速大数据量传输:SPI Flash、LCD/OLED显示屏、摄像头、以太网网卡,需要频繁传输大量数据
- 高性能外设:高速ADC/DAC、FPGA通信,对速率和延迟要求高
- 全双工通信:需要同时收发数据的场景,比如某些工业通信芯片
- 协议简单灵活:外设没有标准I2C接口,自定义SPI协议更灵活
- 对时序要求严格:SPI时钟稳定,延迟更低,时序更可控
五、SPI设备树的核心特点
I2C vs SPI 设备树reg对比
| 总线 | reg属性含义 | 示例 |
|---|---|---|
| I2C | 7位从设备物理地址 | reg = <0x50>;// 地址0x50 |
| SPI | 片选通道号(第几个CS) | reg = <0>;// 使用第0个片选 |
RK3399 SPI从设备节点示例
&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"; }; };