当前位置: 首页 > news >正文

【Linux驱动开发】Linux SPI 通信详解:从硬件到驱动再到应用

Linux SPI 通信详解:从硬件到驱动再到应用

SPI(Serial Peripheral Interface)是 Motorola 于 1980 年代提出的同步全双工串行总线,采用主从式架构,信号线简单、吞吐高、协议灵活,被广泛用于 Flash、LCD 控制器、ADC/DAC、射频芯片、SD 卡等场景。Linux 将其抽象为 SPI Core 子系统,提供统一的总线/设备/驱动模型,并支持用户态字符设备、QSPI/OSPI、SPI-NOR/NAND Flash 子系统。本文从硬件原理、协议时序、底层驱动框架、内核与用户态接口、实际应用到调试方法,系统梳理 Linux SPI 通信全景。


1. 硬件原理

1.1 信号线

信号方向(主视角)描述
SCLK输出时钟,空闲电平可配置(CPOL)
MOSI输出Master Out Slave In,主→从数据
MISO输入Master In Slave Out,从→主数据
SS/CS#输出(可选)片选,低有效,可多脚或解码器扩展
  • 全双工:收发共用同一时钟,数据移位寄存器双向独立;
  • 单主多从:主机提供时钟,从机无时钟输出能力(除非配置为 slave mode)。

1.2 电平与速度

  • TTL 同主控 IO 电压(1.8 V/3.3 V/5 V);高速板需做 50 Ω 阻抗匹配;
  • 时钟频率:几 kHz – 100 MHz+,受 PCB 线长、驱动能力、从芯片限制;
  • 四线制为标准,另有:
    • 三线制(MOSI/MISO 合并为 SIO),半双工;
    • Dual/Quad/Octal SPI:并行数据线 2/4/8 根,提升带宽,常用于 Flash。

1.3 SPI 控制器框图

在这里插入图片描述

  • 时钟生成:分频 PLL/APB 时钟,可配置极性/相位;
  • 移位寄存器:Tx/Rx 双缓冲,位宽 8/16/32 可选;
  • 片选解码:硬件 CS 线 1–8 根,或外接 74HC138;支持 CSAAT(连续传输不抬片选);
  • DMA 接口:Tx/Rx FIFO 与内存散聚 DMA,减少 CPU 介入;
  • 中断事件:Tx FIFO 空、Rx FIFO 满、传输完成、溢出;
  • 可编程延时:CS→CLK 建立、CLK→CS 保持,满足从芯片时序要求。

2. 协议时序与模式

SPI 无起始/停止位,靠片选帧定界,同步时钟移位。

在这里插入图片描述

2.1 时钟极性/相位 (CPOL/CPHA)

模式CPOLCPHA采样沿描述
000上升沿时钟低空闲,第一沿采样
101下降沿时钟低空闲,第二沿采样
210下降沿时钟高空闲,第一沿采样
311上升沿时钟高空闲,第二沿采样
  • 主从必须配置相同模式;
  • 采样沿由 CPHA 决定:CPHA=0 → 奇数沿,CPHA=1 → 偶数沿。

2.2 传输单位

  • 可 8/16/32 位字长;高位先出 (MSB) 为常态,部分控制器支持 LSB;
  • 片选拉低→时钟启动→连续字→片选拉高,构成一次传输 (transfer);
  • 支持“连续传输”:CS 保持低,Tx FIFO 不断填充,实现大长度 DMA。

2.3 与 I2C/UART 差异

  • 全双工、无速度上限、无寻址、无 ACK;
  • 线多(至少 4 线),无硬件仲裁,不适合多主;
  • 协议简单,但不同芯片时序差异大(CS 建立/保持、空闲电平、字长)。

3. Linux SPI 驱动框架

Linux 采用“控制器驱动 + 协议驱动 + 片选 GPIO”分层,核心文件:drivers/spi/spi.c

在这里插入图片描述

3.1 关键数据结构

结构体描述
struct spi_controller代表 SPI 主/从控制器(原 spi_master),含总线号、片选数量、队列/线程化传输模型;
struct spi_device描述一个从设备:总线号、片选、模式 (CPOL/CPHA)、字长、最大速度;
struct spi_driver协议驱动,匹配 spi_device,提供 probe/remove/suspend/resume
struct spi_transfer一次传输片段:Tx/Rx 缓冲区、长度、速度、字长、延时等;
struct spi_message由多个 spi_transfer 组成,片选连续保持,可一次性 DMA;

