ESC
输入关键词搜索文章标题和内容

嵌入式Linux SPI子系统与驱动开发:从协议到实战

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

嵌入式Linux SPI子系统与驱动开发:从协议到实战

SPI是嵌入式系统最常用的外设总线之一,搞懂它的子系统架构和驱动开发流程,是从"调库"到"写驱动"的必经之路。本文从SPI协议基础讲起,拆解内核数据结构、传输API、设备树配置,最后给一个完整的SPI Flash驱动实战。

一、原理简析

SPI协议基础

SPI(Serial Peripheral Interface)是一种同步四线串行总线,广泛用于连接MCU与传感器、存储器、外设。全双工通信——每个时钟周期同时移出和移入一位数据。

四根信号线:

信号 方向 说明
SCLK 主→从 时钟线,由控制器产生
MOSI 主→从 主出从入数据线
MISO 从→主 主入从出数据线
CS/nCS 主→从 片选线,通常低电平有效

SPI没有统一的协议标准,不同厂商芯片的命令格式各异,这是与I2C的重要区别。

SPI四种模式

SPI通过CPOL(时钟极性)和CPHA(时钟相位)两个参数组合出四种工作模式:

模式 CPOL CPHA 空闲时钟 采样边沿
Mode 0 0 0 低电平 上升沿采样
Mode 1 0 1 低电平 下降沿采样
Mode 2 1 0 高电平 下降沿采样
Mode 3 1 1 高电平 上升沿采样

实际开发中Mode 0和Mode 3最常用,选哪种取决于从设备的数据手册要求。

SPI子系统架构

Linux内核SPI子系统采用三层架构:

flowchart TB A["用户空间<br/>/dev/spidevX.Y"] --> B["SPI核心层<br/>drivers/spi/spi.c"] B --> C["SPI控制器驱动<br/>spi_controller"] B --> D["SPI设备驱动<br/>spi_driver"] C --> E["SPI硬件控制器"] D --> F["SPI从设备"] C -.->|注册spi_controller| B D -.->|注册spi_driver| B style A fill:#E8F5E9 style B fill:#E3F2FD style C fill:#FFF8E1 style D fill:#FFF8E1 style E fill:#F3E5F5 style F fill:#F3E5F5
  • SPI核心层(SPI Core):管理总线、设备和驱动的注册/匹配,提供数据传输通用接口
  • SPI控制器驱动(Controller Driver):抽象硬件控制器,实现spi_controller的回调函数
  • SPI设备驱动(Protocol Driver):针对具体从设备(如Flash、传感器)的驱动,通过核心层API与设备通信

二、核心数据结构

spi_controller(原spi_master)

spi_controller是SPI控制器在内核中的抽象。5.15之前的内核叫spi_master,后因需支持Controller/Target双角色而重命名。6.8+内核中spi_master兼容层已移除,新代码统一用spi_controller。

struct spi_controller {
    struct device   dev;
    s16             bus_num;        // 总线编号
    u16             num_chipselect; // 片选数量
    u32             min_speed_hz;   // 最低时钟频率
    u32             max_speed_hz;   // 最高时钟频率

    // 关键回调
    int  (*setup)(struct spi_device *spi);
    int  (*transfer_one)(struct spi_controller *ctlr,
                         struct spi_device *spi,
                         struct spi_transfer *xfer);
    void (*set_cs)(struct spi_device *spi, bool enable);

    // 消息队列相关
    struct kthread_worker  *kworker;
    struct kthread_work    pump_messages;
    struct list_head       queue;
    struct spi_message     *cur_msg;
};

回调说明:

回调 粒度 说明
transfer_one 单个spi_transfer 只需处理单次传输,核心层负责消息调度
transfer_one_message 整个spi_message 需自行处理消息逻辑、CS控制
transfer 整个spi_message 旧接口,驱动自行管理队列,不推荐

优先实现transfer_one,核心层会自动将其包装为spi_transfer_one_message,处理CS、延时、消息状态等逻辑。

spi_device

spi_device代表总线上的一个从设备,由设备树或spi_board_info创建:

struct spi_device {
    struct device           dev;
    struct spi_controller   *controller;
    u32     max_speed_hz;   // 最大时钟频率
    u8      chip_select[SPI_CS_CNT_MAX]; // 片选号
    u8      bits_per_word;  // 每字位数,通常8
    u32     mode;           // SPI模式(CPOL/CPHA等)
    int     irq;            // 中断号
    struct gpio_desc *cs_gpiod[SPI_CS_CNT_MAX]; // CS GPIO描述符
};

