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实验室做出了下图方便大家理解。
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