3.2 传输模型

  • 早期 PIOspi_sync() 在中断上下文忙等待,适合短数据;
  • 线程化 + 队列spi_queued_transfer(默认),内核线程 kworker/spi 处理消息,降低延迟;
  • DMA 散聚:驱动实现 can_dma() + dma_tx/rx 映射,实现零拷贝;
  • 同步/异步spi_sync() 阻塞;spi_async() 注册回调,用于 SPI LCD 刷新等实时场景。

3.3 控制器驱动示例(简化)

#include <linux/spi/spi.h>
static int spi_xxx_setup(struct spi_device *spi)
{/* 根据 spi->mode 配置寄存器 CPOL/CPHA *//* 根据 spi->max_speed_hz 配置分频 */return 0;
}
static int spi_xxx_transfer_one(struct spi_controller *ctlr,struct spi_device *spi,struct spi_transfer *t)
{/* 1. 配置字长 t->bits_per_word *//* 2. 写 Tx FIFO (t->tx_buf) *//* 3. 等待中断/Rx DMA 完成 *//* 4. 读 Rx FIFO 到 t->rx_buf */return 0;
}
static struct spi_controller *ctlr;
static int spi_xxx_probe(struct platform_device *pdev)
{ctlr = spi_alloc_controller(&pdev->dev, sizeof(*drv_data));ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;ctlr->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);ctlr->min_speed_hz = 100000;ctlr->max_speed_hz = 50000000;ctlr->setup = spi_xxx_setup;ctlr->transfer_one = spi_xxx_transfer_one;ctlr->num_chipselect = 4;return devm_spi_register_controller(&pdev->dev, ctlr);
}

3.4 协议驱动示例(RF 芯片)

static int rf_probe(struct spi_device *spi)
{struct spi_transfer t[2] = {{ .tx_buf = &cmd, .len = 1, .speed_hz = 10000000 },{ .rx_buf = buf, .len = 4, .speed_hz = 10000000 },};struct spi_message m;spi_message_init(&m);spi_message_add_tail(&t[0], &m);spi_message_add_tail(&t[1], &m);spi_sync(spi, &m);          /* 读 4 字节寄存器 */
}
static struct spi_driver rf_driver = {.driver = { .name = "rf_xxx", },.probe  = rf_probe,
};
module_spi_driver(rf_driver);

4. 用户态接口

4.1 spidev 字符设备 /dev/spidevB.C

  • 内核配置 CONFIG_SPI_SPIDEV
  • B = 总线号,C = 片选号;
  • ioctl 列表:
SPI_IOC_WR_MODE          // 设置 SPI_MODE_0/1/2/3
SPI_IOC_RD_MODE
SPI_IOC_WR_MAX_SPEED_HZ
SPI_IOC_WR_BITS_PER_WORD
SPI_IOC_MESSAGE(N)       // 一次传输多段,支持全双工
  • 用户态示例(Python spidev 模块):
import spidev
spi = spidev.SpiDev()
spi.open(0, 0)          # /dev/spidev0.0
spi.max_speed_hz = 10_000_000
spi.mode = 0b00         # CPOL=0 CPHA=0
resp = spi.xfer2([0x9F, 0x00, 0x00, 0x00])  # 读 Flash JEDEC ID

4.2 sysfs 节点(已逐步淡化)

  • /sys/bus/spi/devices/spi0.0/ 提供 modalias/uevent;调试时可用 cat driver/name

4.3 调试与跟踪

  • dynamic_debug
echo 'file spi.c +p' > /sys/kernel/debug/dynamic_debug/control
  • 逻辑分析仪:抓 SCLK/MOSI/MISO/CS,看模式、字长、空闲电平;
  • spidev_test 官方工具:
./spidev_test -D /dev/spidev0.0 -s 10000000 -p "\x9f\x00\x00\x00" -v

5. 实际应用与场景

外设模式/速度注意要点
W25Q64 FlashSPI_MODE_0, 80 MHz支持 Quad SPI,先 0xEB 读,DMA 大页
TFT LCD (ST7789)SPI_MODE_3, 40 MHz9-bit 命令/数据(DCX)用 GPIO 或硬件 D/C 线
NRF24L01 射频SPI_MODE_0, 8 MHz连续 CS,内部 FIFO 状态寄存器轮询
ADC (MCP3208)SPI_MODE_0, 1 MHz16 位采样,先 0x06 + 通道号,再读 12 位结果
以太网 ENC28J60SPI_MODE_0, 20 MHz7 位地址 + 5 位数据,硬件 DMA 支持
音频 codecSPI_MODE_1, 24 MHz24 位字长,MSB 先出,需连续时钟