mode字段的常用标志位:

标志 值 说明
SPI_CPHA 0x01 时钟相位1
SPI_CPOL 0x02 时钟极性1
SPI_CS_HIGH 0x04 CS高电平有效
SPI_LSB_FIRST 0x08 LSB先行
SPI_3WIRE 0x10 三线模式
SPI_NO_CS 0x40 无片选信号

spi_driver

spi_driver是设备驱动的核心,通过device_id表与spi_device匹配:

struct spi_driver {
    const struct spi_device_id *id_table;
    const struct of_device_id  *of_match_table;
    int  (*probe)(struct spi_device *spi);
    void (*remove)(struct spi_device *spi);
    struct device_driver driver;
};

注册方式推荐使用module_spi_driver()宏,自动处理init/exit:

module_spi_driver(my_spi_driver);

spi_transfer与spi_message

SPI传输的基本单位:消息由多个传输段组成。

struct spi_transfer {
    const void  *tx_buf;    // 发送缓冲区
    void        *rx_buf;    // 接收缓冲区
    unsigned    len;        // 传输字节数
    u32         speed_hz;   // 本次传输时钟频率(0则用设备默认)
    u8          bits_per_word; // 本次传输字长(0则用设备默认)
    unsigned    cs_change:1;   // 传输后是否改变CS状态
    struct spi_delay delay;    // 传输后延时(新API)
    struct spi_delay word_delay; // 字间延时
};

struct spi_message {
    struct list_head    transfers;   // spi_transfer链表
    struct spi_device   *spi;        // 目标设备
    void (*complete)(void *context); // 异步完成回调
    void        *context;            // 回调上下文
    int         status;              // 传输结果
};

delay_usecs字段已在5.15+内核中废弃,改用struct spi_delay delay,支持微秒、纳秒和时钟周期三种延时单位。

spi_delay结构定义:

struct spi_delay {
#define SPI_DELAY_UNIT_USECS  0   // 微秒
#define SPI_DELAY_UNIT_NSECS  1   // 纳秒
#define SPI_DELAY_UNIT_SCK    2   // 时钟周期
    u16 value;
    u8  unit;
};

三、SPI传输API

简易读写API

内核提供了三个便捷函数,适合简单场景:

// 只写
int spi_write(struct spi_device *spi, const void *buf, size_t len);

// 只读
int spi_read(struct spi_device *spi, void *buf, size_t len);

// 先写后读(常见于发送命令+读取数据)
int spi_write_then_read(struct spi_device *spi,
                        const void *txbuf, unsigned n_tx,
                        void *rxbuf, unsigned n_rx);

spi_write_then_read内部会构建两个spi_transfer并合并为一个spi_message,CS在整个过程中保持有效。

来自 linuxros.cn · linuxROS

同步传输:spi_sync

int spi_sync(struct spi_device *spi, struct spi_message *message);
  • 上下文限制:只能在进程上下文调用,因为会睡眠等待传输完成
  • 返回值:0成功,负数为错误码
  • 特点:调用后阻塞直到传输完成

异步传输:spi_async

int spi_async(struct spi_device *spi, struct spi_message *message);
  • 上下文:可在任意上下文调用(包括中断上下文)
  • 前提:必须提前设置message->complete回调函数
  • 返回值:0表示已提交队列,负数表示提交失败
  • 注意:返回0不代表传输完成,完成状态通过回调通知

消息构建流程

手动构建消息的完整流程:

struct spi_message msg;
struct spi_transfer xfer[2];

// 1. 初始化消息
spi_message_init(&msg);

// 2. 初始化传输段
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = cmd_buf;
xfer[0].len = cmd_len;
xfer[1].rx_buf = data_buf;
xfer[1].len = data_len;

// 3. 将传输段添加到消息
spi_message_add_tail(&xfer[0], &msg);
spi_message_add_tail(&xfer[1], &msg);

