Linux SPI 驱动开发学习笔记:从框架到实战

1. 引言

SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的通信总线,广泛应用于嵌入式 Linux 系统中连接传感器、Flash、ADC、显示屏等外设。本文记录我在学习 Linux SPI 驱动开发过程中的核心知识点、框架结构和实战经验,希望能帮助同样在学习 SPI 驱动的读者少走弯路。

2. SPI 协议基础

在深入 Linux 驱动之前,先回顾 SPI 协议的基本概念。SPI 总线由四根信号线组成:

  • SCLK(时钟线):由主机产生,用于同步数据传输。
  • MOSI(主机输出从机输入):主机向从机发送数据。
  • MISO(主机输入从机输出):从机向主机发送数据。
  • CS(片选线):低电平有效,用于选中目标从机设备。

SPI 通信有四种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)组合决定:

模式 CPOL CPHA 数据采样时机
Mode 0 0 0 时钟上升沿采样
Mode 1 0 1 时钟下降沿采样
Mode 2 1 0 时钟下降沿采样
Mode 3 1 1 时钟上升沿采样

驱动开发中必须根据从机数据手册正确配置模式,否则通信将完全失败。

3. Linux SPI 子系统架构

Linux 内核将 SPI 驱动划分为三个层次,理解这个分层结构是学习的关键:

  • 控制器驱动(SPI Master Driver):负责操作具体的 SPI 控制器硬件,如寄存器读写、DMA 传输等。
  • 核心层(SPI Core):提供统一的框架,管理总线、设备和驱动的注册与匹配。
  • 设备驱动(SPI Device Driver):面向具体外设,实现读写逻辑和业务功能。

这种分层设计使得控制器驱动与设备驱动解耦,同一外设驱动可以运行在不同平台的 SPI 控制器上。

4. 关键数据结构

Linux SPI 子系统围绕几个核心数据结构展开,理解它们是编写驱动的前提。

4.1 spi_master 结构体

struct spi_master 代表一个 SPI 控制器,主要字段包括:

c 复制代码
struct spi_master {
    struct device dev;
    u16 bus_num;              /* 总线编号 */
    u32 mode_bits;            /* 支持的 SPI 模式 */
    u32 bits_per_word_mask;   /* 支持的位宽 */
    u32 min_speed_hz;
    u32 max_speed_hz;
    int (*transfer_one)(struct spi_master *master,
                        struct spi_device *spi,
                        struct spi_transfer *transfer);
    /* ... */
};

4.2 spi_device 结构体

struct spi_device 代表挂在 SPI 总线上的从设备,通常在设备树中描述:

c 复制代码
struct spi_device {
    struct device dev;
    struct spi_master *master;  /* 所属控制器 */
    u32 max_speed_hz;           /* 最大时钟频率 */
    u8 chip_select;             /* 片选编号 */
    u8 mode;                    /* SPI 模式 */
    u8 bits_per_word;           /* 每次传输的位数 */
    /* ... */
};

4.3 spi_transfer 与 spi_message

一次 SPI 通信由 spi_message 承载,其中包含一个或多个 spi_transfer,每个 transfer 描述一段独立的收发数据:

c 复制代码
struct spi_transfer {
    const void *tx_buf;   /* 发送缓冲区 */
    void *rx_buf;         /* 接收缓冲区 */
    unsigned len;         /* 传输长度 */
    u32 speed_hz;         /* 本次传输速率 */
    u8 bits_per_word;
    u16 delay_usecs;
    /* ... */
};

struct spi_message {
    struct list_head transfers;
    struct spi_device *spi;
    int actual_length;
    /* ... */
};

5. 设备树配置

在现代 Linux 内核中,SPI 从设备通常通过设备树(Device Tree)描述。以下是一个典型的 SPI 从设备节点示例:

dts 复制代码
&spi0 {
    status = "okay";
    cs-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;

    mcp3008: mcp3008@0 {
        compatible = "microchip,mcp3008";
        reg = <0>;                 /* 片选索引 */
        spi-max-frequency = <1000000>;  /* 1MHz */
        spi-mode = <0>;            /* Mode 0 */
    };
};

设备树中的 compatible 属性用于与驱动中的 of_match_table 匹配,reg 指定片选编号,spi-max-frequency 和 spi-mode 分别配置通信速率和工作模式。

