SPI
本章介绍 SPI 控制器的相关内容,面向使用 SPI 模块做驱动开发或者用户态程序开发或维护人员。具体包括设备能力,驱动和用户态程序接口,配置流程以及核心参考代码。
概述
SPI(Serial Peripheral Interface,串行外设接口)是一种同步串行通信协议,主要用于短距离设备间的高速数据交换。常见应用包括微控制器与传感器、存储器、显示屏等外设之间的通信。
SPI 采用主从架构,通过四根信号线实现全双工通信:
SCK:时钟信号(由主设备提供)MOSI:主设备输出,从设备输入MISO:主设备输入,从设备输出CS_N:片选信号(低电平有效)
SPI 不具备寻址机制,通信依赖硬件片选,具备以下特点:
- 硬件结构简单
- 无需握手机制
- 传输速率高(可达数十 MHz 乃至百 MHz 级)
- 广泛应用于 EEPROM、LCD、ADC、触控芯片等嵌入式场景
关键特性
- SPI 通道:系统共支持 6 路 SPI(
SPI0–SPI5)SPI0–SPI4位于非安全空间,可由普通应用访问,支持通用 SPI 功能SPI5位于安全空间,仅供安全模块调用,通常用于控制板载关键芯片,如 PMIC、电源芯片、RTC 等
- 支持主模式:所有 SPI 控制器均支持主模式通信
- 通信引脚:使用
SCK、CS_N、MISO、MOSI四根基本信号线 - 频率范围:
SCK时钟频率范围为 0–50 MHz,常见值为 25 MHz 和 50 MHz - 数据传输方式:支持中断模式传输(不支持
DMA) - FIFO 缓冲:每通道具有 256-word(32-bit 宽)
FIFO缓冲区
内核文件
SPI 涉及到的内核文件包含以下几个:
| 文件路径 | 用途说明 |
|---|---|
linux-mthreads/drivers/spi/spi-dw-core.c | DesignWare SPI 模块的核心处理文件 |
linux-mthreads/drivers/spi/spi-dw-m1000.c | SPI0–SPI4 模块初始化文件 |
linux-mthreads/drivers/spi/spi-dw-sm-ss.c | SPI5 模块初始化文件(仅供测试使用,非必要不建议在内核中使用 SPI5) |
linux-mthreads/drivers/spi/spi.c | SPI 子系统的通用程序 接口 |
linux-mthreads/drivers/spi/spidev.c | SPI 驱动的用户接口,生成字符设备节点供用户态访问 |
linux-mthreads/tools/spi/spidev_test.c | SPI 用户态测试示例程序 |
以用户态程序为例:
- 用户的
spidev_test.c通过/dev目录下的spidev字符设备节点与内核的spidev.c进行通信。 - 内核中的
spidev.c调用spi.c提供的通用SPI接口进行数据读写操作。 spi.c根据初始化时注册的spi-dw-core.c中的函数,完成硬件的初始化和调度。spi-dw-core.c最终调用对应的硬件相关实现文件spi-dw-m1000.c,执行具体的硬件层面数据读写操作.
API 接口
内核态核心接口
在编写 SPI device 驱动的时候,先保证打开对应的 config 配置文件和设备树相关说明,之后正常调用 Linux SPI 的标准接口即可。
SPI 相关数据结构
| 结构体/宏 | 描述 |
|---|---|
struct spi_device | 代表一个 SPI 设备,包含片选号、最大速率、模式等配置信息。 |
struct spi_driver | SPI 设备驱动结构体,需实现 probe、remove 等方法。 |
struct spi_transfer | 描述一次 SPI 传输的数据(发送/接收缓冲区、长度、速率等)。 |
struct spi_message | 包含多个 spi_transfer 的传输队列,用于组合复杂传输时序。 |
SPI 相关函数
| 函数名 | 描述 | 参数说明 | 备注 |
|---|---|---|---|
spi_setup() | 配置 SPI 设备参数(如模式、速率等)。 | struct spi_device *spi:SPI 设备指针。 | 在 probe 函数中调用。 |
spi_sync() | 同步传输 SPI 数据(阻塞直到完成)。 | struct spi_device *spi,struct spi_message *message:设备和消息指针。 | 需预先构建 spi_message。 |
spi_async() | 异步传输 SPI 数据(立即返回,通过回调通知完成)。 | struct spi_device *spi,struct spi_message *message。 | 需设置 message->complete 回调函数。 |
spi_write() | 向设备写入数据(仅发送)。 | struct spi_device *spi,const void *buf,size_t len。 | 内部封装了 spi_sync。 |
spi_read() | 从设备读取数据(仅接收)。 | struct spi_device *spi,void *buf,size_t len。 | 同样基于 spi_sync。 |
spi_write_then_read() | 先写后读(常用于命令+数据的交互)。 | struct spi_device *spi,const void *txbuf,u32 n_tx,void *rxbuf,u32 n_rx。 | 高效处理常见双阶段操作。 |
spi_message_init() | 初始化 spi_message 结构体。 | struct spi_message *m:消息指针。 | 必须在添加 spi_transfer 前调用。 |
spi_message_add_tail() | 将 spi_transfer 添加到 spi_message 队列尾部。 | struct spi_transfer *t,struct spi_message *m:传输和消息指针。 | 支持多段传输组合。 |
spi_sync_transfer() | 同步执行一组 SPI 传输(自动构建消息,无需手动初始化)。 | struct spi_device *spi,struct spi_transfer *xfers,int num_xfers:设备、传输结构数组、传输数量。 | 封装了消息构建过程,适用于单次或多段连续传输。 |
用户态核心接口
在打开默认的 spidev
节点之后(注:如想自行配置节点文件,可以参考 章节自行定义内核态接口),用户态便可通过标准系统调用(open、read、write、ioctl)和预定义的
ioctl 命令完成配置和数据传输。以下是需要关注的核心接口和结构体:
核心结构体
用户态主要依赖 spi_ioc_transfer 结构体定义 SPI 数据传输参数,定义在 linux/spi/spidev.h 中:
struct spi_ioc_transfer {
__u64 tx_buf; // 发送数据缓冲区地址(用户态指针)
__u64 rx_buf; // 接收数据缓冲区地址(用户态指针)
__u32 len; // 传输数据长度(字节数)
__u32 speed_hz; // 本次传输的时钟频率(可选覆盖设备默认值)
__u16 delay_usecs; // 传输后的延迟(微秒)
__u8 bits_per_word; // 本次传输的每字位数(可选覆盖设备默认值)
__u8 cs_change; // 传输后是否切换片选(1=切换,0=保持)
__u8 tx_nbits; // 发送数据的线宽(如 SPI_NBITS_DUAL)
__u8 rx_nbits; // 接收数据的线宽
__u8 pad[4]; // 填充字段(保留)
};
-
tx_buf / rx_buf:用户态缓冲区地址,需通过
ioctl传递指针(用户态地址会被自动转换)。 -
len:单次传输的字节数,内核默认限制最大字节为 4096 字节。
-
speed_hz:可覆盖设备默认速度,实现动态速率调整。
-
cs_change:控制片选信号,适用于多设备共享总线时的片选管理。
关键 ioctl 命令
通过 ioctl(fd, request, arg) 控制 SPI 设备,核心命令分为配置类和传输类:
// 配置类
SPI_IOC_RD_MODE // 读取 SPI 模式(CPOL/CPHA)
SPI_IOC_WR_MODE // 设置 SPI 模式
SPI_IOC_RD_LSB_FIRST // 读取字节传输顺序(0 = MSB first)
SPI_IOC_WR_LSB_FIRST // 设置字节传输顺序
SPI_IOC_RD_BITS_PER_WORD // 读取每字位数
SPI_IOC_WR_BITS_PER_WORD // 设置每字位数
SPI_IOC_RD_MAX_SPEED_HZ // 读取最大时钟频率
SPI_IOC_WR_MAX_SPEED_HZ // 设置最大时钟频率
// 传输类
SPI_IOC_MESSAGE(N) // 执行一次或多次全双工传输(N 为传输次数)
配置流程
menuconfig 选项
使用 SPI 时需要打开 SPI 对应的模块,使其编译到内核之中。

