2. SPI驱动核心结构:spi_master/spi_device结构体解析、spi_transfer消息机制、SPI模式与时钟极性配置
SPI驱动开发,说白了就是跟三个核心结构体打交道:spi_master、spi_device 和 spi_transfer。你把这几个东西搞明白了,写驱动就顺了。我刚开始接触SPI时,光看内核文档看得一头雾水,后来自己动手调了几个板子,才真正理解它们之间的关系。
核心关系一句话:spi_master 是控制器,spi_device 是外设,spi_transfer 是它们之间的消息载体。
2.1 spi_master 结构体:控制器的灵魂
spi_master 代表一个 SPI 控制器。每个 SPI 总线都有一个 master。在嵌入式 Linux 里,它通常对应 SoC 内部的 SPI 硬件模块。
我个人习惯先看这个结构体的几个关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
bus_num |
u16 | 总线编号,系统唯一标识 |
num_chipselect |
u16 | 支持的片选数量 |
mode_bits |
u16 | 支持的 SPI 模式位掩码 |
min_speed_hz |
u32 | 最低时钟频率 |
max_speed_hz |
u32 | 最高时钟频率 |
transfer_one_message |
函数指针 | 核心传输函数,驱动必须实现 |
setup |
函数指针 | 配置硬件参数(时钟、模式等) |
注册一个 master 的典型流程:
struct spi_master *master;
struct platform_device *pdev;
// 1. 分配 master 结构体
master = spi_alloc_master(&pdev->dev, sizeof(struct my_spi_data));
if (!master)
return -ENOMEM;
// 2. 设置硬件参数
master->bus_num = 1;
master->num_chipselect = 2;
master->mode_bits = SPI_MODE_0 | SPI_MODE_1 | SPI_MODE_2 | SPI_MODE_3;
master->min_speed_hz = 100000;
master->max_speed_hz = 50000000;
// 3. 实现核心回调
master->transfer_one_message = my_spi_transfer_one_message;
master->setup = my_spi_setup;
// 4. 注册到内核
spi_register_master(master);
我的经验:bus_num 一定要跟设备树里的 alias 对应上,否则设备匹配会出问题。我曾经在这上面浪费过半天时间。
2.2 spi_device 结构体:外设的化身
spi_device 代表挂在 SPI 总线上的一个外设。它由内核在解析设备树或板级文件时自动创建,驱动开发者主要用它来获取外设的配置信息。
关键字段:
| 字段 | 说明 |
|---|---|
master |
指向所属的 spi_master |
chip_select |
片选编号,决定拉低哪根 CS 线 |
mode |
SPI 模式(CPOL/CPHA 组合) |
max_speed_hz |
该外设支持的最大时钟频率 |
bits_per_word |
每字位数,通常是 8 |
irq |
外设的中断号(如果有) |
controller_data |
控制器私有数据,平台相关 |
设备树中描述一个 SPI 外设的典型写法:
&spi1 {
status = "okay";
cs-gpios = &gpio3 10 GPIO_ACTIVE_LOW;
spidev@0 {
compatible = "my,spi-sensor";
reg = <0>; // chip_select = 0
spi-max-frequency = <10000000>;
spi-cpol; // 设置 CPOL=1
spi-cpha; // 设置 CPHA=1
// 等效于 SPI_MODE_3
};
};
注意:spi_device 不是驱动自己创建的,而是内核根据设备树或板级信息创建的。驱动要做的是在 probe 里拿到这个结构体,然后配置硬件。
2.3 spi_transfer 消息机制:数据怎么跑起来
SPI 的数据传输,靠的是 spi_transfer 结构体。它描述了一次完整的 SPI 收发操作。
核心字段:
| 字段 | 说明 |
|---|---|
tx_buf |
发送缓冲区,可以是 DMA 地址 |
rx_buf |
接收缓冲区,可以是 DMA 地址 |
len |
传输字节数 |
speed_hz |
本次传输的时钟频率(可覆盖默认值) |
bits_per_word |
本次传输的每字位数 |
cs_change |
传输完成后是否切换片选 |
delay_usecs |
传输完成后的延迟(微秒) |
一次典型的数据收发:
struct spi_device *spi;
struct spi_transfer t = {
.tx_buf = tx_data,
.rx_buf = rx_data,
.len = 32,
.speed_hz = 5000000,
.cs_change = 0,
};
struct spi_message msg;
spi_message_init(&msg);
spi_message_add_tail(&t, &msg);
// 同步传输,等待完成
spi_sync(spi, &msg);
关键点:SPI 是同步全双工协议。发送一个字节的同时,一定会收到一个字节。如果你只发不收,rx_buf 可以为 NULL;只收不发,tx_buf 可以为 NULL,内核会自动填充 0xFF。
为什么用 spi_message 包裹 spi_transfer?因为一次 SPI 操作可能包含多个 transfer 序列。比如:先发命令,再发地址,最后读数据。把它们串成一个 message,内核保证原子性执行。
2.4 SPI 模式与时钟极性配置
SPI 模式由两个参数决定:CPOL(时钟极性)和 CPHA(时钟相位)。组合起来就是 4 种模式。
我画了一张图,帮你直观理解:
配置模式时,直接设置 spi_device->mode 即可:
// 内核预定义的宏
#define SPI_MODE_0 (0|0) // CPOL=0, CPHA=0
#define SPI_MODE_1 (0|SPI_CPHA) // CPOL=0, CPHA=1
#define SPI_MODE_2 (SPI_CPOL|0) // CPOL=1, CPHA=0
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) // CPOL=1, CPHA=1
// 在驱动中设置
spi->mode = SPI_MODE_2;
spi_setup(spi); // 应用配置到硬件
避坑指南:我曾经在一个项目里,外设要求模式1,但板子默认配成了模式0。现象是数据能发出去,但读回来的全是乱码。用逻辑分析仪一看,采样边沿错了。所以,调 SPI 的第一步,先拿示波器或逻辑分析仪确认时钟极性和数据采样点对不对。
2.5 整体结构关系图
最后,我把这几个核心概念串起来,画一张关系图:
嗯,到这里,SPI 驱动的核心骨架就清楚了。你写驱动时,无非就是:注册 master、匹配 device、构造 transfer、发起 message。把这几个结构体玩熟,剩下的就是具体硬件的适配了。