跳转至

SPI总线协议

概要

本片教程,我们详细的为大家讲解SPI总线协议。

什么是SPI

SPI是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线。SPI协议主要用于短距离的通信系统中,特别是嵌入式系统,很多芯片的外围设备,比如LED显示驱动器、I/O接口芯片、UART收发器等都广泛的采用SPI总线协议。

通信原理

SPI的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多
个从设备。在英文中,通常把主设备称作为 Master, 从设备称作为 Slave.

物理接线

SPI理论上需要4根线才能进行双向数据传输,3根线可以进行单向传输:
SPI理论上的4根接线分别是以下四种:

功能编号 缩写含义 中文含义
SDO 或者叫 MOSI Master Output Slave Input 主设备数据输出,从设备数据输入
SDI 或者叫 MISO Master Input Slave Output 主设备数据输入,从设备数据输出
SCLK serial clock 时钟信号, 由主设备产生
CS 或者叫SS chip select 片选信号, 从设备使能信号,由主设备控制。

为了让大家深刻理解这种主从的模式,我们1Z实验室做出了下图方便大家理解。

img

PS: 把上面这两张图 重新做一下吧

起始、停止信号

如上图,红色编号1和6即为起始和停止信号的发生区域。

CS片选信号(图中的NSS)电平由高变低,则产生起始信号;

CS片选信号电平由低变高,则产生停止信号。

从机检测到自己的CS片选信号线电平被置低,则开始与主机进行通讯;

反之,检测到NSS电平被拉高,则停止通讯。

数据有效性

MOSI和MISO线在SCK的每个时钟周期传输一位数据,开发者可以自行设置MSB或LSB先行,不过需要保证两个通讯设备都使用同样的协定。

从以下的时序图可以看出,在SCK时钟周期的上升沿和下降沿时进行触发和采样。

这里的触发和采样其实是两个特殊的时间节点,分别对应了SCK时钟周期的上升沿和下降沿。

SPI有四种通讯模式,在SCK上升沿触发,下降沿采样只是其中一种模式。四种模式的主要区别便是总线空闲时SCK的状态及数据采样时刻。这涉及到“时钟极性CPOL”和“时钟相位CPHA”,由CPOL和CPHA的组合而产生了四种的通讯模式。

  • CPOL:即在没有数据传输时,时钟的空闲状态的电平。上面的两幅图示中,无数据传输时的时钟空闲状态为低电平。

  • CPHA:即数据的采样时刻,可以是SCK的上升沿,也可以是SCK的下降沿。

如果我们将CPOL和CPHA的两种状态分别用0,1表示,因此由这两种方式排列组合,便可以产生四种模式的SPI:

SPI模式 CPOL 空闲时SCK时钟 CPHA 采样时刻
0 0 低电平 0 SCK下降沿
1 0 低电平 1 SCK上升沿
2 1 高电平 0 SCK下降沿
3 1 高电平 1 SCK上升沿

配合下图,你可以仔细揣摩一番:

很重要的一点是,主机和从机需要工作在相同的模式下才能正常通讯

SPI优点

  • 支持全双工通信,发送数据和接收数据可以同时进行。

  • 通信简单

  • 数据传输速率快

SPI缺点

  • 接线繁杂,需要至少四根接线

  • 在多个从机的情况下,每个从机都需要接入一根CS片选信号线,这是十分的浪费芯片的IO资源

硬件资源

NodeMCU-32S拥有两组硬件SPI总线资源, 分别是HSPI和VSPI,所对应的引脚如下图:

ValueError: SPI ID must be either HSPI(1) or VSPI(2)

为方便大家接线,贴出下表:

SPI组号 MOSI MISO CS LCK
VSPI GPIO 23 GPIO 19 GPIO 5 GPIO 18
HSPI GPIO 13 GPIO 12 GPIO 15 GPIO14

除此两组SPI硬件资源外,其余的GPIO理论上也可以配置成SPI总线的输入输出管脚,只要满足该管脚既能够作为输入也能够作为输出。因此,在MicroPython中,我们拥有两种模式的SPI总线,即:

  • 硬件SPI

  • 软件SPI (GPIO模拟)