6. 编写 SPI 设备驱动

下面以一个虚拟的温度传感器驱动为例,演示 SPI 设备驱动的完整编写流程。

6.1 驱动框架

c 复制代码
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/of.h>

static int temp_probe(struct spi_device *spi)
{
    /* 初始化设备 */
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 1000000;
    spi_setup(spi);
    return 0;
}

static void temp_remove(struct spi_device *spi)
{
    /* 清理资源 */
}

static const struct of_device_id temp_of_match[] = {
    { .compatible = "vendor,temp-sensor" },
    { }
};
MODULE_DEVICE_TABLE(of, temp_of_match);

static struct spi_driver temp_driver = {
    .driver = {
        .name = "temp_sensor",
        .of_match_table = temp_of_match,
    },
    .probe = temp_probe,
    .remove = temp_remove,
};
module_spi_driver(temp_driver);

MODULE_LICENSE("GPL");

6.2 数据传输

核心的数据读写通过 spi_sync_transfer 或手动构造 spi_message 完成:

c 复制代码
static int temp_read_reg(struct spi_device *spi, u8 reg, u8 *val)
{
    u8 tx_buf[2] = { reg, 0x00 };
    u8 rx_buf[2] = { 0 };

    struct spi_transfer tr = {
        .tx_buf = tx_buf,
        .rx_buf = rx_buf,
        .len = 2,
    };

    struct spi_message msg;
    spi_message_init(&msg);
    spi_message_add_tail(&tr, &msg);

    int ret = spi_sync(spi, &msg);
    if (ret)
        return ret;

    *val = rx_buf[1];
    return 0;
}

7. 调试技巧与常见问题

SPI 驱动调试过程中,我总结了一些实用技巧和常见坑点:

  • 使用逻辑分析仪:抓取 SCLK、MOSI、MISO、CS 信号,确认时序是否符合预期,这是最直观的调试手段。
  • 检查设备树 :通过 /sys/bus/spi/devices/ 查看设备是否成功注册,确认 compatible 匹配是否正确。
  • 模式不匹配:CPOL/CPHA 配置错误会导致数据错位,务必对照数据手册确认。
  • 片选信号异常:如果使用 GPIO 模拟片选,注意确认 GPIO 的 active 电平配置。
  • 时钟频率过高 :超出从机支持的最大频率会导致通信不稳定,适当降低 spi-max-frequency。

8. 总结

Linux SPI 驱动开发的核心在于理解三层架构、掌握关键数据结构、正确配置设备树,并熟悉数据传输流程。建议初学者先从简单的字符设备驱动入手,配合逻辑分析仪逐步验证,再过渡到复杂的 DMA 传输和中断处理场景。希望这份学习笔记对你有帮助,欢迎在评论区交流讨论。

相关推荐
一直C44 分钟前
嵌入式 Linux 驱动开发学习笔记(三)设备树、GPIO子系统、中断与外设驱动实战
驱动开发·单片机·嵌入式硬件·嵌入式·arm·imx6ull
Lzg_na1 小时前
基于 inode 的文件系统详解
linux
程序猿乐锅1 小时前
【从0-1学习Python | 第四篇】Python 字符串和元组
java·开发语言·笔记·后端·python·学习
小此方1 小时前
Linux网络(二十二):深入解析IP协议:从地址寻址、头部结构到内核机制与TTL环路控制
linux·网络·tcp/ip
Ruiery1 小时前
Linux 6.6内核内存管理深度解析(四):大页(HugeTLB / THP)— 为什么一个页表项要管 2MB
linux·运维·服务器
Lsetea1 小时前
OpenSSL报certificate rejected:error 28与CA辅助信任属性排查
linux·https·ssl证书·openssl·证书链
沫璃染墨1 小时前
从零入门计算机网络系列(五):UDP套接字编程实战——从核心API到客户端与服务器通信
linux·网络·c++·vscode·网络协议·udp
li星野1 小时前
【学习记录】DAP调试器电路原理图完全详解:从STM32最小系统到SWD调试接口
stm32·嵌入式硬件·学习
Margrop1 Margrop1 小时前
别再挨个试清华、中科大、阿里源了!国家队下场的“CERNET 镜像聚合站”,凭什么让你的下载直接起飞?
linux·开源镜像·cernet