12. SPI
12.1 什么是SPI
SPI(Serial Peripheral Interface)是一种同步串行通信协议,用于在微控制器和外部设备之间进行数据传输。它由一个主设备(通常是微控制器MCU)和一个或多个从设备组成,即一主多从模式。它通常用于短距离、高速、全双工的通信,它在许多嵌入式系统和电子设备中被广泛应用。
12.2 SPI的硬件接口
SPI主要使用4根线,时钟线(SCLK),主输出从输入线(MOSI),主输入从输出线(MISO)和片选线(CS)。
通信线 | 说明 |
---|---|
SCLK | 时钟线,也叫做SCK。由主机产生时钟信号。 |
MOSI | 主设备输出从设备输入线,也叫做SDO。意为主机向从机发送数据。 |
MISO | 主设备输入从设备输出线,也叫做SDI。意为主机接收从机的数据。 |
CS | 片选线,也叫做NSS。从机使能信号,由主机控制。当我们的主机控制某个从机时,需要将从机对应的片选引脚电平拉低或者是拉高。 |
- 主设备通过MOSI线向从设备发送数据。在每个时钟周期中,主设备将一个位发送到MOSI线上,从设备在下一个时钟周期中读取该位。
- 从设备通过MISO线向主设备发送数据。在每个时钟周期中,从设备将一个位发送到MISO线上,主设备在下一个时钟周期中读取该位。
- 数据传输可以是全双工的,即主设备和从设备可以同时发送和接收数据。
- 数据传输的长度可以是可变的,通常以字节为单位。
- 数据传输可以是单向的,即主设备只发送数据或只接收数据。
- 数据传输可以是多主设备的,即多个主设备可以与多个从设备进行通信。
主设备是通过片选线选择要与之通信的从设备。每个从设备都有一个片选线,当片选线为低电平时,表示该从设备被选中。(也有一些设备以高电平有效,需要根据其数据手册确定)。主设备通过控制时钟线的电平来同步数据传输。时钟线的上升沿和下降沿用于控制数据的传输和采样。
SPI的主从接线方式与串口类似,需要发送与接收交叉连接。
12.3 SPI的模式选择
SPI协议定义了多种传输模式,也称为SPI模式或时序模式,用于控制数据在时钟信号下的传输顺序和数据采样方式。SPI的传输模式主要由两个参数决定:时钟极性 (CKPL) 和相位 (CKPH)。时钟极性 (CKPL):时钟极性定义了时钟信号在空闲状态时的电平。
CKPL = 0:时钟信号在空闲状态时为低电平。
CKPL = 1:时钟信号在空闲状态时为高电平。
时钟相位 (CKPH):相位定义了数据采样和更新发生在时钟信号的哪个边沿上。
CKPH = 0:数据采样发生在时钟的第一个边沿,数据更新发生在第二个边沿。
CKPH = 1:数据采样发生在时钟的第二个边沿,数据更新发生在第一个边沿。
以下是常见的SPI模式:
模式0(CKPL=0,CKPH=0)
:
- 时钟极性(Clock Polarity)为0,表示时钟空闲状态为低电平。
- 时钟相位(Clock Phase)为0,表示数据在时钟信号的第一个边沿(时钟上升沿)进行采样和稳定。
模式1(CKPL=0,CKPH=1)
:
- 时钟极性为0,时钟空闲状态为低电平。
- 时钟相位为1,数据在时钟信号的第二个边沿(时钟下降沿)进行采样和稳定。
模式2(CKPL=1,CKPH=0)
:
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为0,数据在时钟信号的第一个边沿(时钟下降沿)进行采样和稳定。
模式3(CKPL=1,CKPH=1)
:
- 时钟极性为1,时钟空闲状态为高电平。
- 时钟相位为1,数据在时钟信号的第二个边沿(时钟上升沿)进行采样和稳定。
选择SPI模式的决策通常取决于从设备的规格要求和通信协议。不同的设备可能采用不同的模式,所以在与特定从设备通信之前,必须了解从设备所需的SPI模式。如果没有明确指定SPI模式,通常可以根据从设备的规格手册或通信协议选择最常见的模式0或模式3进行尝试。此外,还需要注意SPI模式时钟的频率限制,以确保主设备和从设备之间的时钟频率匹配。
12.4 SPI的基本参数
SPI协议定义了一组参数,这些参数对于正确设置通信参数并实现SPI通信非常重要。 SPI的基本参数包括:
时钟极性(CPOL)
:指定时钟空闲状态时信号线的电平,有两种状态:空闲时为高电平(CPOL=1)或空闲时为低电平(CPOL=0);时钟相位(CPHA)
:指定数据采样的时刻,有两种状态:在时钟上升边沿之后采样数据(CPHA=0)或在时钟下降边沿之前采样数据(CPHA=1);数据位数
:指定每个SPI数据包包含的位数(通常为8位),也可以设置为较小或较大的值;传输模式
:确定数据如何在SPI总线上传输(如全双工、半双工或单向模式);时钟速率
:指定SPI总线的时钟速率,以bits per second (bps)为单位;主/从模式
:确定设备是SPI总线上的主机还是从机;传输顺序
:指定数据的 bit 传输顺序,MSB(most significant bit)优先或LSB(least significant bit)优先。
这些参数可以通过配置SPI控制寄存器来设置,以确保SPI设备之间的正确通信。在确定这些参数时,应该考虑实际硬件设置和通信需求。如果需要使用SPI进行通信,请确保正确设置这些参数。
12.5 软件SPI与硬件SPI
SPI与IIC类似,都分有软件SPI和硬件SPI,软件SPI部分不再讲解,本章节着重讲解硬件SP部分。
ESP32-S3芯片集成了四个 SPI 控制器:
- SPI0
- SPI1
- 通用 SPI2,即 GP-SPI2
- 通用 SPI3,即 GP-SPI3
SPI0 和 SPI1 控制器主要供内部使用以访问外部 flash 及 PSRAM。我们只能使用SPI2和SPI3。 硬件SPI支持以下特性:
12.6 SPI的使用流程
在 Arduino 中使用 SPI(Serial Peripheral Interface)需要以下步骤:
12.6.1 包含 SPI 库
首先,在你的 Arduino 代码中包含 SPI 库。可以使用下面的代码行添加 SPI 库的引用:
#include <SPI.h>
12.6.2 初始化 SPI
在 setup() 函数中,你需要初始化 SPI。调用
SPI.begin()` 函数以启动 SPI 通信。你可以设置参数来配置 SPI 的模式(主/从),数据位顺序和时钟频率。例如,以下代码将将 Arduino 配置为 SPI 主设备:
void setup() {
// 初始化 SPI
SPI.begin();
// 设置 SPI 模式和时钟频率
SPI.setClockDivider(SPI_CLOCK_DIV4);
SPI.setDataMode(SPI_MODE0);
}
2
3
4
5
6
7
12.6.3 传输数据
通过 SPI 发送和接收数据。你可以使用 SPI.transfer()
函数来发送和接收一个字节的数据。例如,以下代码将发送一个字节并等待接收一个字节的响应:
void loop() {
byte dataToSend = 0x55;
byte receivedData;
// 传输数据
digitalWrite(CS_PIN, LOW); // 选择 SPI 设备
receivedData = SPI.transfer(dataToSend); // 发送并接收数据
digitalWrite(CS_PIN, HIGH); // 取消 SPI 设备选择
// 处理接收到的数据
// ...
delay(1000);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在 loop()
函数中,你可以使用 digitalWrite()
来选择 SPI 设备(例如,通过设置CS电平低来选择设备),然后使用 SPI.transfer()
来发送和接收数据。完成后,取消选择 SPI 设备。
12.7 SPI相关函数
SPI初始化
cSPI.begin();
1SPI接口默认VSPI. 接口频率1 000 000, 数据默认采用MSB格式(低有效位优先), 时钟模式:SPI_MODE0(SCLK闲置为0, SCLK上升沿采样)
设置传输方式
cSPI.setBitOrder(LSBFIRST);
1参数:
bitOrder: 传输方式, 可选: LSBFIRST 低有效位先传 ; HSBFIRST 高有效位先传设置频率
cSPI.setFrequency(1000000);
1参数:
freq:频率设置时钟模式
cSPI.setDataMode(SPI_MODE0);
1参数:
dataMode:时钟模式, 可以取以下值
模式 | 说明 |
---|---|
SPI_MODE0 | SCLK闲置为低电平,上升沿采样(默认) |
SPI_MODE1 | SCLK闲置为低电平,下降沿采样 |
SPI_MODE2 | SCLK闲置为高电平,上升沿采样 |
SPI_MODE3 | SCLK闲置为高电平,下降沿采样 |
按照setting的设置启动SPI通信 采用该函数,可以代替上面三个函数了
cSPI.beginTransaction(setting);
1参数:setting 设置. 是SPISettings类型的对象, 有_bitOrder ,_clock ,_dataMode 这三个属性. 示例:
csetting1._bitOrder = LSBFIRST; setting1._clock = 1000000; setting1._dataMode = SPI_MODE0; SPI.beginTransaction(setting1);
1
2
3
4结束SPI通信
cSPI.endTransaction();
1功能:结束SPI通信
接收/发送一个字节的数据
cuint8_t SPIClass::transfer(uint8_t data)
1参数:
data: 要发送的数据
返回值: 接收到的数据
我们还可以选择发送数据的长度,如下示例:
SPI.transfer(0x01);
SPI.transfer16(0x0102);
SPI.transfer32(0x01020304);
uint8_t byte1;
uint16_t bytes2;
uint32_t bytes3;
byte1 = SPI.transfer();
bytes2 = SPI.transfer16();
bytes3 = SPI.transfer32();
2
3
4
5
6
7
8
9
10
12.8 SPI验证
通过0.96寸SPI接口的OLED屏幕作为案例测试。首先安装U8G2库。
代码示例:
#include <Arduino.h>
#include <U8g2lib.h>
// 构造对象
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=d0=*/8, /* data=d1=*/9,
/* cs=*/12, /* dc=*/11, /* reset=*/10);
void setup(void)
{
// 初始化 oled 对象
u8g2.begin();
// 开启中文字符集支持
u8g2.enableUTF8Print();
}
void loop(void)
{
// 设置字体
u8g2.setFont(u8g2_font_unifont_t_chinese2);
// 设置字体方向
u8g2.setFontDirection(0);
u8g2.clearBuffer();
u8g2.setCursor(0, 15);
u8g2.print("Hello LCKFB!");
u8g2.setCursor(0, 40);
u8g2.print("你好立创开发板!");
u8g2.sendBuffer();
delay(1000);
}
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
12.9 SPI效果
静态显示:Hello LCKFB! 你好立开发板!