一、SPI 总线基础
1. 什么是 SPI
前面已经学习过SPI了。SPI(Serial Peripheral Interface)是一种高速串行通信总线,和 I2C 类似,但速度更快,适合 Flash、屏幕、传感器等设备。
2. SPI 的四根线
| 线 | 全称 | 作用 |
|---|---|---|
| SCLK | Serial Clock | 时钟线 |
| MOSI | Master Out Slave In | 主机发数据 |
| MISO | Master In Slave Out | 主机收数据 |
| CS | Chip Select | 片选线 |
每个 SPI 从设备有一根独立的 CS 线,主机拉低哪根 CS,就和哪个设备通信。
3. SPI 和 I2C 对比
| 项目 | I2C | SPI |
|---|---|---|
| 线数 | 2 根 | 4 根 |
| 速度 | 慢(100k~400k) | 快(几 MHz~几十 MHz) |
| 设备数 | 多(靠地址区分) | 多(靠片选线区分) |
| 全双工 | 半双工 | 全双工 |
| 典型设备 | EEPROM、温度传感器 | Flash、屏幕、ADC |
二、SPI 驱动的整体框架
text
头文件
↓
全局变量(spi_device)
↓
初始化函数(读设备 ID、配置寄存器)
↓
读取函数(读数据)
↓
file_operations(绑定 read)
↓
miscdevice(描述 misc 设备)
↓
probe(SPI 匹配成功时调用)
↓
remove(卸载时调用)
↓
spi_driver + spi_register_driver
三、SPI 驱动的两种传输方式
方式一:spi_write_then_read(简单)
c
int spi_write_then_read(struct spi_device *spi,
const void *txbuf, unsigned n_tx,
void *rxbuf, unsigned n_rx);
| 参数 | 含义 |
|---|---|
spi |
SPI 设备 |
txbuf |
发送缓冲区 |
n_tx |
发送字节数 |
rxbuf |
接收缓冲区 |
n_rx |
接收字节数 |
先发 txbuf,再收 rxbuf。适合先写命令再读数据的场景(比如寄存器读写)。
用法示例:
c
// 读寄存器
tx[0] = 0x00 | (0x1 << 7); // 寄存器地址 + 读标志
spi_write_then_read(spi, tx, 1, &rx, 1);
// 写寄存器
tx[0] = 0x31; // 寄存器地址
tx[1] = 0x0b; // 要写的数据
spi_write_then_read(spi, tx, 2, NULL, 0);
方式二:spi_sync + spi_message(复杂)
c
struct spi_transfer transfer = {
.tx_buf = tx,
.rx_buf = rx,
.len = 7,
.delay_usecs = 20,
};
struct spi_message sendmsg;
spi_message_init(&sendmsg);
spi_message_add_tail(&transfer, &sendmsg);
spi_sync(spi, &sendmsg);
| 结构体 | 作用 |
|---|---|
spi_transfer |
一次 SPI 传输(发送/接收缓冲区、长度) |
spi_message |
一次完整的 SPI 消息(可以包含多个 transfer) |
适合多段传输、自定义时序的场景。
两种方式对比
| 方式 | 函数 | 适合 |
|---|---|---|
| 简单 | spi_write_then_read |
先写命令,再读数据 |
| 复杂 | spi_sync + spi_message |
多段传输、自定义时序 |
四、SPI 驱动的关键结构体
1. spi_device
c
static struct spi_device *padxl345_spi = NULL;
| 成员 | 含义 |
|---|---|
max_speed_hz |
最大时钟频率 |
chip_select |
片选编号 |
bits_per_word |
每字位数 |
mode |
SPI 模式(0~3) |
2. spi_transfer
c
struct spi_transfer {
const void *tx_buf; // 发送缓冲区
void *rx_buf; // 接收缓冲区
unsigned len; // 数据长度
unsigned delay_usecs; // 传输后延时
};
3. spi_message
c
struct spi_message {
struct list_head transfers; // 传输链表
};
五、SPI 驱动的完整代码结构
1. 头文件
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/spi/spi.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
2. 全局变量
c
static struct spi_device *padxl345_spi = NULL;
3. 初始化函数
c
static int adxl345_init(void)
{
int ret = 0;
unsigned char tx[2] = {0};
unsigned char rx = {0};
// 1. 读设备 ID
tx[0] = 0x00 | (0x1 << 7);
ret = spi_write_then_read(padxl345_spi, tx, 1, &rx, 1);
// 2. 配置寄存器
tx[0] = 0x31;
tx[1] = 0x0b;
ret = spi_write_then_read(padxl345_spi, tx, 2, NULL, 0);
// 3. 开启采集
tx[0] = 0x2D;
tx[1] = 0x08;
ret = spi_write_then_read(padxl345_spi, tx, 2, NULL, 0);
return 0;
}
4. 读取函数
c
static int adxl345_read_xyz(short *pxa, short *pya, short *pza)
{
int ret = 0;
unsigned char tx[7] = {0};
unsigned char rx[7] = {0};
struct spi_message sendmsg;
struct spi_transfer transfer = {
.tx_buf = tx,
.rx_buf = rx,
.len = 7,
.delay_usecs = 20,
};
spi_message_init(&sendmsg);
tx[0] = (0x32 | (0x1 << 7) | (0x1 << 6)); // 多字节读
spi_message_add_tail(&transfer, &sendmsg);
ret = spi_sync(padxl345_spi, &sendmsg);
*pxa = (rx[2] << 8 | rx[1]);
*pya = (rx[4] << 8 | rx[3]);
*pza = (rx[6] << 8 | rx[5]);
return 0;
}
5. 应用层接口
c
static ssize_t adxl345_read(struct file *fp, char __user *puser, size_t n, loff_t *off)
{
unsigned long nret = 0;
int ret = 0;
short acc[3] = {0};
ret = adxl345_init();
ret = adxl345_read_xyz(&acc[0], &acc[1], &acc[2]);
nret = copy_to_user(puser, acc, sizeof(acc));
if (nret) {
pr_info("copy_to_user failed\n");
return -1;
}
return sizeof(acc);
}
6. file_operations
c
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = adxl345_read,
};
7. miscdevice
c
static struct miscdevice adxl345_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "adxl345_misc",
.fops = &fops,
};
8. probe
c
static int adxl345_probe(struct spi_device *spi)
{
int ret = 0;
pr_info("max_speed_hz = %d\n", spi->max_speed_hz);
pr_info("chip_select = %d\n", spi->chip_select);
pr_info("bits_per_word = %d\n", spi->bits_per_word);
pr_info("mode = %d\n", spi->mode);
padxl345_spi = spi;
ret = misc_register(&adxl345_misc);
if (ret) {
pr_info("misc_register failed\n");
return -1;
}
return 0;
}
9. remove
c
static int adxl345_remove(struct spi_device *spi)
{
misc_deregister(&adxl345_misc);
return 0;
}
10. 匹配表
c
static struct spi_device_id adxl345_id_table[] = {
{.name = "puteadxl345"},
{},
};
static struct of_device_id adxl345_of_match_table[] = {
{.compatible = "pute,puteadxl345"},
{},
};
11. spi_driver
c
static struct spi_driver adxl345_drv = {
.probe = adxl345_probe,
.remove = adxl345_remove,
.driver = {
.name = "puteadxl345",
.owner = THIS_MODULE,
.of_match_table = adxl345_of_match_table,
},
.id_table = adxl345_id_table,
};
12. 注册和注销
c
static int __init adxl345_drv_init(void)
{
return spi_register_driver(&adxl345_drv);
}
static void __exit adxl345_drv_exit(void)
{
spi_unregister_driver(&adxl345_drv);
}
module_init(adxl345_drv_init);
module_exit(adxl345_drv_exit);
六、设备树
dts
&ecspi1 {
status = "okay";
adxl345@0 {
compatible = "pute,puteadxl345";
reg = <0>; // 片选编号
spi-max-frequency = <1000000>; // 最大时钟频率
spi-cpol; // 时钟极性
spi-cpha; // 时钟相位
};
};
| 属性 | 作用 |
|---|---|
compatible |
驱动匹配 |
reg |
片选编号 |
spi-max-frequency |
最大时钟频率 |
spi-cpol / spi-cpha |
SPI 模式(0~3) |
七、应用程序
c
int fd = open("/dev/adxl345_misc", O_RDWR);
short acc[3] = {0};
read(fd, acc, sizeof(acc));
printf("X: %d, Y: %d, Z: %d\n", acc[0], acc[1], acc[2]);
close(fd);
八、SPI 和 I2C 驱动对比
| 项目 | I2C | SPI |
|---|---|---|
| 设备结构 | i2c_client |
spi_device |
| 驱动结构 | i2c_driver |
spi_driver |
| 传输结构 | i2c_msg |
spi_transfer + spi_message |
| 简单传输 | master_xfer |
spi_write_then_read |
| 复杂传输 | i2c_transfer |
spi_sync |
| 注册 | i2c_add_driver |
spi_register_driver |
| 注销 | i2c_del_driver |
spi_unregister_driver |
| probe 参数 | i2c_client * |
spi_device * |
框架一样,API 不同。
九、总结
| 部分 | 作用 |
|---|---|
spi_device |
描述 SPI 从设备 |
spi_transfer |
描述一次 SPI 传输 |
spi_message |
描述一次完整的 SPI 消息 |
spi_write_then_read |
简单读写 |
spi_sync |
同步执行复杂传输 |
spi_register_driver |
注册 SPI 驱动 |
compatible |
设备树匹配 |
SPI 驱动的框架和 platform、I2C 一样,只是总线 API 不同。掌握一种,其他就是换 API。