0%

SPI通信协议分析

SPI通信协议详解

写在最前: 本文讲述了SPI通信协议的基本内容包括如下

  • SPI的基础知识
  • SPI的读写时序

本文重点参考 英文维基百科 中文维基百科 百度百科

注意: 倘若读者有足够的耐心和英文水平,强烈建议自行去英文维基百科去阅读相关知识,这篇百科远远胜过一大堆的博客(包括本文)。本文主要通过英文的维基百科的内容来讲解 SPI 通信协议。

相关链接:
SPI中文维基百科(没有翻译全)
SPI 英文维基百科

SPI 的基本内容

  首先来看SPI接口的组成,图片来自维基百科。基本的有4根线,分别是 SCKL即时钟线, MOSI主机master输出从机slave输入,即主机向从机输送数据,MISO主机输入,从机输出,即从机向主机发送数据。SS或者叫CS(分别是 slave select 和chip select)是一个东西,片选线,用来表示从机被选中,开始通信。

  在这里先解释一些东西。SPI可以接多个从机。其中SCLK MOSI MISO是所有设备共享的,但是 SS 不是共享的,每个从机都有一个SS。如下图:

   SS通常在电平被拉低时开始通信。如果在单主单从下,有些设备可以一直在低电平,保持与主机的通信,比如OLED我的OLED的CS脚直接悬空了也能保持通信。但有些设备不行,英文维基百科上有一个例子。如下:

If a single slave device is used, the SS pin may be fixed to logic low if the slave permits it. Some slaves require a falling edge of the chip select signal to initiate an action. An example is the Maxim MAX1242 ADC, which starts conversion on a high→low transition. With multiple slave devices, an independent SS signal is required from the master for each slave device.

该设备需要一个下降沿信号来表示 active 然后通信。
  然后我们再解释下 SCLK MOSI MISO。如果读者有接触过IIC协议,应该知道我们需要发送起始信号 应答信号等流程。但是SPI不一样,参考一些中文博客的说法,SPI是在进行数据交换。这个描述其实很贴切,稍后会详细解释。现在我们需要明白的是,不同于IIC读写分别有时序,SPI在每两个信号沿(即SCLK的一次完整地拉高拉低)过程中,主机向从机输送了一个bit地数据,从机也向主机输送了一个bit地数据,尽管有时候我们只想要写数据或者只想要读数据。不论是读还是写数据,时钟信号SCLK都由主机发出。

从宏观来看SPI通信的过程

  首先我们需要清楚,MOSI是主机向从机传输数据,MISO是从机向主机传输数据,而何时传输数据由SCLK地电平变化来表示,在ss被拉低后,设备开始通信,随着SCLK的一次拉高拉低,一位的数据便传输了。我们先宏观来看这个过程。图片来自百度百科:

  如果看的不是很明白,我来做一些解释。其中的脉冲其实就是SCLK的电平变化,上应该就是上升沿,下应该就是下降沿(大概?),主机sbuff就是主机准备发出去的数据,从机sbuff是从机发出的数据,sdi是MISO,sdo是MOSI。我们再仔细看看数据是如何交换的。在1上的时候,主机和从机分别移出了一位,把数据放到了sdisdo上,在1下的时候主机接受了MISO和从机接受了MOSI上的数据,并把数据作为自己的最低位(LSB)。我们再看看维基百科上如何说明这个过程,希望读者能认真看完这段英文,讲的真的很清楚。

Transmissions normally involve two shift registers of some given word-size, such as eight bits, one in the master and one in the slave; they are connected in a virtual ring topology. Data is usually shifted out with the most significant bit first. On the clock edge, both master and slave shift out a bit and output it on the transmission line to the counterpart. On the next clock edge, at each receiver the bit is sampled from the transmission line and set as a new least-significant bit of the shift register.After the register bits have been shifted out and in, the master and slave have exchanged register values. If more data needs to be exchanged, the shift registers are reloaded and the process repeats. Transmission may continue for any number of clock cycles. When complete, the master stops toggling the clock signal, and typically deselects the slave.
传输通常会使用到给定字长的两个移位寄存器,一个在主设备中,一个在从设备中; 它们以虚拟环形拓朴连接。 数据通常先移出最大的位。 在时钟边沿,主机和从机均移出一位,然后在传输线上输出给对方。 在下一个时钟沿,每个接收器都从传输线接受该位,并设置为移位寄存器的新的最低有效位。 在完成这样一个移出-移入的周期后,主机和从机就交换寄存器中的一位。 如果需要更多的数据交换,则需要重新加载移位寄存器并重复该过程。 传输可能会持续任意数量的时钟周期。 完成后,主设备会停止切换时钟信号,并通常会取消选择从设备。

