
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的通信协议,常用于微控制器与外围设备(如传感器、显示屏、ADC/DAC 等)之间的短距离通信。
高速传输速率(通常可达几 Mbps)
全双工通信(可同时发送和接收数据)
同步通信(使用时钟信号同步数据传输)
支持多个从设备
SPI 通信主要通过 4 根线实现:
SCLK(Serial Clock):时钟线,由主机产生,用于同步数据传输
MOSI(Master Out Slave In):主机输出,从机输入
MISO(Master In Slave Out):主机输入,从机输出
CS/SS(Chip Select/Slave Select):片选线,用于选择通信的从设备(低电平有效)
主机通过拉低某个从设备的 CS 线选中该设备
主机产生 SCLK 时钟信号
数据在时钟信号的同步下,通过 MOSI 和 MISO 线同时传输
每个时钟周期传输一位数据
通信结束后,主机拉高 CS 线释放该从设备
树莓派 Pico的SPI 有 2 个硬件 SPI 控制器:SPI0 和 SPI1,默认引脚分配如下:其中CS 引脚可任意选择 GPIO 引脚。
| spi控制器 | SCK引脚 | MOSI引脚 | MISO引脚 |
| SPI0 | SPI2 | GP3 | GP4 |
| SPI1 | GP10 | GP11 | GP12 |
以下示例展示如何使用树莓派 Pico 的 SPI 接口进行通信,包含主机和从机两种模式的代码。
from machine import Pin, SPI
import time
# 初始化SPI0为主机模式
spi = SPI(0,
baudrate=1000000, # 1MHz
polarity=0, # 时钟空闲时为低电平
phase=0, # 数据在时钟上升沿采样
sck=Pin(2), # SCK引脚
mosi=Pin(3), # MOSI引脚
miso=Pin(4)) # MISO引脚
# 初始化CS引脚
cs = Pin(5, Pin.OUT, value=1) # 初始化为高电平(未选中)
def send_receive_data(data):
cs.value(0) # 拉低CS,选中从设备
time.sleep_us(10) # 短暂延时
# 创建接收缓冲区(与发送数据长度相同)
rx_data = bytearray(len(data))
# 发送并接收数据
spi.write_readinto(data, rx_data)
time.sleep_us(10)
cs.value(1) # 拉高CS,释放从设备
return rx_data
# 主循环
while True:
# 发送数据
tx_data = bytearray([0x01, 0x02, 0x03, 0x04])
rx_data = send_receive_data(tx_data)
print(f"发送: {list(tx_data)}")
print(f"接收: {list(rx_data)}")
time.sleep(1)
主机模式代码(spi_master.py):
初始化 SPI0 为主机模式,设置波特率为 1MHz
配置 SCLK、MOSI、MISO 和 CS 引脚
send_receive_data函数实现:拉低 CS 选中设备、发送数据并接收响应、最后释放 CS
主循环中周期性发送数据并打印收发内容
从机模式代码(spi_slave.py):
初始化 SPI0 为从机模式,参数需与主机匹配
监听 CS 引脚状态,当被选中时进行数据收发
接收主机数据的同时发送预设的响应数据
简单修改响应数据,以便观察通信效果
准备两块树莓派 Pico 板
分别将主机代码和从机代码上传到两块 Pico
按如下方式连接线路:
主机 SCLK(GP2)→ 从机 SCLK(GP2)
主机 MOSI(GP3)→ 从机 MOSI(GP3)
主机 MISO(GP4)→ 从机 MISO(GP4)
主机 CS(GP5)→ 从机 CS(GP5)
两块 Pico 共地(GND 相连)
分别给两块 Pico 上电,通过串口监视器观察通信结果
主从设备的 SPI 参数(波特率、极性、相位)必须一致
通信距离不宜过长,通常在几米以内
多从机情况下,每个从机需要独立的 CS 线
高速通信时可能需要考虑线路阻抗匹配
通过 SPI 协议,树莓派 Pico 可以高效地与各种外设进行通信,适用于需要高速数据传输的场景。
这里以ESP32开发板为例,使用Arduino IDE开发框架,Arduino已经内置的 <SPI.h> 库可以非常方便地操作 SPI。ESP32 拥有多个 SPI 控制器(如 HSPI / VSPI),以下示例使用默认的 VSPI 引脚。
#include <SPI.h>
// 定义引脚(ESP32 默认 VSPI 引脚)
const int SCK_PIN = 18;
const int MISO_PIN = 19;
const int MOSI_PIN = 23;
const int CS_PIN = 5;
void setup() {
Serial.begin(115200);
// 初始化片选引脚
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // 默认不选中
// 初始化 SPI 总线(使用默认 VSPI 引脚)
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
// 如果需要自定义时钟频率、位序和模式,可以使用 SPISettings
// SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
Serial.println("ESP32 SPI 初始化完成");
}
void loop() {
// 选中的配置参数:1MHz 频率,高位先发(MSBFIRST),SPI 模式 0
SPISettings settings(1000000, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(settings);
digitalWrite(CS_PIN, LOW); // 拉低片选,选中从设备
// 发送读取命令并接收数据
byte cmd = 0x9F;
byte response1 = SPI.transfer(cmd); // 发送命令,接收一个字节
byte response2 = SPI.transfer(0x00); // 发送空字节继续接收
digitalWrite(CS_PIN, HIGH); // 拉高片选,结束传输
SPI.endTransaction();
// 打印结果
Serial.print("ESP32 SPI 收到响应: 0x");
Serial.print(response1, HEX);
Serial.print(", 0x");
Serial.println(response2, HEX);
delay(2000);
}这段 ESP32 C++ 示例代码的核心逻辑可以拆解为 初始化(setup) 和 周期性数据通信(loop) 两个主要部分。它的本质流程是:建立 SPI 通信通道→ 打开从设备开关→边送数据边收数据→关闭开关→打印结果。
setup() 函数在 ESP32 开机或复位后仅执行一次,主要负责外设引脚的配置和总线准备:
2.1串口初始化 (Serial.begin)
开启串口通信(波特率 115200),用于将 SPI 通信的结果打印到电脑串口监视器中。
2.2GPIO 引脚状态设置 (pinMode & digitalWrite)
将片选引脚 CS_PIN (Pin 5) 设置为输出模式。
立即将 CS_PIN 设置为 高电平(HIGH)。因为 SPI 的片选线通常是低电平有效,拉高表示目前不与任何从设备通信,防止开机时总线产生干扰信号。
3.3SPI 硬件外设初始化 (SPI.begin)
传入定义的 SCK(18)、MISO(19)、MOSI(23)、CS(5) 引脚。
ESP32 硬件内部的 SPI 控制器完成引脚复用映射,为后续的高速收发做准备。
loop() 函数会无限循环执行,每次循环完成一次完整的 SPI 发送与接收流程:
配置 SPI 传输参数 (SPISettings)
定义本次通信的“工作协议”:
频率:1000000 Hz (1 MHz)。
位序:MSBFIRST(最高位先发)。
时钟模式:SPI_MODE0(空闲时时钟为低电平,在第一个上升沿采样数据)。
锁定总线并开启事务 (SPI.beginTransaction)
应用上述配置,并防止其他可能使用 SPI 的中断或任务干扰当前总线。
激活目标从设备 (digitalWrite(CS_PIN, LOW))
将 CS_PIN 拉低(LOW)。这相当于告诉挂载在总线上的目标从设备:“注意,我现在要和你说话了!”
全双工数据收发 (SPI.transfer)
第一步:执行 SPI.transfer(0x9F)。ESP32 的 MOSI 线送出 0x9F(通常是外设读取 ID 的指令),同一时间 MISO 线上从设备返还的数据被 ESP32 接收并存入 response1。
第二步:执行 SPI.transfer(0x00)。为了继续读取下一个字节的数据,主机必须继续产生时钟,因此发送一个无意义的哑数据(Dummy byte, 0x00),将从设备返回的第二个字节读取到 response2 中。
关闭从设备并释放总线 (digitalWrite & SPI.endTransaction)
将 CS_PIN 拉高(HIGH),结束本次传输,从设备回到待机状态。
释放 SPI 总线控制权,允许其他 SPI 事务运行。
结果处理与延时 (Serial.print & delay)
将收到的 response1 和 response2 以十六进制格式打印出来。
delay(2000) 让程序暂停 2 秒,随后进入下一次循环。