SPI 通信
SPI(Serial Peripheral Interface,串行外设接口) 是一种高速、全双工的同步串行通信协议。SPI 广泛应用于微控制器与各种外设之间的通信,如 SD 卡、显示屏、传感器、Flash 存储器等。
SPI 具有以下特点:
- 四线通信:使用 4 根信号线进行通信(在单从设备时可减少为 3 根)
- 主从架构:明确的主从关系,主设备控制通信时序
- 全双工:可以同时进行数据发送和接收
- 高速传输:速度通常比 I2C 和 UART 更快,可达到数十 MHz
- 同步通信:由主设备提供时钟信号,确保数据传输的可靠性
SPI 协议的四根核心信号线分别为:
- SCK(Serial Clock,时钟线):串行时钟线。由主机产生的时钟信号,用于同步数据传输。
- MOSI(Master Out Slave In,主出从入):主出从入线。主设备通过这条线向从设备发送数据。
- MISO(Master In Slave Out,主入从出):主入从出线。从设备通过这条线向主设备发送数据。
- SS/CS(Slave Select/Chip Select,从机选择/片选):片选信号线。主机用此线选择当前要通信的从机,低电平为选中。
SPI 主机通过不同的 CS (片选)线选中目标从机。每增加一个 SPI 外设,就要多占用一个独立的 CS 引脚。
1. ESP32 中的 SPI
ESP32 有多组硬件 SPI 控制器(常用 HSPI 和 VSPI),能灵活映射到大部分 GPIO。常见应用包括连接 SD 卡模块、TFT/OLED 屏、无线射频(nRF24L01)、AD/DA 芯片等。
ESP32 芯片通常有 4 个 SPI 控制器(以具体设备规格书为准):
- SPI0 和 SPI1:主要用于连接内部 Flash 存储器,一般不建议用户使用
- SPI2 和 SPI3:可供用户自由使用的 SPI 控制器,可以分配到大多数 GPIO 引脚上
ESP32 的 SPI 引脚分配具有高度灵活性。这得益于其内部的 IO MUX 和 GPIO 矩阵 两种硬件路由机制:
- IO MUX:为外设提供一组固定的、性能最优的默认引脚。使用这些引脚可以达到最高的通信速率,是驱动高速设备的理想选择。
- GPIO 矩阵:允许将 SPI 功能路由到几乎任何可用的 GPIO 引脚。虽然最高速率会受到一定限制,但这为电路设计提供了极大的便利。
在 Arduino 编程环境中,使用 SPI.h
库即可方便地进行 SPI 外设的通信。
在 Arduino 中调用 SPI.begin()
时,如果不指定引脚,库会使用默认的 IOMUX 引脚。若提供了自定义的引脚号,则会通过 GPIO 矩阵进行路由。
2. 示例 1:使用 SPI 控制显示屏
这个示例演示如何使用 SPI 接口驱动 OLED 显示屏。与 I2C 版本相比,SPI 版本的显示屏通常有更快的刷新速度。
2.1 搭建电路
需要使用的器件有:
- 微雪 1.5 寸 OLED 模块 × 1
- 面包板 × 1
- 导线
- ESP32 开发板
按照下面接线图连接电路:
ESP32-S3-Zero 引脚图

ESP32 引脚 | OLED 模块 | 说明 |
---|---|---|
GPIO 13 | SCK | SPI 时钟线 |
GPIO 11 | MOSI | SPI 数据输出 |
GPIO 10 | CS | 片选信号 |
GPIO 8 | DC | 数据/命令选择 |
3.3V | VCC | 电源正极 |
GND | GND | 电源负极 |
2.2 代码
此代码示例依赖 "Adafruit_SSD1327" 库。请在 Arduino IDE 的库管理器中搜索并安装 'Adafruit SSD1327' 库。
安装方法请参考:Arduino 库管理教程。
#include <Adafruit_SSD1327.h>
#define OLED_SCK 13 // 时钟线
#define OLED_MOSI 11 // 数据输出线
#define OLED_CS 10 // 片选线
#define OLED_DC 8 // 数据/命令选择线
#define OLED_RESET -1 // 复位引脚(未使用)
// 创建显示器对象
Adafruit_SSD1327 display(128, 128, &SPI, OLED_DC, OLED_RESET, OLED_CS);
void setup() {
Serial.begin(9600);
// 初始化 SPI 总线
SPI.begin(OLED_SCK, -1, OLED_MOSI, OLED_CS);
Serial.println("SSD1327 OLED test");
// 初始化显示器
if (!display.begin()) {
Serial.println("Unable to initialize OLED");
while (1) yield();
}
// 显示设置
display.clearDisplay();
display.setRotation(3);
display.setTextSize(2);
display.setTextColor(SSD1327_WHITE);
// 显示文本
display.setCursor(10, 10);
display.println("Hello,");
display.setCursor(40, 30);
display.setTextColor(SSD1327_BLACK, SSD1327_WHITE);
display.println(" World!");
display.display();
display.setCursor(15, 60);
display.setTextColor(SSD1327_WHITE);
display.println("SPI TEST");
display.display();
delay(1000);
}
void loop() {
}
2.3 代码解析
-
#include <SPI.h>
: 引入 Arduino 的 SPI 通信库。 -
SPI.begin(OLED_SCK, -1, OLED_MOSI, OLED_CS)
: 初始化 SPI 总线并指定引脚。OLED_SCK
: 时钟线引脚-1
: MISO 引脚(此处未使用,因为显示屏只需要单向数据传输)OLED_MOSI
: 数据输出线引脚OLED_CS
: 片选线引脚
-
Adafruit_SSD1327 display(...)
: 创建显示器对象。128, 128
: 屏幕分辨率&SPI
: 使用默认的 SPI 对象OLED_DC
: 数据/命令选择引脚OLED_RESET
: 复位引脚OLED_CS
: 片选引脚
-
DC 引脚的作用: SPI 显示屏通常需要额外的 DC(Data/Command)引脚来区分传输的是显示数据还是控制命令。
2.4 运行结果
-
OLED 屏幕将被点亮,显示以下内容:
- 第一行是白色字体的 “Hello,”。
- 第二行是反色显示的 “ World!”(即黑色文字,白色背景)。
- 第三行是白色字体的 “SPI TEST”。
3. 示例 2:使用多个 SPI 设备
代码
在 SPI 库中,已经预先创建了 SPI
这个对象。如果要使用另一个 SPI 控制器,可以通过下面的方式定义:
#include <SPI.h>
SPIClass spiB(HSPI); //需要在 setup() 函数前定义
....
spiB.begin(sclkB, misoB, mosiB, csB);