SPI API文档

硬件SPI构造

上文我们为大家罗列出了两组硬件SPI,构造他们很简单:

以下是HSPI的构造:

>>> from machine import SPI
>>> hspi = SPI(1)
>>> hspi
SPI(id=1, baudrate=500000, polarity=0, phase=0, bits=8, firstbit=0, sck=-1, mosi=-1, miso=-1)
>>> 

以下是VSPI的构造:

>>> from machine import SPI
>>> vspi = SPI(2)
>>> vspi
SPI(id=1, baudrate=500000, polarity=0, phase=0, bits=8, firstbit=0, sck=-1, mosi=-1, miso=-1)
>>> 

注意

你可能会发现无法同时构造两个硬件SPI,即使你认为这么做并不该出错。

然而MicroPython并不允许你这样做,hspi和vspi只能存在一个,不能一起使用。

软件SPI构造

软件SPI构造较为硬件要传入的参数比较繁多,有两种构造方法:使用类构造或使用init函数构造

类构造

SPI(baudrate, polarity, phase, bits, firtbit, sck, mosi, miso)

  • baudrate:SCK时钟频率 范围 0 < baudrate ≤ 0x0FFFFFFF (十进制:0 < baudrate ≤ 2147483647)

  • polarity:极性

  • 0 时钟空闲时候的电平是低电平,所以当SCLK有效的时候,就是高电平

  • 1 时钟空闲时候的电平是高电平,所以当SCLK有效的时候,就是低电平

  • phase:相位

  • 0 在第一时钟沿采样数据

  • 1 在第二时钟沿采样数据

  • bits:传输数据位数

  • firtbit:数据传输的第一位(高位或低位)

  • sck 时钟信号引脚

  • mosi 主设备输出,从设备输入引脚

  • miso 主设备输入,从设备输出引脚

​ Pin(0)、Pin(2)、Pin(4)、Pin(5)、Pin(9)、Pin(16~19)、Pin(21~23)、Pin(25~27)

示例:

from machine import SPI, Pin

spi = SPI(baudrate=100000, polarity=1, phase=0, sck=Pin(17), mosi=Pin(27), miso=Pin(18))

使用init函数构造

SPI.init(baudrate, polarity, phase, sck, mosi, miso)

函数说明:初始化SPI总线
参数含义同上文类构造一致

示例:

spi = SPI.init(baudrate=100000, polarity=1, phase=0, sck=Pin(17), mosi=Pin(27), miso=Pin(18))

写数据

SPI.write(buf)

函数说明:将 buf 中的所有数据写入到总线。
示例:

buf = bytearray([1,2,3,4,5,6,7,8])
spi.write(buf)

读数据

SPI.read(len, data=0x00)

函数说明:读取len个数据的同时写入len个data数据,以数组的形式返回读取到的数据。

len: 需要读取的字节长度
data: 写入的单字节数据

示例:

buf=bytearray(2) #申请长度为2的缓冲区
buf = spi.read(2, 0x00)

SPI.readinto(buf, data=0x00)

函数说明:读取buf.len个数据并存入buf中,同时写入buf.len个data数据,函数返回None。

buf: 数据缓冲区
data: 写入的单字节数据

SPI.write_readinto(write_buf, read_buf)

函数说明:写入write_buf并读取到 read_buf,写入并读取的长度为buf长度,要求两个缓冲区长度相同。

write_buf: 写数据缓冲区
read_buf: 读数据缓冲区

示例:

write_buf = bytearray([1, 2, 3, 4, 5, 6, 7, 8])
read_buf = bytearray(8)
spi.write_readinto (write_buf, read_buf)
print(read_buf)

释放资源

SPI.deinit()

函数说明:关闭SPI。
示例:

spi.deinit()

  • SPI.MSB = 0 从一个字节中的最高位依次到最低位开始发送该字节数据

  • SPI.LSB = 1 从一个字节中的最低位依次到最高位开始发送该字节数据

综合示例

todo