嵌入式学习 day68:驱动——SPI总线

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

相关推荐
其实防守也摸鱼1 小时前
CTF Web 6 题全解 Writeup
linux·sql·学习·算法·安全·ctf·xss
Xudde.2 小时前
DC-1 靶机渗透测试(Vulnhub Writeup)
笔记·学习·安全·web安全
浪子不回头4152 小时前
同花顺一面
学习
小王师傅662 小时前
LangChain 系统化学习入门
学习·langchain
dualven_in_csdn2 小时前
【网络安全】fscan的学习与使用
网络·学习
skywalk81632 小时前
Monty / 千脑理论(Thousand Brains Project)学习笔记
笔记·学习·monty·千脑理论
kyrie_sakura2 小时前
大模型学习15 - npl自然语言处理16 - Transformer模型API(PyTorch)
学习·自然语言处理·transformer
for_ever_love__2 小时前
LangChain 效果评测:没有评测,优化就是在瞎调
人工智能·学习·langchain·rag·评估·langsmith·评测指标
li星野3 小时前
Subject–Verb Agreement — One-Page Notes
学习