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
相关产品推荐
相关产品推荐

