一、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。