编译后,请到 drivers/spi 目录下寻找生成的 .ko 文件。根据配置选项,可能包含以下模块:
spidev.kospi-dw-m1000.kospi-dw-sm-ss.ko
请将这些模块文件替换到镜像文件系统中的对应位置:/lib/modules/6.6.10/kernel/drivers/spi/
DTS 配置说明
以下以 spi2 为例,说明相关配置含义。
节点定义在 m1000-main.dtsi 中,默认禁用,禁止直接修改原文件!
使用时应通过节点引用方式覆盖默认配置,在自己产品对应的 DTS 文件中进行修改。
主要配置参数说明:
spi2: spi2@00721000 {
compatible = "mthreads,spi-dw-m1000"; // 匹配 spi-dw-m1000.c 驱动
status = "disabled"; // 默认禁用该控制器
#address-cells = <1>; // 子设备地址用 1 个 cell 表示
#size-cells = <0>; // 子设备不需要大小说明
reg = <0x0 0x00721000 0x0 0x1000>; // 寄存器物理地址范围
interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH>; // SPI2 使用 GIC 中断 35,高电平触发
resets = <&crg RESET_PERI_SPI2>; // 关联的复位控制器
reset-names = "spi"; // 复位信号名称
reg-io-width = <4>; // 寄存器访问位宽为 32bit(4 字节)
num-cs = <1>; // 支持 1 个片选信号
clocks = <&crg PERI_SPI2_CLK>; // 时钟源配置
};
// ========== SPI2 控制器启用配置 ==========
// 配置对应的管脚,通常需要 4 个管脚,CS 管脚可自定义,具体请查管脚表
pin131: pin131 {
groups = "pin131";
function = "customized";
function-value = <1>;
};
&spi2 {
status = "okay"; // 启用 SPI2 控制器
pinctrl-names = "default";
pinctrl-0 = <&pin131 &pin132 &pin133 &pin134>;
minimal_spidev@0 {
status = "okay";
reg = <0>;
compatible = "minimal_spidev"; // 使用时请修改为自己驱动对应的名字
};
};
开发参考
内核态 device 开发可参考 linux-mthreads/drivers/spi/spidev.c 用户态 APP 开发可参考 linux-mthreads/tools/spi/spidev_test.c 下方给出一个参考示例,示例中使用 spi2 生成了一个字符节点 /dev/minimal_spidev,之后又编写了一个用户态测试脚本,用来通过此节点发送数据给设备。程序运行结果如下所示,自行使用时可修改下方用户态脚本中指出来的位置,替换为自己需要的传递内容。