// 4. 提交传输
int ret = spi_sync(spi, &msg);
flowchart TB A["spi_message_init()"] --> B["设置spi_transfer字段"] B --> C["spi_message_add_tail()"] C --> D{还有更多transfer?} D -->|"是"| B D -->|"否"| E{"选择提交方式"} E -->|"进程上下文"| F["spi_sync()"] E -->|"中断/任意上下文"| G["spi_async()"] F --> H["传输完成,检查status"] G --> I["回调complete()通知完成"] style A fill:#E8F5E9 style F fill:#E3F2FD style G fill:#E3F2FD style H fill:#E8F5E9 style I fill:#E8F5E9 style D fill:#FFF8E1 style E fill:#FFF8E1

四、设备树配置

SPI设备通过设备树声明,挂载在SPI控制器节点下:

&spi0 {
    status = "okay";

    /* 使用GPIO作为片选 */
    cs-gpios = <&gpio0 17 GPIO_ACTIVE_LOW>,
               <&gpio0 18 GPIO_ACTIVE_LOW>;

    /* SPI Flash设备 */
    spiflash: mx25l25635f@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;                    /* 片选号0 */
        spi-max-frequency = <50000000>; /* 最大50MHz */
        spi-cpol;                     /* CPOL=1 */
        spi-cpha;                     /* CPHA=1 → Mode 3 */
        spi-cs-high;                  /* CS高电平有效(可选) */
    };

    /* SPI传感器设备 */
    sensor@1 {
        compatible = "vendor,sensor-xyz";
        reg = <1>;                    /* 片选号1 */
        spi-max-frequency = <10000000>; /* 最大10MHz */
        /* 默认Mode 0,无需额外属性 */
    };
};

设备树属性速查:

属性 说明
reg 片选编号,从0开始
spi-max-frequency 设备支持的最大时钟频率(Hz)
spi-cpol 设置CPOL=1
spi-cpha 设置CPHA=1
spi-cs-high CS高电平有效
spi-3wire 三线模式(MOSI/MISO合并)
spi-lsb-first LSB先行
cs-gpios 控制器节点的GPIO片选列表

当硬件片选不够用或需要更灵活的CS控制时,通过cs-gpios属性指定GPIO作为片选。内核会自动将GPIO映射到对应的chip_select编号。

五、完整驱动示例:SPI Flash驱动

以下是一个SPI NOR Flash驱动的核心实现,包含RDID(读ID)、READ(读数据)、PAGE_PROGRAM(页编程)、SECTOR_ERASE(扇区擦除)四个操作:

#include <linux/module.h>
#include <linux/spi/spi.h>

#define CMD_RDID         0x9F
#define CMD_READ         0x03
#define CMD_PAGE_PROGRAM 0x02
#define CMD_SECTOR_ERASE 0x20
#define CMD_WREN         0x06

struct my_spi_flash {
    struct spi_device *spi;
    u8 manufacturer_id;
    u8 memory_type;
    u8 capacity;
};

/* 读设备ID */
static int spi_flash_read_id(struct my_spi_flash *flash)
{
    int ret;
    u8 cmd = CMD_RDID;
    u8 id[3];

    ret = spi_write_then_read(flash->spi, &cmd, 1, id, 3);
    if (ret)
        return ret;

    flash->manufacturer_id = id[0];
    flash->memory_type = id[1];
    flash->capacity = id[2];

    dev_info(&flash->spi->dev, "ID: %02x %02x %02x\n",
             id[0], id[1], id[2]);
    return 0;
}

/* 读数据 */
static int spi_flash_read(struct my_spi_flash *flash,
                          u32 addr, u8 *buf, size_t len)
{
    struct spi_message msg;
    struct spi_transfer xfer[2];
    u8 cmd[4];

    cmd[0] = CMD_READ;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    spi_message_init(&msg);
    memset(xfer, 0, sizeof(xfer));

    xfer[0].tx_buf = cmd;
    xfer[0].len = 4;
    spi_message_add_tail(&xfer[0], &msg);

    xfer[1].rx_buf = buf;
    xfer[1].len = len;
    spi_message_add_tail(&xfer[1], &msg);

    return spi_sync(flash->spi, &msg);
}

/* 写使能 */
static int spi_flash_write_enable(struct my_spi_flash *flash)
{
    u8 cmd = CMD_WREN;
    return spi_write(flash->spi, &cmd, 1);
}