5.1 多从片选方式

  • 独立 CS 线:控制器硬件 CS,切换简单;
  • GPIO 片选:spi_device.cs_gpio = gpio#,驱动自动拉低/拉高;
  • 解码器:3-8 译码器 74HC138,节省引脚,需 cs_setup_hz 延时。

5.2 大吞吐量优化

  • 双缓冲 DMA:Tx/Rx 各用环形 buffer,LCD 刷新不丢帧;
  • 中断线程化:PREEMPT_RT 下,spi->irq 线程化,抖动 < 100 µs;
  • 连续传输:一次 spi_message 包含 240×320×2 字节,CS 保持低,DMA 链完成;
  • CPU 亲和:SPI DMA 中断绑到独立核,避免任务迁移。

6. 故障排查与性能优化

现象可能原因排查/解决
模式不匹配CPOL/CPHA 错逻辑分析仪看采样沿,主从统一模式
数据偏移字长/位序错示波器测 8/16/32 位,MSB/LSB 先出
片选抖动CS 建立不足cs_setup/delay 或降速,用 GPIO CS
丢字节DMA 未完成查看 spi->dma_tx/rx 返回,开 dynamic_debug
高误码线长/阻抗失配降速 < 10 MHz,加 22 Ω 串阻,用双绞屏蔽线
实时抖动中断抢占绑核 + irqthread + spi_async 回调

7. 小结

SPI 以“4 线 + 全双工 + 无寻址”提供极简高速串行链路,协议灵活但时序多样。Linux 通过“spi_controller/spi_device/spi_driver”模型把控制器与协议分离,统一使用 spi_message/spi_transfer 进行 DMA 友好传输;用户态则通过 spidev 字符设备快速验证外设。掌握 CPOL/CPHA、字长、CS 建立/保持、DMA 散聚与实时绑核技巧,可在嵌入式显示、高速数据采集、射频通信等场景中发挥 SPI 的最大带宽与确定性。


附录:参考文档

  • Linux Kernel: Documentation/spi/, drivers/spi/spi.c, spidev.c
  • Motorola SPI Block Guide
  • 芯片手册:W25Q64JV、ST7789、NRF24L01、MCP3208、ENC28J60
  • 书籍:《Linux 设备驱动开发详解》《嵌入式 Linux 开发教程》

本文档示例基于 Linux 4.4.94,不同版本内核接口基本一致,高速/DMA/实时细节请以实际 SoC 手册与驱动为准。

http://www.dtcms.com/a/617895.html

相关文章:

  • 【ASP.NET进阶】Controller层核心:Action方法全解析,从基础到避坑
  • Imec实现了GaN击穿电压的记录
  • Streaming ELT with Flink CDC · Iceberg Sink
  • AI(新手)
  • 海南城乡建设厅网站百度竞价关键词查询
  • QT开发——常用控件(2)
  • 【Java架构师体系课 | MySQL篇】⑥ 索引优化实战二
  • Spring Boot、Redis、RabbitMQ 在项目中的核心作用详解
  • 做完整的网站设计需要的技术长治建立公司网站的步骤
  • 南宁京象建站公司网站建设留言板实验心得
  • AI、LLM全景图
  • pip升级已安装包方法详解
  • 【Linux日新月异(六)】CentOS 7网络命令深度解析:从传统到现代网络管理
  • LangChain 构建 AI 代理(Agent)
  • 人工智能训练师备考——3.1.1题解
  • 【RL】ORPO: Monolithic Preference Optimization without Reference Model
  • 公益平台网站怎么做网站跳出
  • QT的5种标准对话框
  • 用Rust构建一个OCR命令行工具
  • 网站代码大全国内网站设计作品欣赏
  • LeetCode 热题 100——子串——滑动窗口最大值
  • CPP(容器)STL:
  • 【Java常用API】----- Math
  • RAG 系统 “检索 - 筛选 - 生成” 完整流程
  • 时间复杂度 和 嵌入式时钟概念 有关系。 我的理由是:时钟经常需要计算频率,而频率往往需要和时间进行计数次数i 。 时间复杂度就像是计数次数i
  • 公司做普通网站建立网站地图
  • Java 大视界 -- Java 大数据在智能农业病虫害精准识别与绿色防控中的创新应用
  • 【高并发架构】从 0 到亿,从单机部署到 K8s 编排:高并发架构的 8 级演进之路
  • 基于Streamlit的交互式3D手指运动学仿真
  • 甘肃做网站找谁金种子酒业网站建设