# SPI总线协议 - [概要](#概要) - [什么是SPI](#什么是spi) - [通信原理](#通信原理) - [物理接线](#物理接线) - [起始、停止信号](#起始停止信号) - [数据有效性](#数据有效性) - [操作SPI设备通用方法集合](#操作spi设备通用方法集合) - [SPI优点](#spi优点) - [SPI缺点](#spi缺点) - [SPI 引脚定义](#spi--引脚定义) - [machine.SPI API详解](#machinespi-api详解) - [硬件SPI构造`machine.spi`](#硬件spi构造machinespi) - [宏定义](#宏定义) - [类](#类) - [函数](#函数) - [SPI.init初始化](#spiinit初始化) - [SPI.write写数据](#spiwrite写数据) - [SPI.read读数据](#spiread读数据) - [释放资源SPI.deinit](#释放资源spideinit) - [软件SPI构造`machine.SoftSPI`](#软件spi构造machinesoftspi) - [宏定义](#宏定义-1) - [类](#类-1) - [函数](#函数-1) - [示例](#示例) ## 概要 本节详细讲解SPI总线协议。 ## 什么是SPI - SPI是串行外设接口(Serial Peripheral Interface)的缩写。是 Motorola 公司推出的一 种同步串行接口技术,是一种高速的,全双工,同步的通信总线。 - SPI用于在主设备和从设备之间进行通信,常用于与闪存、实时时钟、传感器以及模数转换器等进行通信。 - 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 | 片选信号, 从设备使能信号,由主设备控制。 | 一个主设备和两个从设备的连接示意图如图1所示,Device A和Device B共享主设备的SCLK、MISO和MOSI三根引脚,Device A的片选CS0连接主设备的CS0,Device B的片选CS1连接主设备的CS1。  <div align=center> 图 1 SPI主从设备连接示意图 </div> - SPI通信通常由主设备发起,通过以下步骤完成一次通信: 1. 通过CS选中要通信的从设备,在任意时刻,一个主设备上最多只能有一个从设备被选中。 2. 通过SCLK给选中的从设备提供时钟信号。 3. 基于SCLK时钟信号,主设备数据通过MOSI发送给从设备,同时通过MISO接收从设备发送的数据,完成通信。 #### 起始、停止信号  如上图,红色编号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接口定义了操作SPI设备的通用方法集合,包括: - SPI设备句柄获取和释放。 - SPI读写: 从SPI设备读取或写入指定长度数据。 - SPI自定义传输:通过消息传输结构体执行任意读写组合过程。 - SPI设备配置:获取和设置SPI设备属性。 ### SPI优点 - 支持全双工通信,发送数据和接收数据可以同时进行。 - 通信简单 - 数据传输速率快 ### SPI缺点 - 接线繁杂,需要至少四根接线 - 在多个从机的情况下,每个从机都需要接入一根CS片选信号线,这是十分的浪费芯片的IO资源 ## SPI 引脚定义  有10个引脚可以用作SPI | SPI组号 | SCK | MOSI | MSIO | | ------- | ------------- | ------------ | ----- | | SPI 1 | pin 0 | pin 2 | pin 1 | | SPI 0 | pin 6、pin 10 | pin 8、pin 9 | pin 7 |   除此两组SPI硬件资源外,其余的GPIO理论上也可以配置成SPI总线的输入输出管脚,只要满足该管脚既能够作为输入也能够作为输出。因此, 在MicroPython中,拥有两种模式的SPI总线: - 硬件SPI: - 软件SPI (GPIO模拟): ## machine.SPI API详解 ### 硬件SPI构造`machine.spi`   硬件SPI:由SPI 0或SPI1组成   使用`from machine import SPI`导入`machine`模块的类`SPI`   再使用`TAB` 按键来查看`SPI`中所包含的内容: ```python >>>from machine import SPI >>>SPI. read readinto write LSB MSB deinit init write_readinto ``` #### 宏定义   下面的宏定义用于配置SPI发送字节数据的顺序。 | 宏定义 | 含义 | | ------- | -------------------------------------------------- | | Pin.MSB | 从一个字节中的最高位依次到最低位开始发送该字节数据 | | Pin.LSB | 从一个字节中的最低位依次到最高位开始发送该字节数据 |   以下是SPI1的构造: ```python >>> from machine import SPI >>> spi=SPI(1,sck=Pin(0)) >>> spi SPI(id=1, baudrate=500000, polarity=0, phase=0, bits=8, endia=1, sck=0, mosi=-1, miso=-1) ```   以下是SPI0的构造: ```python >>> from machine import SPI >>> spi0=SPI(0,sck=Pin(6)) >>> spi0 SPI(id=0, baudrate=500000, polarity=0, phase=0, bits=8, endia=1, sck=6, mosi=-1, miso=-1) ``` #### 类   `class machine.SPI(id,baudrate, polarity, phase, bits, endia, sck, mosi, miso)` - `id`:取决于特定端口及其硬件。 值0、1等常用于选择硬件SPI0或SPI1。 - `baudrate`:SCK时钟频率 范围 `0 < baudrate ≤ 0x0FFFFFFF (十进制:0 < baudrate ≤ 2147483647)` - `polarity`:极性 - `0` 时钟空闲时候的电平是低电平,所以当SCLK有效的时候,就是高电平 - `1` 时钟空闲时候的电平是高电平,所以当SCLK有效的时候,就是低电平 - `phase`:相位 - `0` 在下降沿采样数据 - `1` 在上升沿采样数据 - `bits`:传输数据位数 - `endia`:字节数据传输的方式,可为 `SPI.MSB`即0 或 `SPI.LSB`即1 - `sck` 时钟信号引脚 - `mosi` 主设备输出,从设备输入引脚 - `miso` 主设备输入,从设备输出引脚   示例: ```python from machine import SPI, Pin spi0 = SPI(0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7)) #构造SPI0,SCK时钟频率为40000000,SCK空闲状态为高电平,SCK下降沿采样 #传输数据位8位,字节数据传输的方式为SPI.MSB从高到低 #sck时钟信号引脚是pin6,mosi主设备输出引脚是pin8,msio主设备输入引脚是pin7 ``` #### 函数 ##### 初始化   `SPI.init(baudrate, polarity, phase, sck, mosi, miso)`   函数说明:初始化SPI总线   参数含义同上文类构造一致   示例: ```python >>>from machine import SPI, Pin >>>spi0 = SPI(0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7))#构造硬件SPI0 >>>spi0.init(baudrate=100000, polarity=1, phase=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7))#初始化SPI总线 >>>spi0 SPI(id=0, baudrate=100000, polarity=1, phase=0, bits=8, endia=0, sck=6, mosi=8, miso=7) ``` ##### 写数据   `SPI.write(buf)`   函数说明:将 buf 中的所有数据写入到总线。   示例: ```python >>>from machine import SPI, Pin >>>spi0 = SPI(0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7))#构造硬件SPI0 >>>write_buf = bytearray([1,2,3,4,5,6,7,8]) >>>sw=spi0.write(write_buf)#将write中的所有数据写入到总线 ``` ##### 读数据   `SPI.read(len, data=0x00)`   函数说明:读取len个数据的同时写入len个data数据,以数组的形式返回读取到的数据。   `len`: 需要读取的字节长度   `data`: 写入的单字节数据   示例: ```python >>>from machine import SPI, Pin >>>spi0 = SPI(0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7))#构造硬件SPI0 >>print(spi0.read(2, 0x00)) #读取2个字节,写入数据0x00 b'\x00\x00' ```   `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`: 读数据缓冲区   示例: ```python >>>from machine import SPI, Pin >>>spi0 = SPI(0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7))#构造硬件SPI0 >>>write_buf = bytearray([1, 2, 3, 4, 5, 6, 7, 8]) >>>read_buf = bytearray(8) >>>spi.write_readinto (write_buf, read_buf)#写入write_buf并读取8个字节 >>>print(read_buf) bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00') ``` ##### 释放资源   `SPI.deinit()`   函数说明:关闭SPI。   示例: ```python >>>from machine import SPI, Pin >>>spi0 = SPI(0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7))#构造硬件SPI0 >>>spi0.deinit()#关闭SPI0 >>>spi0 SPI(id=0, baudrate=40000000, polarity=1, phase=0, bits=8, endia=0, sck=6, mosi=8, miso=7) ``` ### 软件SPI构造`machine.SoftSPI`   硬件SPI构造的引脚功能必须全为SPI0或全为SPI1的,软件SPI构造更为灵活,可以随机选择SPI0或SPI1的sck,miso,msio引脚。   使用`from machine import SoftSPI`导入`machine`模块的类`SoftSPI`   再使用`TAB` 按键来查看`SoftSPI`中所包含的内容: ```python >>>from machine import SoftSPI >>>SoftSPI. read readinto write LSB MSB deinit init write_readinto ``` #### 宏定义   软件SPI构造宏定义和硬件构造相同 #### 类   `class machine.SoftSPI(id,baudrate, polarity, phase, bits, endia, sck, mosi, miso)` - `baudrate`:SCK时钟频率 范围 `0 < baudrate ≤ 0x0FFFFFFF (十进制:0 < baudrate ≤ 2147483647)` - `polarity`:极性 - `0` 时钟空闲时候的电平是低电平,所以当SCLK有效的时候,就是高电平 - `1` 时钟空闲时候的电平是高电平,所以当SCLK有效的时候,就是低电平 - `phase`:相位 - `0` 在下降沿采样数据 - `1` 在上升沿采样数据 - `bits`:传输数据位数 - `firstbi`:字节数据传输的方式,可为 `SPI.MSB`即0 或 `SPI.LSB`即1 - `sck` 时钟信号引脚 - `mosi` 主设备输出,从设备输入引脚 - `miso` 主设备输入,从设备输出引脚   示例: ```python >>>from machine import SoftSPI, Pin >>>softspi = SoftSPI(baudrate=400000, polarity=0, phase=0, bits=8, firstbit=0, sck=Pin(6, dir=Pin.OUT), mosi=Pin(8, dir=Pin.OUT), miso=Pin(7, dir=Pin.OUT)) #构造SPI,SCK时钟频率为400000,SCK空闲状态为低电平,SCK下降沿采样 #传输数据位8位,字节数据传输的方式为SPI.MSB从高到低 #sck时钟信号引脚是pin6、,mosi主设备输出引脚是pin8,msio主设备输入引脚是pin7,引脚都是输出模式 ``` #### 函数   软件构造的函数与硬件构造的函数相同 ## 示例   分别构建硬件SPI和软件SPI ```python from machine import SPI, Pin, SoftSPI # 硬件 SPI spi = SPI(0, baudrate=2000000, polarity=0, phase=0, bits=8, endia=0, sck=Pin(6), mosi=Pin(8), miso=Pin(7)) # 软件 SPI softspi = SoftSPI(baudrate=400000, polarity=0, phase=0, bits=8, firstbit=0, sck=Pin(6, dir=Pin.OUT), mosi=Pin(8, dir=Pin.OUT), miso=Pin(7, dir=Pin.OUT)) ```   第一行导入`machine`模块的硬件类`Pin`,硬件SPI类`SPI`和软SPI`SoftSPI`   第三行构造硬件SPI - 0代表构建的是SPI0 - baudrate=2000000表示SCK时钟频率为2000000 - polarity=0表示SCK空闲状态为低电平 - phase=0表示SCK下降沿采样 - bits=8传输数据位8位 - endia=0表示字节数据传输的方式为SPI.MSB从高到低 - sck=Pin(6)表示sck时钟信号引脚是pin6 - mosi=Pin(8)表示mosi主设备输出引脚是pin8 - miso=Pin(7)msio主设备输入引脚是pin7   第五行构造软件SPI - baudrate=2000000表示SCK时钟频率为2000000 - polarity=0表示SCK空闲状态为低电平 - phase=0表示SCK下降沿采样 - bits=8传输数据位8位 - firstbit=0表示字节数据传输的方式为SPI.MSB从高到低 - sck=Pin(6, dir=Pin.OUT)表示sck时钟信号引脚是pin6,引脚为输出模式 - mosi=Pin(8, dir=Pin.OUT)表示主设备输出引脚是pin6,引脚为输出模式 - miso=Pin(7, dir=Pin.OUT))表示主设备输入引脚是pin6,引脚为输出模式