Device Tree Pin Configuration (.dts)
// 配置 SPI2 所对应的管脚,注意管脚号和对应的 function-value
pin131: pin131 {
groups = "pin131";
function = "customized";
function-value = <1>;
};
pin132: pin132 {
groups = "pin132";
function = "customized";
function-value = <1>;
};
pin133: pin133 {
groups = "pin133";
function = "customized";
function-value = <1>;
};
pin134: pin134 {
groups = "pin134";
function = "customized";
function-value = <1>;
};
// 引用定义好的 pinctrl 管脚,增加最简单的 minimal_spidev 节点
&spi2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pin131 &pin132 &pin133 &pin134>;
minimal_spidev@0 {
status = "okay";
reg = <0>;
compatible = "minimal_spidev"; // 使用时修改为自己的驱动对应的名字
};
};
Kernel Driver Code (C)
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/device.h>
// 定义 IOCTL 命令
#define MY_SPI_IOCTL_MAGIC 'k'
#define MY_SPI_IOCTL_SETUP _IOW(MY_SPI_IOCTL_MAGIC, 1, struct spi_setup_params)
#define MY_SPI_IOCTL_SYNC_XFER _IOWR(MY_SPI_IOCTL_MAGIC, 2, struct spi_xfer)
#define MY_SPI_IOCTL_ASYNC_XFER _IOW(MY_SPI_IOCTL_MAGIC, 3, struct spi_xfer_async)
#define MY_SPI_IOCTL_SIMPLE_WRITE _IOW(MY_SPI_IOCTL_MAGIC, 4, struct spi_write_args)
#define MY_SPI_IOCTL_SIMPLE_READ _IOR(MY_SPI_IOCTL_MAGIC, 5, struct spi_read_args)
#define MY_SPI_IOCTL_WRITE_THEN_READ _IOWR(MY_SPI_IOCTL_MAGIC, 6, struct spi_wtr_args)
#define MY_SPI_IOCTL_MULTI_XFER _IOW(MY_SPI_IOCTL_MAGIC, 7, struct spi_multi_xfer)
#define BUFFER_SIZE 4096
// 定义数据结构
struct spi_xfer {
unsigned char *tx_buf;
unsigned char *rx_buf;
int len;
};
struct spi_setup_params {
__u32 mode;
__u32 max_speed_hz;
__u8 bits_per_word;
};
struct spi_write_args {
unsigned char *tx_buf;
__u32 len;
};
struct spi_read_args {
unsigned char *rx_buf;
__u32 len;
};
struct spi_rdwr_args {
unsigned char *tx_buf;
__u32 tx_len;
unsigned char *rx_buf;
__u32 rx_len;
};
#define MAX_TRANSFERS 8
struct spi_xfer_async {
void __user *tx_buf;
void __user *rx_buf;
int len;
};
struct spi_wtr_args {
void __user *tx_buf;
void __user *rx_buf;
int tx_len;
int rx_len;
};
struct spi_multi_xfer {
struct spi_transfer __user *transfers;
int num_transfers;
int total_len;
};
struct minimal_spidev {
struct spi_device *spi;
struct mutex lock;
unsigned char *buffer;
struct cdev cdev;
dev_t devt;
struct class *cls;
};
// 设备操作函数
static int minimal_spidev_open(struct inode *inode, struct file *filp)
{
struct minimal_spidev *mdev = container_of(inode->i_cdev, struct minimal_spidev, cdev);
filp->private_data = mdev;
return 0;
}
static int minimal_spidev_release(struct inode *inode, struct file *filp)
{
return 0;
}
// IOCTL 解析函数(部分核心命令展示)
static long minimal_spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct minimal_spidev *mdev = filp->private_data;
int ret = 0;
if (_IOC_TYPE(cmd) != MY_SPI_IOCTL_MAGIC)
return -ENOTTY;
mutex_lock(&mdev->lock);
switch (cmd) {
case MY_SPI_IOCTL_SETUP: {
struct spi_setup_params params;
if (copy_from_user(¶ms, (void __user *)arg, sizeof(params))) {
ret = -EFAULT;
break;
}
mdev->spi->mode = params.mode;
mdev->spi->max_speed_hz = params.max_speed_hz;
mdev->spi->bits_per_word = params.bits_per_word;
ret = spi_setup(mdev->spi);
break;
}
case MY_SPI_IOCTL_SYNC_XFER: {
struct spi_xfer xfer;
struct spi_message msg;
struct spi_transfer t = {0};
if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer))) {
ret = -EFAULT;
break;
}
if (xfer.len > BUFFER_SIZE) {
ret = -EINVAL;
break;
}
if (xfer.tx_buf && copy_from_user(mdev->buffer, xfer.tx_buf, xfer.len)) {
ret = -EFAULT;
break;
}
t.tx_buf = xfer.tx_buf ? mdev->buffer : NULL;
t.rx_buf = xfer.rx_buf ? mdev->buffer : NULL;
t.len = xfer.len;
spi_message_init(&msg);
spi_message_add_tail(&t, &msg);
ret = spi_sync(mdev->spi, &msg);
if (ret == 0 && xfer.rx_buf) {
if (copy_to_user(xfer.rx_buf, mdev->buffer, xfer.len))
ret = -EFAULT;
}
break;
}
// 其他 ioctl 命令省略(参照你提供的代码完整实现)
default:
ret = -ENOTTY;
break;
}
mutex_unlock(&mdev->lock);
return ret;
}
// 文件操作结构体
static const struct file_operations minimal_spidev_fops = {
.owner = THIS_MODULE,
.open = minimal_spidev_open,
.release = minimal_spidev_release,
.unlocked_ioctl = minimal_spidev_ioctl,
};
// 设备初始化、移除、驱动注册函数省略(完整代码请参考上文)
User-Space SPI Test Program (test_spidev.c)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/spi/spidev.h>
#define BUFFER_SIZE 4096
#define SPI_DEVICE "/dev/spi0_spidev.0" // 标准设备节点路径
int main(int argc, char *argv[])
{
int fd, ret;
static unsigned char tx_buffer[BUFFER_SIZE];
static unsigned char rx_buffer[BUFFER_SIZE];
struct spi_ioc_transfer xfer = {0};
// 打开设备
fd = open(SPI_DEVICE, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
exit(EXIT_FAILURE);
}
// 配置参数
uint32_t mode = SPI_MODE_0;
uint8_t bits = 8;
uint32_t speed = 500000; // 500 kHz
ioctl(fd, SPI_IOC_WR_MODE, &mode);
ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
printf("\n=== Test Case 1: Normal Transfer ===\n");
const char *test_str = "Hello, SPI Device!";
strncpy((char *)tx_buffer, test_str, sizeof(tx_buffer));
// 传输参数配置
xfer.tx_buf = (unsigned long)tx_buffer;
xfer.rx_buf = (unsigned long)rx_buffer;
xfer.len = strlen(test_str) + 1;
xfer.speed_hz = speed;
xfer.bits_per_word = bits;
// 执行 SPI 传输
ret = ioctl(fd, SPI_IOC_MESSAGE(1), &xfer);
if (ret < 0) {
perror("IOCTL failed");
} else {
printf("Sent %d bytes: %s\n", xfer.len, (char *)tx_buffer);
printf("Received %d bytes: %s\n", xfer.len, (char *)rx_buffer);
}
close(fd);
return 0;
}
调试方法
本章节提供一些常见的调试方向,以应对 SPI 通信或驱动开发过程中遇到的问题。
硬件检查
-
物理连接验证
- 确认
CS(片选)、SCLK(时钟)、MOSI(主发从收)、MISO(从发主收)连线正确,无短路/断路。 - 检查设备供电电压是否稳定,
SPI电平是否与控制器匹配(如 3.3V vs 1.8V)。
- 确认
-
SPI 模式匹配
- 核对外设支持的 SPI 模式(
CPOL和CPHA),确保驱动中配置一致(模式 0/1/2/3)。 - 检查配置的时钟频率是否在设备允许范围内。建议从较低频率开始测试,逐步提升。
- 核对外设支持的 SPI 模式(
-
波形查看
- 使用示波器或逻辑分析仪,观察
SPI总线的电平波形,确认传输逻辑符合预期。 - 重点查看
SCLK、CS、MOSI、MISO四根信号是否同步、抖动、时序错误等问题。
- 使用示波器或逻辑分析仪,观察
软件调试
-
确认设备树(DTS)配置正确。
-
在关键函数或节点增加打印(如
printk),查看是否可以正常输出和运行。 -
可以暂时将驱动编译为模块(M 模式),方便与其他模块隔离,独立观察影响。
常见问题
Q: 如果不使用默认的 CS 管脚,想使用自定义的 CS 管脚应该如何做?
A: 需要修改 spi-dw-m1000.c 文件,参考 spi-dw-sm-ss.c
中的片选配置方式,将 CS 控制由硬件配置修改为程序控制(GPIO 控制)。
实现步骤:
- 在
spi-dw-m1000.c中增加以下函数,实现自定义 GPIO 控制:
static void dw_spi_set_cs_gpio(struct spi_device *spi)
{
// 可以在此处实现 GPIO 配置逻辑,借助内核框架调用 GPIO 接口。
// 相比默认硬件控制,此方式可以提前几毫秒拉低 CS,更灵活。
// 若不在此实现逻辑,也可在数据传输前后手动控制拉低/拉高 CS。
return;
}
- 在初始化 SPI 控制器时,将该函数赋值给
dws->set_cs:
dws->set_cs = dw_spi_set_cs_gpio;
这样配置后,驱动框架会自动调用 dw_spi_set_cs_gpio() 控制 CS 引脚,实现软控制方式。

