Arduino - SPI 总线通信
Arduino - 串行外设接口 (SPI)
Section titled “Arduino - 串行外设接口 (SPI)”**串行外设接口(SPI)**是一种同步串行通信协议,主要用于嵌入式系统中的短距离通信。它以比 I2C 更快并支持全双工(同时发送和接收)通信而闻名。
SPI 通信涉及一个控制通信的主设备(Master)和一个或多个响应主设备的从设备(Slave)。它使用一组四根信号线进行操作。
四根 SPI 接线
Section titled “四根 SPI 接线”| 信号 | 替代名称 | 描述 |
|---|---|---|
| SCK | SCLK | 串行时钟 - 主设备发送的时钟信号,用于同步数据传输。 |
| MOSI | COPI | 主出、从入 - 用于从主设备向从设备发送数据的线路。 |
| MISO | CIPO | 主入、从出 - 用于从从设备向主设备发送数据的线路。 |
| SS | CS | 从设备选择 - 由主设备控制的线路,用于选择要与其通信的从设备。每个从设备都需要一条专用的 SS 线。 |
Arduino SPI 引脚和接线
Section titled “Arduino SPI 引脚和接线”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 编程:事务
Section titled “现代 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 总线供其他设备使用。
示例:主从设备通信
Section titled “示例:主从设备通信”在此示例中,主设备将向从设备发送一个命令字符。从设备将处理该命令并准备一个响应。主设备随后将读取此响应。
/* 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 = '?'; // 未知命令 } }}SPI 调试
Section titled “SPI 调试”- 无通信:最常见的问题是接线。仔细检查所有连接:MOSI 到 MOSI、MISO 到 MISO、SCK 到 SCK,尤其是共地。
- 乱码数据:如果您收到不正确的数据,很可能是 SPI 设置不匹配。确保主设备和从设备使用相同的
dataMode和dataOrder。 - 多从设备问题:当使用多个从设备时,确保在任何给定时间只有一个从设备被选中(其 SS 引脚为低电平)。所有其他设备必须被取消选择(SS 引脚为高电平)。
- 最佳工具:对于复杂的 SPI 问题,逻辑分析仪是无价的。它允许您查看导线上的实际信号,并快速诊断时序、模式或数据问题。