再开始了解具体的时序

  希望读者能够通过上文能够了解SPI是如何工作的。但是仅仅知道这些还不足以写出对应的程序来模拟SPI。我们接下来需要了解更多的细节。

  我们从百度百科摘下来的图片可以看出在上升沿移出了数据,然后在下降沿接受了数据。但是其实并非一定要在上升沿移出数据并放到数据线上,下降沿接受数据到到寄存器里。
  我们首先来了解时钟极性时钟相位(Clock polarity and phase)。时钟极性通常写作CPOL(clock polarity),时钟相位通常写作CPHA(clock phase)。这两个参数有时可以自行配置,有时则是设备固定,这两个参数决定了我们的SPI到底如何通信。

注意:文中关键概念: 前沿(leading edge)、 后沿(trailing edge) 、上升沿(rising edge)、 下降沿(falling edge)、 捕获(captures) 、采样(sample)、 锁存(latch) 、改变(change)

  时钟极性CPOL表示在空闲时,SCLK是高电平还是低电平,如果SCLK配置为0,那么空闲时就是低电平,配置为1,那么空闲时就是高电平。最方便的记忆方法就是 0 就是低电平 ,1 就是高电平。

  时钟相位CPHA表示,在何时 捕获(capture)改变 数据。其中捕获有很多种说法,捕获(captures)采样(sample)锁存(latch)说的其实是一回事,在本文我们采用捕获这一说法。时钟极性很好解释,但是时钟相位就不那么容易解释了。首先需要知道 前沿后沿,现在我们结合起时钟极性CPOL来了解这个问题,然后再去了解时钟相位CPHA

  按照SPI协议通信的要求,首先是SS(CS)被拉低,然后开始通信。那么根据时钟极性,我们可以知道SS被拉低后,SCLK有两种可能,若CPOL=0,那么SCLK就是从0开始,若CPOL=1,那么就是从1开始。当主机改变SCLK时,数据交换就开始了。这里我们称 第一次发生的时钟改变叫做前沿(leading edge),随后在发生的时钟改变叫做后沿(trailing)(我并不是专业相关,只是根据我的理解给的定义,如有更专业更通俗的解释可以左侧邮箱联系我),一个前沿再加一个后沿完成了一个时钟周期。现在我们通过一些思考来巩固这个内容。假如 CPOL=0,那么前沿就是一个上升沿后沿就是一个下降沿。假如CPOL=1,那么前沿就是一个下降沿后沿就是一个上升沿。

  看看维基百科的表述,仅摘录CPOL=0:

CPOL=0 is a clock which idles at 0, and each cycle consists of a pulse of 1. That is, the leading edge is a rising edge, and the trailing edge is a falling edge.

  有了这些知识铺垫,终于可以解释时钟相位CPHA了。可能有些难以理解,请仔细斟酌意思,以下是我自己总结的,稍后也会给出维基百科的定义,强烈推荐阅读。

  如果CPHA=0,那么前沿发生采样后沿发生数据的改变

  如果CPHA=1,那么后沿发生数据的改变前沿发生数据的采样。

  所谓数据的采样,就是在之前宏观意义上的把数据接受到寄存器里,所谓数据的改变(不同文章有不同的叫法,有叫移出 输送的,但在笔者看来和改变是一个意思,笔者对移出输送的含义其实并不是很理解,在维基百科中,使用的是改变(change)) ,就是把数据从寄存器移出,并放置在MOSIMISO上。

现在我们来看维基百科对此的定义,下面这段话很重要,希望能耐心看完。

For CPHA=0, the “out” side changes the data on the trailing edge of the preceding clock cycle, while the “in” side captures the data on (or shortly after) the leading edge of the clock cycle. The out side holds the data valid until the trailing edge of the current clock cycle. For the first cycle, the first bit must be on the MOSI line before the leading clock edge.

An alternative way of considering it is to say that a CPHA=0 cycle consists of a half cycle with the clock idle, followed by a half cycle with the clock asserted.

For CPHA=1, the “out” side changes the data on the leading edge of the current clock cycle, while the “in” side captures the data on (or shortly after) the trailing edge of the clock cycle. The out side holds the data valid until the leading edge of the following clock cycle. For the last cycle, the slave holds the MISO line valid until slave select is deasserted.

An alternative way of considering it is to say that a CPHA=1 cycle consists of a half cycle with the clock asserted, followed by a half cycle with the clock idle.

看完这段话后我们需要一张图片,图片来自维基百科

  我们挑选一个 CPOL=1,CPHA=1的情况来举例说明。
