You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux多从机SPI编程:如何设置片选(struct spi_ioc_transfer)

关于Linux SPI片选(SS)配置与spi_ioc_transfer的疑问解答

嘿,刚涉足嵌入式领域就开始啃Linux SPI开发,已经很棒啦!关于SS引脚和spi_ioc_transfer的这些问题,我当初入门时也纠结过,下面给你掰扯清楚:

一、Linux SPI子系统的SS引脚管理方式

Linux里SPI的片选引脚有两种主流控制方式,对应不同的配置逻辑:

1. 内核自动管理SS引脚

这是最常用的场景,大部分驱动都会这么配置:

  • 当你在设备树中给SPI从机节点指定了cs-gpios属性(比如绑定到某个GPIO引脚),或者通过sysfs接口配置了片选引脚后,内核会自动帮你处理SS的拉低/拉高动作。
  • 这时候你不需要在代码里手动操作SS引脚,只需要打开对应从机的SPI设备节点即可——比如/dev/spidev0.0对应SPI总线0、片选0,/dev/spidev0.1对应总线0、片选1,打开哪个节点就等于指定了要操作的从机片选。

关于cs_change字段的作用

这个字段是用来控制单次传输结束后是否保持SS低电平:

  • 设为0:传输完成后,内核自动将SS拉回高电平,释放当前从机。
  • 设为1:传输完成后,SS保持低电平,不会释放。这个场景适合连续给同一个从机发送多组数据,避免频繁拉低/拉高SS的开销;如果是多从机切换,这个参数要谨慎使用,不然会导致从机一直被选中。

2. 用户态手动控制SS引脚

如果你需要更灵活的片选控制(比如自定义引脚、或者内核没配置自动管理),可以把SS引脚当成普通GPIO来操作:

  • 首先要告诉内核不要自动管理片选:打开SPI设备后,通过ioctl设置SPI模式时,加上SPI_NO_CS标志位,比如:
    uint8_t mode = SPI_MODE_0 | SPI_NO_CS;
    ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
    
  • 然后用GPIO控制接口(比如sysfs的/sys/class/gpio路径,或者libgpiod库)来手动操作SS引脚:传输前拉低SS,传输完成后拉高SS。

二、初始片选的指定逻辑

初始片选的指定其实和你打开的SPI设备节点直接绑定:

  • 每个/dev/spidevX.Y节点对应唯一的SPI总线(X)和片选(Y),打开这个节点就等于告诉内核你要操作这个从机,内核会自动关联对应的SS引脚(如果是自动管理模式)。
  • 如果是手动控制模式,初始片选的指定就是你手动拉低对应的GPIO引脚,没有通过SPI设备节点绑定的逻辑。

三、简单代码示例

下面是一个内核自动管理片选的基础示例,展示如何使用spi_ioc_transfer:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <unistd.h>
#include <cstring>
#include <iostream>

int main() {
    // 打开总线0、片选0的SPI设备
    int spi_fd = open("/dev/spidev0.0", O_RDWR);
    if (spi_fd < 0) {
        std::cerr << "Failed to open SPI device" << std::endl;
        return -1;
    }

    // 配置SPI模式为MODE0,时钟极性0,相位0
    uint8_t mode = SPI_MODE_0;
    if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) < 0) {
        std::cerr << "Failed to set SPI mode" << std::endl;
        close(spi_fd);
        return -1;
    }

    // 配置波特率为1MHz
    uint32_t speed = 1000000;
    if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
        std::cerr << "Failed to set SPI speed" << std::endl;
        close(spi_fd);
        return -1;
    }

    // 准备读写数据:发送寄存器地址0x01,读取一个字节
    uint8_t tx_buf[2] = {0x01, 0x00};
    uint8_t rx_buf[2] = {0};

    struct spi_ioc_transfer xfer = {
        .tx_buf = reinterpret_cast<unsigned long>(tx_buf),
        .rx_buf = reinterpret_cast<unsigned long>(rx_buf),
        .len = sizeof(tx_buf),
        .delay_usecs = 0,
        .speed_hz = speed,
        .bits_per_word = 8,
        .cs_change = 0, // 传输完成后释放片选
    };

    // 执行SPI传输
    if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &xfer) < 0) {
        std::cerr << "SPI transfer failed" << std::endl;
        close(spi_fd);
        return -1;
    }

    std::cout << "Read data: 0x" << std::hex << static_cast<int>(rx_buf[1]) << std::endl;

    close(spi_fd);
    return 0;
}

如果是多从机场景,你只需要分别打开对应的spidevX.Y节点,各自执行传输操作即可,内核会自动切换对应的SS引脚。

刚开始接触这些细节确实容易懵,多试几次结合内核SPI的文档(比如内核源码里的Documentation/spi/spidev.rst)就能慢慢理清啦!

内容的提问来源于stack exchange,提问作者Hesham.K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 06:57:22