1. 引言
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的通信总线,广泛应用于嵌入式 Linux 系统中连接传感器、Flash、ADC、显示屏等外设。本文记录我在学习 Linux SPI 驱动开发过程中的核心知识点、框架结构和实战经验,希望能帮助同样在学习 SPI 驱动的读者少走弯路。
2. SPI 协议基础
在深入 Linux 驱动之前,先回顾 SPI 协议的基本概念。SPI 总线由四根信号线组成:
- SCLK(时钟线):由主机产生,用于同步数据传输。
- MOSI(主机输出从机输入):主机向从机发送数据。
- MISO(主机输入从机输出):从机向主机发送数据。
- CS(片选线):低电平有效,用于选中目标从机设备。
SPI 通信有四种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)组合决定:
| 模式 | CPOL | CPHA | 数据采样时机 |
|---|---|---|---|
| Mode 0 | 0 | 0 | 时钟上升沿采样 |
| Mode 1 | 0 | 1 | 时钟下降沿采样 |
| Mode 2 | 1 | 0 | 时钟下降沿采样 |
| Mode 3 | 1 | 1 | 时钟上升沿采样 |
驱动开发中必须根据从机数据手册正确配置模式,否则通信将完全失败。
3. Linux SPI 子系统架构
Linux 内核将 SPI 驱动划分为三个层次,理解这个分层结构是学习的关键:
- 控制器驱动(SPI Master Driver):负责操作具体的 SPI 控制器硬件,如寄存器读写、DMA 传输等。
- 核心层(SPI Core):提供统一的框架,管理总线、设备和驱动的注册与匹配。
- 设备驱动(SPI Device Driver):面向具体外设,实现读写逻辑和业务功能。
这种分层设计使得控制器驱动与设备驱动解耦,同一外设驱动可以运行在不同平台的 SPI 控制器上。
4. 关键数据结构
Linux SPI 子系统围绕几个核心数据结构展开,理解它们是编写驱动的前提。
4.1 spi_master 结构体
struct spi_master 代表一个 SPI 控制器,主要字段包括:
c
struct spi_master {
struct device dev;
u16 bus_num; /* 总线编号 */
u32 mode_bits; /* 支持的 SPI 模式 */
u32 bits_per_word_mask; /* 支持的位宽 */
u32 min_speed_hz;
u32 max_speed_hz;
int (*transfer_one)(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *transfer);
/* ... */
};
4.2 spi_device 结构体
struct spi_device 代表挂在 SPI 总线上的从设备,通常在设备树中描述:
c
struct spi_device {
struct device dev;
struct spi_master *master; /* 所属控制器 */
u32 max_speed_hz; /* 最大时钟频率 */
u8 chip_select; /* 片选编号 */
u8 mode; /* SPI 模式 */
u8 bits_per_word; /* 每次传输的位数 */
/* ... */
};
4.3 spi_transfer 与 spi_message
一次 SPI 通信由 spi_message 承载,其中包含一个或多个 spi_transfer,每个 transfer 描述一段独立的收发数据:
c
struct spi_transfer {
const void *tx_buf; /* 发送缓冲区 */
void *rx_buf; /* 接收缓冲区 */
unsigned len; /* 传输长度 */
u32 speed_hz; /* 本次传输速率 */
u8 bits_per_word;
u16 delay_usecs;
/* ... */
};
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
int actual_length;
/* ... */
};
5. 设备树配置
在现代 Linux 内核中,SPI 从设备通常通过设备树(Device Tree)描述。以下是一个典型的 SPI 从设备节点示例:
dts
&spi0 {
status = "okay";
cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
mcp3008: mcp3008@0 {
compatible = "microchip,mcp3008";
reg = <0>; /* 片选索引 */
spi-max-frequency = <1000000>; /* 1MHz */
spi-mode = <0>; /* Mode 0 */
};
};
设备树中的 compatible 属性用于与驱动中的 of_match_table 匹配,reg 指定片选编号,spi-max-frequency 和 spi-mode 分别配置通信速率和工作模式。
6. 编写 SPI 设备驱动
下面以一个虚拟的温度传感器驱动为例,演示 SPI 设备驱动的完整编写流程。
6.1 驱动框架
c
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
static int temp_probe(struct spi_device *spi)
{
/* 初始化设备 */
spi->mode = SPI_MODE_0;
spi->max_speed_hz = 1000000;
spi_setup(spi);
return 0;
}
static void temp_remove(struct spi_device *spi)
{
/* 清理资源 */
}
static const struct of_device_id temp_of_match[] = {
{ .compatible = "vendor,temp-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, temp_of_match);
static struct spi_driver temp_driver = {
.driver = {
.name = "temp_sensor",
.of_match_table = temp_of_match,
},
.probe = temp_probe,
.remove = temp_remove,
};
module_spi_driver(temp_driver);
MODULE_LICENSE("GPL");
6.2 数据传输
核心的数据读写通过 spi_sync_transfer 或手动构造 spi_message 完成:
c
static int temp_read_reg(struct spi_device *spi, u8 reg, u8 *val)
{
u8 tx_buf[2] = { reg, 0x00 };
u8 rx_buf[2] = { 0 };
struct spi_transfer tr = {
.tx_buf = tx_buf,
.rx_buf = rx_buf,
.len = 2,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&tr, &msg);
int ret = spi_sync(spi, &msg);
if (ret)
return ret;
*val = rx_buf[1];
return 0;
}
7. 调试技巧与常见问题
SPI 驱动调试过程中,我总结了一些实用技巧和常见坑点:
- 使用逻辑分析仪:抓取 SCLK、MOSI、MISO、CS 信号,确认时序是否符合预期,这是最直观的调试手段。
- 检查设备树 :通过
/sys/bus/spi/devices/查看设备是否成功注册,确认 compatible 匹配是否正确。 - 模式不匹配:CPOL/CPHA 配置错误会导致数据错位,务必对照数据手册确认。
- 片选信号异常:如果使用 GPIO 模拟片选,注意确认 GPIO 的 active 电平配置。
- 时钟频率过高 :超出从机支持的最大频率会导致通信不稳定,适当降低
spi-max-frequency。
8. 总结
Linux SPI 驱动开发的核心在于理解三层架构、掌握关键数据结构、正确配置设备树,并熟悉数据传输流程。建议初学者先从简单的字符设备驱动入手,配合逻辑分析仪逐步验证,再过渡到复杂的 DMA 传输和中断处理场景。希望这份学习笔记对你有帮助,欢迎在评论区交流讨论。