对于CPOL=1,那么时钟前沿就是一个下降沿,后沿是一个上升沿。那么从维基百科的说明里看出,”out” side(对于MOSI来说out是主机,对于MISO来说,out是从机)在当前时钟周期的leading edge(结合CPOL,前沿就是下降沿)会改变数据,也就是把数据输送到 MOSI(主机输送)和MISO(从机输送)两根数据线上。 “in” side 捕获数据发生在后沿,结合CPOL,也就是上升沿,也有可能在上升沿之后(shortly after),以后你查看你设备SPI通信的要求时,你会看到一系列参数,比如信号的建立事件等,因为不是马上通信完成的。文中也指出”the out side holds the data valid until the leading edge of the following clock cycle.” 也就是输送的信号需要保持稳定,直到下一个 leading edge(结合CPOL 就是下降沿)到来才能发生改变。那么通俗一点地解释,数据在上升沿后要保持稳定,直到下一个下降沿到来,那么说明我们主机和从机读取数据要在上升沿后,下降沿前。数据在下降沿发生改变(change),也就是我们在下降沿后设置好主机MOSI(不用考虑从机,从机发送数据是从机的事情,从机会自己在下降沿改变数据),然后到下一个上升沿,从机就会采样数据,同时你也可以读取从机发送过来的数据。

  用软件模拟SPI的流程图就出来了:

  手动拉低SCLK:主机去改变 MOSI 的电平表示即将发送的数据,从机自己设置MISO的电平表示发送给主机的数据。

  手动拉高SCLK:从机会采样即接收在MOSI的数据,如果主机需要读取,可以读取MISO上的数据

  如此循环。

如果感觉自己掌握了,那就再看一个CPOL=0,CPHA=0的例子,我们稍加思考,发现和CPOL=1,CPHA=1相像,都是在上升沿捕获数据,下降沿改变数据。事实上,这两种情况时最常见的情况,即上升沿捕获,下降沿改变。
百科可能有一些绕口,” the “out” side changes the data on the trailing edge of the preceding clock cycle “,因为CPOL=0时,一个时钟就是上升沿马上就要捕获数据,第二个再是下降沿,因此用了”preceding clock cycle”这个说法,自己体会下。
以下是维基百科给出的代码,风格奇特,但是思想在里面了,代码CPOL=0 CPHA=0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
* Simultaneously transmit and receive a byte on the SPI.
*
* Polarity and phase are assumed to be both 0, i.e.:
* - input data is captured on rising edge of SCLK.
* - output data is propagated on falling edge of SCLK.
*
* Returns the received byte.
*/
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
uint8_t byte_in = 0;
uint8_t bit;

for (bit = 0x80; bit; bit >>= 1) {
/* Shift-out a bit to the MOSI line */
write_MOSI((byte_out & bit) ? HIGH : LOW);

/* Delay for at least the peer's setup time */
delay(SPI_SCLK_LOW_TIME);

/* Pull the clock line high */
write_SCLK(HIGH);

/* Shift-in a bit from the MISO line */
if (read_MISO() == HIGH)
byte_in |= bit;

/* Delay for at least the peer's hold time */
delay(SPI_SCLK_HIGH_TIME);

/* Pull the clock line low */
write_SCLK(LOW);
}

return byte_in;
}

  这段代码我们看出来了其实数据并不是单向的读写,更像是一种交换,主机在输出,从机也在输出,有读也有写。
注意: 有读也有写意味着如果主机试图向从机读取数据(如向温度传感器),那么主机也必须发送数据。用代码举个例子:

1
2
// Send a dummy byte to receive the contents of the WHOAMI register
int whoami = SPI_transfer_byte(0x00);

SPI_transfer_byte函数就是上文代码里的函数。我们如果想要读取WHO_ AM _ I寄存器,那么也需要write一个字节的数据,称之为 dummy byte

SPI如何操纵设备

  讲解完了SPI的时序,那么就要思考实际应用中到底是如何使用的,一个设备里有好多的寄存器,如何才能对它进行读写呢?
   这个问题取决于你的设备,需要查阅你的芯片手册。我这里举个例子:

  这是一款能用SPI通信的陀螺仪,里面对SPI的介绍中,你可以看到:
  该设备在上升沿采样,下降沿输送,这里的说法是latch,在文中提到过,是一样的。没有看出是CPOL是0还是1,因为我没有截图完整,前面有一个时序图,可以看出CPOL其实是 1。
  该设备接收最高位,意味着我们需要先发送最高位(MSB),再发送(LSB)。部分读者可能会疑惑本文直接假定了先发送最高位,后最低位。事实上这是最常见的,也有例外(维基百科没有距离,但是说了通常先发送最高位)。而且带有硬件SPI的单片机通常都能配置寄存器选择先发送最高位还是最低位。
  操作方法是先发送 7位寄存器地址,其中最高位是读写位,0表示读,1表示写。

以上只是以我的设备举例,具体方法请查阅你的datasheet