3. SPI数据帧格式:MSB/LSB优先、8位/16位数据帧、SPI传输的字节序与字节对齐

SPI通信看起来简单——就四根线,一主一从,时钟一拉数据就跑了。但真到项目里,数据帧格式这块儿坑不少。我见过不少工程师,硬件连好了,波形也抓出来了,就是读不到正确的传感器数据。问题出在哪儿?十有八九是数据帧格式没搞对。

说白了,SPI的数据帧格式就是三个问题:先发高位还是低位?一次传8位还是16位?多字节数据在内存里怎么排?今天咱们一个一个说清楚。

3.1 MSB优先 vs LSB优先

SPI协议本身没有规定数据位传输的顺序。但绝大多数芯片默认是MSB优先——也就是先传最高位(Bit 7),再传次高位,最后传最低位(Bit 0)。

为什么会这样?因为大部分传感器和存储芯片的数据手册里,寄存器地址、命令码都是按MSB优先定义的。你想想看,如果先传LSB,那地址0x80(二进制1000 0000)就变成了0x01,这不乱套了吗?

不过,也有例外。我记得有一次调试一个音频编解码芯片,数据手册里明确写着"LSB first for data word"。当时我没仔细看,直接用默认的MSB优先去读音频数据,结果出来的波形全是乱的。后来抓了逻辑分析仪一看,每个字节的位顺序都是反的。

关键点:SPI控制器通常支持配置MSB/LSB优先。Linux内核中通过spi_device的mode字段的SPI_LSB_FIRST标志位来控制。

代码里怎么配?看下面这个例子:

struct spi_device *spi;
// 默认是MSB优先
// 如果需要LSB优先,设置如下:
spi->mode |= SPI_LSB_FIRST;

我的建议:除非数据手册明确要求LSB优先,否则一律用MSB优先。这是行业惯例,兼容性最好。

3.2 8位数据帧 vs 16位数据帧

SPI的数据帧宽度可以是8位、16位,甚至32位。但实际项目中,8位数据帧是绝对的主流。为什么?因为大部分传感器寄存器都是8位宽,命令码、状态寄存器、数据寄存器,一个字节搞定。

但16位数据帧也有它的用武之地。比如一些高精度ADC(模数转换器),转换结果直接是16位;再比如某些加速度计,X轴、Y轴数据各占16位。用16位帧格式,一次传输就能拿到完整数据,省去了拼接的麻烦。

我在一个工业数据采集项目里就遇到过这种情况。用的是ADI的AD7768,24位ADC,但SPI支持16位帧模式。我配置成16位帧,每次读两个16位字拼成24位数据。嗯,这里要注意:24位数据用16位帧读,需要读两次,然后自己拼。

配置方法如下:

// 8位数据帧(默认)
spi->bits_per_word = 8;

// 16位数据帧
spi->bits_per_word = 16;

避坑指南:我曾经在STM32上踩过一个坑——配置了16位帧,但SPI控制器和从设备之间的时钟极性/相位不匹配,导致16位数据被当成两个8位数据接收。结果就是数据全错位了。所以改帧宽度时,一定要确认主从双方的CPOL和CPHA一致。

3.3 字节序与字节对齐

字节序(Endianness)这个问题,在SPI通信里经常被忽略。但一旦出问题,排查起来特别头疼。

SPI本身是大端传输的——先传高位字节,再传低位字节。比如你要传一个16位数据0x1234,SPI线上先看到的是0x12,然后是0x34。这跟I2C是一样的。

但问题来了:你的CPU可能是小端模式(比如ARM Cortex-M系列)。CPU内存里存0x1234,实际是低地址存0x34,高地址存0x12。如果你直接把内存里的数据扔给SPI发送寄存器,那发出去的就是0x34 0x12——顺序反了!

怎么解决?两种办法:

  • 软件转换:发送前手动调用cpu_to_be16()htons()做字节序转换。
  • 硬件自动转换:有些SPI控制器支持硬件字节序转换,配置寄存器即可。

看个实际例子:

uint16_t data = 0x1234;
uint16_t tx_data;

// 小端CPU,需要转换
tx_data = cpu_to_be16(data);

// 发送
spi_write(spi, &tx_data, 2);

再说字节对齐。SPI传输时,数据缓冲区通常要求4字节对齐。为什么?因为DMA传输需要。如果你传一个未对齐的缓冲区,DMA可能会触发异常,或者性能大幅下降。

经验之谈:我在Linux SPI驱动开发中,每次分配发送/接收缓冲区都用devm_kzalloc(),它默认返回对齐的内存。如果自己用栈上的数组,记得加__attribute__((aligned(4)))

// 正确做法:对齐的缓冲区
uint8_t tx_buf[32] __attribute__((aligned(4)));
uint8_t rx_buf[32] __attribute__((aligned(4)));

// 或者用动态分配
uint8_t *tx_buf = devm_kzalloc(dev, 32, GFP_KERNEL);

3.4 知识体系总览

下面这张图把SPI数据帧格式的核心逻辑串起来了。你看一眼就能明白:

SPI数据帧格式核心逻辑 位传输顺序 MSB优先(默认) LSB优先(特殊场景) 数据帧宽度 8位帧(主流) 16位帧(ADC等) 字节序与对齐 大端传输(SPI协议) 小端CPU需转换 4字节对齐(DMA要求) 正确配置 → 可靠通信

3.5 实际项目中的配置建议

说了这么多理论,最后给几个实战建议:

  1. 先看数据手册:每个传感器/从设备的SPI时序图里,都会标明MSB/LSB、帧宽度。照着配就行。
  2. 用逻辑分析仪验证:我每次调SPI,第一件事就是抓波形。看第一个字节是不是对的,位顺序对不对。这比猜快多了。
  3. 统一字节序:整个项目里,所有SPI通信都用大端。如果CPU是小端,在驱动层统一做转换,不要让上层应用操心。
  4. 缓冲区对齐:用DMA传输时,缓冲区必须4字节对齐。否则轻则性能下降,重则DMA报错。

一个小技巧:如果你不确定当前SPI配置是否正确,可以发一个已知的测试数据(比如0x55或0xAA),然后看回环回来的数据对不对。0x55(0101 0101)和0xAA(1010 1010)的位模式很规律,一眼就能看出MSB/LSB有没有搞反。

好了,SPI数据帧格式就聊到这儿。记住一句话:配置对了,通信就成功了一半。剩下的就是时序和中断处理了。


公众号:蓝海资料掘金营,微信deep3321