/* 页编程(最多256字节) */
static int spi_flash_page_program(struct my_spi_flash *flash,
                                  u32 addr, const u8 *buf, size_t len)
{
    struct spi_message msg;
    struct spi_transfer xfer[2];
    u8 cmd[4];
    int ret;

    ret = spi_flash_write_enable(flash);
    if (ret)
        return ret;

    cmd[0] = CMD_PAGE_PROGRAM;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    spi_message_init(&msg);
    memset(xfer, 0, sizeof(xfer));

    xfer[0].tx_buf = cmd;
    xfer[0].len = 4;
    spi_message_add_tail(&xfer[0], &msg);

    xfer[1].tx_buf = buf;
    xfer[1].len = len;
    spi_message_add_tail(&xfer[1], &msg);

    return spi_sync(flash->spi, &msg);
}

/* 扇区擦除(4KB) */
static int spi_flash_sector_erase(struct my_spi_flash *flash, u32 addr)
{
    u8 cmd[4];
    int ret;

    ret = spi_flash_write_enable(flash);
    if (ret)
        return ret;

    cmd[0] = CMD_SECTOR_ERASE;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    return spi_write(flash->spi, cmd, 4);
}

static int my_spi_flash_probe(struct spi_device *spi)
{
    struct my_spi_flash *flash;

    flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
    if (!flash)
        return -ENOMEM;

    flash->spi = spi;
    spi_set_drvdata(spi, flash);

    /* 配置SPI模式 */
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 50000000;
    spi->bits_per_word = 8;
    spi_setup(spi);

    /* 读ID验证通信 */
    return spi_flash_read_id(flash);
}

static void my_spi_flash_remove(struct spi_device *spi)
{
    dev_info(&spi->dev, "removed\n");
}

static const struct of_device_id my_spi_flash_of_match[] = {
    { .compatible = "my-vendor,spi-flash" },
    { }
};
MODULE_DEVICE_TABLE(of, my_spi_flash_of_match);

static struct spi_driver my_spi_flash_driver = {
    .driver = {
        .name = "my-spi-flash",
        .of_match_table = my_spi_flash_of_match,
    },
    .probe  = my_spi_flash_probe,
    .remove = my_spi_flash_remove,
};
module_spi_driver(my_spi_flash_driver);

MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("SPI NOR Flash Driver Example");
MODULE_LICENSE("GPL");

SPI主机控制器驱动

如果需要编写控制器驱动(通常SoC厂商已提供),核心流程如下:

static int my_spi_transfer_one(struct spi_controller *ctlr,
                               struct spi_device *spi,
                               struct spi_transfer *xfer)
{
    /* 配置时钟频率和模式 */
    my_spi_set_clk(spi->max_speed_hz);
    my_spi_set_mode(spi->mode);

    /* 执行全双工传输 */
    if (xfer->tx_buf && xfer->rx_buf) {
        /* 全双工 */
    } else if (xfer->tx_buf) {
        /* 只写 */
    } else if (xfer->rx_buf) {
        /* 只读,发送dummy字节 */
    }

    /* 返回0表示成功,负数为错误码 */
    return 0;
}

static int my_spi_probe(struct platform_device *pdev)
{
    struct spi_controller *ctlr;

    ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(struct my_spi_priv));
    if (!ctlr)
        return -ENOMEM;

    ctlr->transfer_one = my_spi_transfer_one;
    ctlr->bus_num = pdev->id;
    ctlr->num_chipselect = 2;
    ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;

    return devm_spi_register_controller(&pdev->dev, ctlr);
}

六、对比表格

SPI vs I2C

特性 SPI I2C
信号线 4根(SCLK/MOSI/MISO/CS) 2根(SCL/SDA)
通信方式 全双工 半双工
速度 高(可达几十MHz) 低(标准100K/快速400K)
寻址方式 硬件片选(CS) 软件地址(7/10位)
协议标准化 无统一协议 有标准协议
多从设备 每设备需一根CS线 共享总线,地址区分
适用场景 高速数据传输 低速传感器/配置

同步vs异步传输

特性 spi_sync spi_async
调用上下文 仅进程上下文 任意上下文
阻塞行为 阻塞等待完成 立即返回
完成通知 返回值 complete回调
使用复杂度 简单 需管理回调
典型场景 常规读写操作 中断中传输、DMA传输

七、核心流程图

SPI子系统架构

