Skip to content

Arduino - SPI 总线通信

**串行外设接口(SPI)**是一种同步串行通信协议,主要用于嵌入式系统中的短距离通信。它以比 I2C 更快并支持全双工(同时发送和接收)通信而闻名。

SPI 通信涉及一个控制通信的主设备(Master)和一个或多个响应主设备的从设备(Slave)。它使用一组四根信号线进行操作。

信号替代名称描述
SCKSCLK串行时钟 - 主设备发送的时钟信号,用于同步数据传输。
MOSICOPI主出、从入 - 用于从主设备向从设备发送数据的线路。
MISOCIPO主入、从出 - 用于从从设备向主设备发送数据的线路。
SSCS从设备选择 - 由主设备控制的线路,用于选择要与其通信的从设备。每个从设备都需要一条专用的 SS 线。

SPI 引脚位于不同 Arduino 开发板的特定引脚上。在 Arduino UNO 上,它们位于 ICSP 排针上,也映射到数字引脚 10-13。以下是连接两个 Arduino UNO 的标准接线方式:

  • 共地:将主设备上的 GND 引脚连接到从设备上的 GND 引脚。这至关重要!
  • SCK:主设备引脚 13 → 从设备引脚 13
  • MOSI:主设备引脚 11 → 从设备引脚 11
  • MISO:主设备引脚 12 → 从设备引脚 12
  • SS:主设备引脚 10 → 从设备引脚 10

注意: 对于 Mega 或 MKR 系列等其他开发板,请查阅其官方引脚图以定位 SPI 引脚。

现代的 SPI 通信方法使用“事务”。这确保了在通信开始之前,SPI 设置(如时钟速度和数据模式)已为特定设备正确配置,并且不会受到同一总线上其他设备的干扰。这是最佳实践。

  • SPI.beginTransaction(SPISettings(speed, dataOrder, dataMode)):开始一个 SPI 事务。SPISettings 是您配置通信参数的地方。
  • speed:时钟速度,单位赫兹 (Hz)(例如,4000000 表示 4MHz)。
  • dataOrder:MSBFIRST(最高有效位优先)或 LSBFIRST。
  • dataMode:SPI_MODE0、SPI_MODE1、SPI_MODE2 或 SPI_MODE3。这定义了时钟极性和相位。设备的数据手册将指定使用哪种模式。
  • SPI.transfer(data):发送一个字节的数据并同时接收一个字节回来,该字节是函数的返回值。
  • SPI.endTransaction():释放 SPI 总线供其他设备使用。

在此示例中,主设备将向从设备发送一个命令字符。从设备将处理该命令并准备一个响应。主设备随后将读取此响应。

/*
SPI 主设备示例 - 现代化
此草图向 SPI 从设备发送命令并读取响应。
*/
#include <SPI.h>
const int slaveSelectPin = 10;
// 定义我们的 SPI 通信设置
SPISettings slaveSettings(4000000, MSBFIRST, SPI_MODE0);
void setup() {
Serial.begin(115200);
while (!Serial);
// 将 Slave Select 引脚设置为输出
pinMode(slaveSelectPin, OUTPUT);
digitalWrite(slaveSelectPin, HIGH); // 最初取消选择从设备
// 初始化 SPI
SPI.begin();
Serial.println("主设备已初始化");
}
void loop() {
// 发送命令 'a' 并获取响应
char response = sendCommand('a');
Serial.print("从从设备接收到: ");
Serial.println(response);
delay(1000);
// 发送命令 'b' 并获取响应
response = sendCommand('b');
Serial.print("从从设备接收到: ");
Serial.println(response);
delay(1000);
}
char sendCommand(char command) {
// 使用从设备的设置开始事务
SPI.beginTransaction(slaveSettings);
// 选择从设备
digitalWrite(slaveSelectPin, LOW);
// 同时发送命令并接收一个字节
char result = SPI.transfer(command);
// 取消选择从设备
digitalWrite(slaveSelectPin, HIGH);
// 结束事务
SPI.endTransaction();
return result;
}
/*
SPI 从设备示例 - 现代化
此草图监听 SPI 主设备发送的命令并进行回复。
它使用中断来高效处理传入数据。
*/
#include <SPI.h>
volatile byte receivedData;
volatile byte dataToSend;
volatile bool newData = false;
void setup() {
Serial.begin(115200);
while (!Serial);
// 将 MISO 设置为输出
pinMode(MISO, OUTPUT);
// 将 SPI 配置为从设备模式
SPCR |= _BV(SPE); // 启用 SPI
SPCR |= _BV(SPIE); // 启用 SPI 中断
receivedData = 0;
dataToSend = 'x'; // 默认初始响应
Serial.println("从设备已初始化");
}
// SPI 中断服务程序
ISR(SPI_STC_vect) {
receivedData = SPDR; // 读取接收到的数据
SPDR = dataToSend; // 预加载下次要发送的数据
newData = true; // 为主循环设置标志
}
void loop() {
if (newData) {
newData = false; // 清除标志
Serial.print("接收到命令: ");
Serial.println((char)receivedData);
// 处理命令并准备下一个响应
if (receivedData == 'a') {
dataToSend = 'A'; // 准备 'A' 作为下次传输的响应
} else if (receivedData == 'b') {
dataToSend = 'B'; // 准备 'B'
} else {
dataToSend = '?'; // 未知命令
}
}
}
  • 无通信:最常见的问题是接线。仔细检查所有连接:MOSI 到 MOSI、MISO 到 MISO、SCK 到 SCK,尤其是共地。
  • 乱码数据:如果您收到不正确的数据,很可能是 SPI 设置不匹配。确保主设备和从设备使用相同的 dataMode 和 dataOrder。
  • 多从设备问题:当使用多个从设备时,确保在任何给定时间只有一个从设备被选中(其 SS 引脚为低电平)。所有其他设备必须被取消选择(SS 引脚为高电平)。
  • 最佳工具:对于复杂的 SPI 问题,逻辑分析仪是无价的。它允许您查看导线上的实际信号,并快速诊断时序、模式或数据问题。