一、什么是 SPI
SPI(Serial Peripheral Interface) 是一种同步串行全双工总线协议,由 Motorola 提出,广泛用于嵌入式系统中连接传感器、Flash、DAC、显示屏等外设。
1.1 物理信号线
| 信号 | 方向 | 说明 |
|---|---|---|
| SCLK | Master → Slave | 时钟,由 Master 产生 |
| MOSI | Master → Slave | Master Out Slave In,主发从收 |
| MISO | Slave → Master | Master In Slave Out,从发主收 |
| CS/SS | Master → Slave | 片选,低有效(通常) |
多个 Slave 时,每个 Slave 独享一根 CS 线,其余三线共享。
1.2 与 I²C 对比
| 维度 | SPI | I²C |
|---|---|---|
| 线数 | 4(+N个CS) | 2 |
| 速度 | 几 MHz ~ 百 MHz | 100K / 400K / 1MHz |
| 全双工 | ✅ | ❌(半双工) |
| 寻址 | CS 硬件片选 | 7/10-bit 软件地址 |
| 多主 | 不标准 | ✅ 支持仲裁 |
| 适用场景 | 高速、传感器、Flash | 低速、多设备、配置总线 |
二、SPI 时序模式(CPOL / CPHA)
SPI 有 4 种模式,由时钟极性(CPOL)和相位(CPHA)决定:
| 模式 | CPOL | CPHA | 空闲时钟 | 采样沿 |
|---|---|---|---|---|
| MODE 0 | 0 | 0 | 低 | 上升沿 |
| MODE 1 | 0 | 1 | 低 | 下降沿 |
| MODE 2 | 1 | 0 | 高 | 下降沿 |
| MODE 3 | 1 | 1 | 高 | 上升沿 |
MODE 0 (CPOL=0, CPHA=0):
SCLK ─┐ ┌─┐ ┌─┐ ┌─
└─┘ └─┘ └─┘
MOSI ═══X═══X═══X══ 在时钟上升沿被 Slave 采样
↑采样
ICM-20608 使用 SPI_MODE_0(CPOL=0,CPHA=0)。
三、Linux SPI 子系统架构
┌──────────────────────────────────────────┐
│ 用户空间 │
│ spidev(/dev/spidevX.Y) 或 内核驱动 │
└──────────────────┬───────────────────────┘
│
┌──────────────────▼───────────────────────┐
│ SPI 核心层(spi.c) │
│ spi_register_driver / spi_sync │
│ 设备-驱动匹配(of_match / id_table) │
└──────────────────┬───────────────────────┘
│
┌──────────────────▼───────────────────────┐
│ SPI 控制器驱动(spi-imx.c) │
│ struct spi_master / spi_controller │
│ DMA / PIO 传输实现 │
└──────────────────┬───────────────────────┘
│ 寄存器操作
┌──────────────────▼───────────────────────┐
│ 硬件 SPI 控制器 │
│ iMX6ULL ECSPI1~4 │
└──────────────────────────────────────────┘
四、核心数据结构
4.1 struct spi_device — SPI 从设备
代表总线上的一个 Slave 设备,由内核根据 DTS 自动创建。
struct spi_device {
struct device dev;
struct spi_master *master; /* 所属控制器 */
u32 max_speed_hz; /* 最大时钟频率 */
u8 chip_select; /* CS 编号 */
u8 bits_per_word;/* 字长,通常 8 */
u16 mode; /* SPI_MODE_0~3 等 */
int irq; /* 中断号(DTS 配置后自动填充)*/
/* ... */
};
常用 mode 标志:
| 标志 | 说明 |
|---|---|
SPI_MODE_0~3 | 时序模式(CPOL/CPHA 组合) |
SPI_CS_HIGH | CS 高有效(非标准) |
SPI_LSB_FIRST | 低位先发 |
SPI_3WIRE | 半双工(MOSI/MISO 共用) |
4.2 struct spi_transfer — 单次传输描述
struct spi_transfer {
const void *tx_buf; /* 发送缓冲区(NULL = 发全0)*/
void *rx_buf; /* 接收缓冲区(NULL = 丢弃)*/
unsigned len; /* 字节数 */
u32 speed_hz; /* 本次传输的时钟(0=用设备默认)*/
u8 bits_per_word;
u16 delay_usecs; /* 传输后延迟 */
unsigned cs_change:1; /* 传输后是否切换 CS */
struct list_head transfer_list;
};
4.3 struct spi_message — 一次完整事务
一个 spi_message 包含一组 spi_transfer,CS 在整个 message 期间保持激活:
struct spi_message {
struct list_head transfers; /* spi_transfer 链表 */
struct spi_device *spi;
int status; /* 完成后的错误码 */
/* ... */
};
/* 使用示例 */
struct spi_message msg;
struct spi_transfer xfer[2];
spi_message_init(&msg);
xfer[0].tx_buf = cmd_buf;
xfer[0].len = 1;
spi_message_add_tail(&xfer[0], &msg);
xfer[1].rx_buf = rx_buf;
xfer[1].len = 14;
spi_message_add_tail(&xfer[1], &msg);
ret = spi_sync(spi, &msg); /* 同步等待完成 */
4.4 struct spi_driver — 驱动描述
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
struct device_driver driver;
};
4.5 数据结构全景类图
classDiagram
class spi_device {
+spi_master *master
+u32 max_speed_hz
+u8 chip_select
+u8 bits_per_word
+u16 mode
+int irq
}
class spi_master {
+u16 bus_num
+int (*transfer_one_message)()
+int num_chipselect
}
class spi_transfer {
+const void *tx_buf
+void *rx_buf
+unsigned len
+u32 speed_hz
}
class spi_message {
+list_head transfers
+spi_device *spi
+int status
}
class spi_driver {
+probe() int
+remove() int
+id_table
+of_match_table
}
class DriverPrivData {
<<驱动私有数据>>
+spi_device *spi
+mutex lock
}
spi_device --> spi_master : .master
spi_message "1" --> "N" spi_transfer : transfers 链表
spi_message --> spi_device : .spi
spi_driver ..> spi_device : probe 匹配
DriverPrivData --> spi_device : .spi 指针五、常用 API
5.1 便捷函数(推荐优先使用)
/* 只写 N 字节 */
int spi_write(struct spi_device *spi, const void *buf, size_t len);
/* 只读 N 字节 */
int spi_read(struct spi_device *spi, void *buf, size_t len);
/*
* 先写 n_tx 字节,再读 n_rx 字节,同一 CS 事务内完成。
* 最适合"写寄存器地址 + 读数据"的读操作模式。
*/
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);
5.2 手动构造 spi_message(需要精细控制时)
int spi_sync(struct spi_device *spi, struct spi_message *message);
int spi_async(struct spi_device *spi, struct spi_message *message);
/* spi_async 不阻塞,完成后调用 message->complete 回调 */
5.3 probe 中必须调用 spi_setup
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
ret = spi_setup(spi); /* 将配置写入控制器硬件 */
5.4 驱动数据绑定
/* probe 中:把 iio_dev 绑定到 spi_device */
spi_set_drvdata(spi, indio_dev);
/* remove 中:取回 */
struct iio_dev *indio_dev = spi_get_drvdata(spi);
六、SPI 驱动开发模板
/* 1. 定义私有数据 */
struct mydev_data {
struct spi_device *spi;
struct mutex lock;
};
/* 2. 寄存器读写 */
static int mydev_read(struct spi_device *spi, u8 reg, u8 *val)
{
u8 cmd = reg | BIT(7); /* 读标志(视芯片协议而定)*/
return spi_write_then_read(spi, &cmd, 1, val, 1);
}
static int mydev_write(struct spi_device *spi, u8 reg, u8 val)
{
u8 buf[2] = { reg, val };
return spi_write(spi, buf, 2);
}
/* 3. probe */
static int mydev_probe(struct spi_device *spi)
{
struct mydev_data *data;
int ret;
/* 配置 SPI 参数 */
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
ret = spi_setup(spi);
if (ret) return ret;
data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
data->spi = spi;
mutex_init(&data->lock);
spi_set_drvdata(spi, data);
/* 硬件初始化 ... */
return 0;
}
static int mydev_remove(struct spi_device *spi)
{
/* devm_ 资源自动释放,这里只需关闭硬件 */
return 0;
}
/* 4. 驱动注册 */
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "vendor,mydev" },
{ }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
static struct spi_driver mydev_driver = {
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
},
.probe = mydev_probe,
.remove = mydev_remove,
};
module_spi_driver(mydev_driver);
七、iMX6ULL DTS 配置
7.1 最小配置
/* iomuxc 中定义引脚复用 */
&iomuxc {
pinctrl_ecspi3: ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS (GPIO) */
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1
>;
};
};
/* ECSPI3 控制器节点 */
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 软件 GPIO 片选 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
mydev@0 {
compatible = "vendor,mydev";
reg = <0>; /* CS 编号 */
spi-max-frequency = <8000000>; /* 8 MHz */
};
};
7.2 带 INT 引脚的配置
mydev@0 {
compatible = "vendor,mydev";
reg = <0>;
spi-max-frequency = <8000000>;
interrupt-parent = <&gpio4>;
interrupts = <16 IRQ_TYPE_EDGE_RISING>; /* INT 引脚 → GPIO4_IO16 */
};
内核会将 INT 引脚的 GPIO 转换为 spi->irq,驱动直接使用。
7.3 硬件 CS vs 软件 GPIO CS
| 方式 | 说明 |
|---|---|
| 硬件 CS | DTS 中用 ECSPI3_SS0 复用,控制器自动控制 |
| 软件 GPIO CS(推荐) | DTS 中用 GPIO1_IO20 复用 + cs-gpios 属性,内核 SPI 核心控制 |
iMX6ULL 的 ECSPI 硬件 CS 存在时序问题(CS 在每个字节之间会短暂拉高),推荐使用软件 GPIO CS。
八、spi_write_then_read 内部原理
CS ──┐ ┌──
└──────────────────────────────┘
SCLK ┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐ ┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐
MOSI ══╪══ reg|0x80 ══╪══╪══ dummy ═╪═
MISO ══╪══ (忽略) ══╪══╪══ data ═╪═
←── n_tx=1B ──→ ←── n_rx=NB ──→
└──────────── 同一 CS 事务 ───────┘
内部会构造两个 spi_transfer,通过一个 spi_message 一次性提交,CS 全程不拉高,保证读写操作的原子性。
九、常见问题
Q1:probe 不执行(driver not bound)
- DTS 的
compatible字符串与驱动of_match_table不一致 - DTS 节点
status = "disabled"或未写 - 父节点(ecspi3)的
status未设为"okay" - 引脚被其他节点(如 uart2)占用导致 pinctrl 申请失败
Q2:SPI 读取全是 0xFF
- SPI MODE 不匹配(检查 CPOL/CPHA)
spi_setup未调用- CS 极性错误(active high vs active low)
spi-max-frequency设置过高,信号质量差
Q3:spi_sync 返回 -EINVAL
spi_transfer.len为 0tx_buf和rx_buf同时为 NULLbits_per_word控制器不支持(一般用 8)
Q4:不能在中断上下文调用 spi_sync
spi_sync 内部可能睡眠等待 DMA 完成,禁止在中断处理程序中直接调用。
解决方案:使用 Workqueue 下半部(见 17_ICM20608_SPI_IIO驱动开发)。
十、参考资料
Documentation/spi/— 内核官方 SPI 文档drivers/spi/spi-imx.c— iMX6ULL SPI 控制器驱动include/linux/spi/spi.h— SPI 核心头文件- 17_ICM20608_SPI_IIO驱动开发 — SPI IIO 驱动实战
延伸阅读
理解 SPI 核心结构后,可进入 ICM-20608 实战,并与通用设备模型建立联系。
- 返回嵌入式 Linux 学习路线,确认本文在完整知识结构中的位置。
- ICM-20608 SPI IIO 驱动开发
- Linux Platform 设备驱动模型