flowchart TB subgraph 用户空间 A["/dev/spidevX.Y<br/>ioctl读写"] end subgraph 内核空间 B["SPI Core<br/>drivers/spi/spi.c"] C["spi_controller<br/>控制器驱动"] D["spi_driver<br/>设备驱动"] E["spi_message<br/>spi_transfer"] end subgraph 硬件层 F["SPI控制器硬件"] G["SPI从设备"] end A -->|sysfs/ioctl| B D -->|spi_sync/spi_async| B B -->|transfer_one| C C -->|寄存器操作| F F -->|SCLK/MOSI/MISO/CS| G E --> B style A fill:#E8F5E9 style B fill:#E3F2FD style C fill:#FFF8E1 style D fill:#FFF8E1 style E fill:#F3E5F5 style F fill:#FFEBEE style G fill:#FFEBEE

SPI传输完整流程

flowchart TB A(["驱动调用spi_sync/spi_async"]) --> B["构建spi_message<br/>+ spi_transfer"] B --> C["SPI Core将消息<br/>加入控制器队列"] C --> D["唤醒kworker线程"] D --> E["调用transfer_one_message"] E --> F["遍历message中的<br/>每个transfer"] F --> G["调用控制器transfer_one"] G --> H["硬件执行传输"] H --> I{"传输完成?"} I -->|"否"| H I -->|"是"| J{"还有下一个<br/>transfer?"} J -->|"是"| F J -->|"否"| K["spi_finalize_current_transfer"] K --> L{"同步or异步?"} L -->|"spi_sync"| M["唤醒等待队列<br/>返回结果"] L -->|"spi_async"| N["调用complete回调<br/>通知完成"] style A fill:#E8F5E9 style B fill:#E3F2FD style C fill:#E3F2FD style D fill:#E3F2FD style E fill:#FFF8E1 style F fill:#FFF8E1 style G fill:#FFF8E1 style H fill:#F3E5F5 style I fill:#FFF8E1 style J fill:#FFF8E1 style K fill:#E3F2FD style L fill:#FFF8E1 style M fill:#E8F5E9 style N fill:#E8F5E9

八、常见问题解决

Q1:spi_sync调用返回-EINVAL

检查spi_setup()是否已调用,以及spi_transfer的tx_buf/rx_buf是否至少设置了一个。如果只读不写,tx_buf可以设为NULL(控制器会发送dummy字节),反之亦然。

Q2:spi_async的complete回调没有被调用

确认message->complete和message->context在提交前已设置。同时检查控制器驱动的transfer_one是否正确调用了spi_finalize_current_transfer()。

Q3:设备树配置了SPI设备但probe没有被调用

依次排查:①compatible字符串是否与驱动of_match_table一致;②控制器节点status是否为"okay";③reg指定的片选号是否在num_chipselect范围内;④用ls /sys/bus/spi/devices/确认设备是否被枚举。

Q4:SPI通信数据错位或乱码

最常见的原因是SPI模式不匹配。用示波器确认从设备要求的CPOL/CPHA,确保设备树或spi_setup()中设置的模式与器件手册一致。另外检查bits_per_word和字节序(MSB/LSB)。

Q5:CS信号行为异常

如果使用GPIO片选,确认设备树中cs-gpios属性正确,且GPIO已被控制器驱动申请。如果CS在多transfer消息中间不应改变,确保cs_change字段为0(默认值)。

Q6:新内核编译报错"spi_master未定义"

6.8+内核已移除spi_master兼容宏,统一使用spi_controller。将代码中所有spi_master替换为spi_controller,spi_alloc_master替换为spi_alloc_master(该函数名暂未改),devm_spi_register_master替换为devm_spi_register_controller。

九、总结

SPI子系统是嵌入式Linux驱动开发的必修课。本文从协议原理到内核数据结构,从传输API到设备树配置,再到完整的Flash驱动实战,走完了SPI驱动开发的整条链路。要点回顾:

  • 协议层:CPOL/CPHA四种模式,Mode 0和Mode 3最常用
  • 架构层:三层架构(核心层/控制器驱动/设备驱动),驱动开发者只需关注设备驱动
  • 数据结构:spi_controller/spi_device/spi_driver/spi_transfer/spi_message五个关键结构
  • 传输层:spi_sync用于进程上下文,spi_async用于中断上下文;delay_usecs已废弃,改用spi_delay
  • 实战层:module_spi_driver()宏简化注册,spi_write_then_read()处理命令+数据场景

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页