
本文详解如何使用 spidev.xfer2() 实现带前置指令与填充字节、后置响应数据的 spi 通信时序,核心在于理解“发送字节数决定接收字节数”的同步机制,并通过构造等长收发缓冲区精准定位有效响应数据。
本文详解如何使用 spidev.xfer2() 实现带前置指令与填充字节、后置响应数据的 spi 通信时序,核心在于理解“发送字节数决定接收字节数”的同步机制,并通过构造等长收发缓冲区精准定位有效响应数据。
在 Linux 下使用 Python 的 spidev 模块进行底层 SPI 通信时,xfer2() 是最常用且功能最完整的同步传输方法——它在同一 SPI 时钟周期内完成全双工收发:每输出一个 MOSI 字节,就同时采样一个 MISO 字节。关键原则是:xfer2(buf) 总是发送 len(buf) 个字节,也严格返回 len(buf) 个字节;MISO 数据并非“延迟到达”,而是与每个 MOSI 字节严格对齐采样。
回到典型场景:某 SPI 外设要求先发送 2 字节命令(txdata0, txdata1),再发送 1 字节填充(padding,通常为 0x00),随后继续发送 4 个 dummy 字节(用于提供时钟脉冲,驱动外设输出响应),最终从第 4 个时钟起在 MISO 上返回 4 字节有效数据(data0–data3)。其时序示意如下:
byte index: 0 1 2 3 4 5 6 MOSI: txdata0 txdata1 padding 0x00 0x00 0x00 0x00 MISO: ignored ignored ignored data0 data1 data2 data3
要达成此行为,必须构造一个 7 字节的发送缓冲区,因为需产生 7 个 SPI 时钟周期(即 7 次采样)才能完整捕获全部 4 字节响应。注意:前 3 字节的 MISO 值虽被采样(对应指令和填充阶段),但属于无效或无关数据,应丢弃。
✅ 正确实现如下:
立即学习“Python免费学习笔记(深入)”;
import spidev
spi = spidev.SpiDev()
spi.open(0, 0) # bus 0, device 0
spi.max_speed_hz = 1000000
txdata0, txdata1 = 0x12, 0x34
# 构造 7 字节发送缓冲区:
# [命令0, 命令1, 填充字节] + [4 个 dummy 字节用于驱动响应]
tx_buf = [txdata0, txdata1, 0x00] + [0x00] * 4 # 长度 = 7
rx_buf = spi.xfer2(tx_buf) # 发送 7 字节 → 返回 7 字节
# 有效响应位于索引 3 开始的连续 4 字节
data0, data1, data2, data3 = rx_buf[3:7]
print(f"Response: {rx_buf[3:7]}") # e.g., [0xaa, 0xbb, 0xcc, 0xdd]⚠️ 常见误区澄清:
- ❌ spi.xfer2([txdata0, txdata1], 7) —— xfer2() 不接受长度参数,此调用语法错误;
- ❌ spi.xfer2([txdata0, txdata1, 0], 4) —— 同样非法,且即使存在该接口,也无法保证时钟数与响应对齐;
- ❌ 试图用 xfer()(非阻塞变体)替代 —— xfer() 在部分内核版本中存在时序缺陷,xfer2() 才是推荐的同步可靠接口。
? 核心总结:
- 发送长度即采样长度:len(tx_buf) == len(rx_buf) 是硬约束;
- MISO 与 MOSI 严格逐字节对齐:第 i 个时钟沿既发出 tx_buf[i],也采入 rx_buf[i];
- 有效数据位置由外设协议决定:需根据器件手册明确“何时开始输出有效数据”,再反推 dummy 字节数及切片索引;
- 填充与 dummy 统一用 0x00 安全:绝大多数 SPI 外设将高阻或未定义 MOSI 视为 0x00,显式发送更可控。
掌握这一模式后,即可灵活适配各类 SPI 协议(如 ADS131E08 的多通道读取、FRAM 的状态轮询、OLED 的命令+数据混合传输等),真正实现“所发即所控,所收即所需”。









