Arduino开发实例-Arduino的SPI详解

Arduino的SPI详解

文章目录

Arduino是一个流行的开源平台,用于开发电子项目。Arduino板基于微控制器,可以使用不同的外设和传感器执行各种任务。Arduino微控制器的一个重要特性是SPI。SPI是一种同步串行数据协议,用于微控制器与一个或多个外围设备之间快速地进行短距离通信。

在本文中,我们将介绍Arduino SPI的概念,解释它的工作原理,并展示一些在不同场景中使用Arduino SPI的例子。

1、什么是Arduino SPI?

SPI是一种同步串行数据协议,用于微控制器与一个或多个外围设备之间快速地进行短距离通信。SPI通常由一个控制器设备(通常是一个微控制器)和一个或多个外围设备组成。SPI通常有三条线路对所有设备共用:

  • CIPO(控制器输入外围输出)- 外围设备发送数据给控制器的线路
  • COPI(控制器输出外围输入)- 控制器发送数据给外围设备的线路
  • SCK(串行时钟)- 由控制器生成的同步数据传输的时钟脉冲

以及一条针对每个设备特定的线路:

  • CS(芯片选择)- 控制器可以用来使能和失能特定设备的引脚。当一个设备的芯片选择引脚为低电平时,它与控制器通信。当它为高电平时,它忽略控制器。这允许你有多个SPI设备共享同样的CIPO,COPI和SCK线路。

要为一个新的SPI设备编写代码,你需要注意以下几点:

  • 你的设备可以使用的最大SPI速度是多少?这由SPISettings中的第一个参数控制。如果你使用的芯片额定为15 MHz,就使用15000000。Arduino会自动使用等于或小于你在SPISettings中使用的数字的最佳速度。
  • 数据是先移入最高有效位(MSB)还是最低有效位(LSB)?这由SPISettings中的第二个参数控制,可以是MSBFIRST或LSBFIRST。大多数SPI芯片使用MSB优先的数据顺序。
  • 数据时钟在空闲时是高电平还是低电平?数据是在时钟脉冲的上升沿还是下降沿采样?这些模式由SPISettings中的第三个参数控制。SPI标准是松散的,每个设备都有一些不同的实现。这意味着你在编写代码时要特别注意设备的数据手册。一般来说,有四种传输模式。这些模式控制数据是在数据时钟信号的上升沿还是下降沿移入和移出(称为时钟相位),以及时钟在空闲时是高电平还是低电平(称为时钟极性)。

2、Arduino SPI的例子

为了演示Arduino SPI的使用,我们将展示两个例子。一个是Arduino作为控制器,与一个AT25HP512 Atmel串行EEPROM芯片通信。另一个是Arduino作为外围设备,与另一个Arduino通信。

2.1 例子1:Arduino与EEPROM通信

在这个例子中,我们将使用SPI协议来读写一个AT25HP512 Atmel串行EEPROM芯片。EEPROM芯片是一种非常有用的数据存储器件,可以在断电后保持数据不丢失。我们将使用以下硬件和软件:

  • AT25HP512串行EEPROM芯片(或类似的)
  • 跳线
  • 面包板
  • Arduino微控制器(链接到商店)
  • Arduino IDE(在线或离线)
2.1.1 硬件连接

我们需要将EEPROM芯片的引脚与Arduino板的相应引脚连接起来。下表列出了EEPROM芯片的引脚功能和对应的Arduino引脚:

EEPROM Pin Function Arduino Pin
1 CS 10
2 COPI 11
3 CIPO 12
4 GND GND
5 WP GND
6 HOLD VCC
7 SCK 13
8 VCC VCC

注意,我们需要将WP(写保护)引脚接地,以允许写入数据。我们也需要将HOLD(暂停)引脚接电源,以允许正常操作。

2.1.2 软件编程

我们需要使用SPI库来与EEPROM芯片通信。SPI库已经包含在每个Arduino核心/平台中,所以我们不需要额外安装它。我们只需要在代码中包含以下语句:

1
2
#include <SPI.h>

然后,我们需要定义一些常量和变量,例如EEPROM芯片的CS引脚,EEPROM芯片的指令集,以及用于存储数据的缓冲区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Define the CS pin for the EEPROM chip
const int csPin = 10;

// Define the EEPROM instructions
const byte WRITE_ENABLE = 0x06;
const byte WRITE_DISABLE = 0x04;
const byte READ_STATUS = 0x05;
const byte WRITE_STATUS = 0x01;
const byte READ_DATA = 0x03;
const byte WRITE_DATA = 0x02;

// Define the buffer size and the buffer array
const int bufferSize = 32;
byte buffer[bufferSize];


接下来,我们需要在setup()函数中初始化SPI通信,并设置CS引脚为输出:

1
2
3
4
5
6
7
8
9
10
11
12
void setup() {
// Initialize serial communication at baud rate of 9600 bps
Serial.begin(9600);

// Initialize SPI communication
SPI.begin();

// Set the CS pin as output
pinMode(csPin, OUTPUT);
}


然后,我们需要在loop()函数中执行以下步骤:

  • 写入一些数据到EEPROM芯片中的某个地址
  • 等待写入完成
  • 从EEPROM芯片中的同一个地址读取数据
  • 打印读取到的数据到串口监视器

为了实现这些步骤,我们需要定义一些辅助函数,例如:

  • writeEnable() - 发送写使能指令到EEPROM芯片,允许写入数据
  • writeDisable() - 发送写失能指令到EEPROM芯片,禁止写入数据
  • readStatus() - 读取EEPROM芯片的状态寄存器,返回一个字节
  • writeStatus() - 写入EEPROM芯片的状态寄存器,接受一个字节作为参数
  • readData() - 从EEPROM芯片中的某个地址读取一定数量的数据,接受地址和数量作为参数,将数据存储到缓冲区中
  • writeData() - 向EEPROM芯片中的某个地址写入一定数量的数据,接受地址和数量作为参数,从缓冲区中读取数据
  • printBuffer() - 打印缓冲区中的数据到串口监视器,接受数量作为参数

这些函数的实现如下:

1
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Send write enable instruction to the EEPROM chip
void writeEnable() {
// Pull CS pin low to enable communication
digitalWrite(csPin, LOW);

// Send write enable instruction
SPI.transfer(WRITE_ENABLE);

// Pull CS pin high to disable communication
digitalWrite(csPin, HIGH);
}

// Send write disable instruction to the EEPROM chip
void writeDisable() {
// Pull CS pin low to enable communication
digitalWrite(csPin, LOW);

// Send write disable instruction
SPI.transfer(WRITE_DISABLE);

// Pull CS pin high to disable communication
digitalWrite(csPin, HIGH);
}

// Read the status register of the EEPROM chip and return a byte
byte readStatus() {
// Declare a variable to store the status byte
byte status;

// Pull CS pin low to enable communication
digitalWrite(csPin, LOW);

// Send read status instruction
SPI.transfer(READ_STATUS);

// Receive the status byte
status = SPI.transfer(0x00);

// Pull CS pin high to disable communication
digitalWrite(csPin, HIGH);

// Return the status byte
return status;
}

// Write a byte to the status register of the EEPROM chip
void writeStatus(byte status) {
// Pull CS pin low to enable communication
digitalWrite(csPin, LOW);

// Send write status instruction
SPI.transfer(WRITE_STATUS);

// Send the status byte
SPI.transfer(status);

// Pull CS pin high to disable communication
digitalWrite(csPin, HIGH);
}

// Read a certain amount of data from a certain address of the EEPROM chip and store it in the buffer array
void readData(int address, int amount) {
// Pull CS pin low to enable communication
digitalWrite(csPin, LOW);

// Send read data instruction
SPI.transfer(READ_DATA);

// Send the high byte of the address
SPI.transfer(highByte(address));

// Send the low byte of the address
SPI.transfer(lowByte(address));

// Receive the data bytes and store them in the buffer array
for (int i = 0; i < amount; i++) {
buffer[i] = SPI.transfer(0x00);
}

// Pull CS pin high to disable communication
digitalWrite(csPin, HIGH);
}

// Write a certain amount of data from the buffer array to a certain address of the EEPROM chip
void writeData(int address, int amount) {
// Pull CS pin low to enable communication
digitalWrite(csPin, LOW);

// Send write data instruction
SPI.transfer(WRITE_DATA);

// Send the high byte of the address
SPI.transfer(highByte(address));

// Send the low byte of the address
SPI.transfer(lowByte(address));

// Send the data bytes from the buffer array
for (int i = 0; i < amount; i++) {
SPI.transfer(buffer[i]);
}

// Pull CS pin high to disable communication
digitalWrite(csPin, HIGH);
}

// Print a certain amount of data from the buffer array to the serial monitor
void printBuffer(int amount) {
// Print an opening bracket
Serial.print("[");

// Print each data byte in hexadecimal format
for (int i = 0; i < amount; i++) {
Serial.print("0x");
if (buffer[i] < 16) {
Serial.print("0");
}
Serial.print(buffer[i], HEX);
if (i < amount -1) {
Serial.print(", ");
}
}

// Print a closing bracket and a new line
Serial.println("]");
}


最后,我们可以在loop()函数中使用这些辅助函数来实现我们的目标:

1
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void loop() {

// Write some data to address
writeEnable();
buffer[0] = 'H';
buffer[1] = 'e';
buffer[2] = ‘l’;
buffer[3] = ‘l’;
buffer[4] = ‘o’;
writeData(0x0000, 5);
writeDisable();

// Wait for the write to complete
while (readStatus() & 0x01) { // Do nothing, just wait
}

// Read the same data from address
readData(0x0000, 5);

// Print the data to the serial monitor
Serial.print("Data written: ");
printBuffer(5);

// Write some different data to address
writeEnable();
buffer[0] = ‘W’;
buffer[1] = ‘o’;
buffer[2] = ‘r’;
buffer[3] = ‘l’;
buffer[4] = ‘d’;
writeData(0x0000, 5);
writeDisable();

// Wait for the write to complete
while (readStatus() & 0x01) { // Do nothing, just wait
}

// Read the same data from address
readData(0x0000, 5);

// Print the data to the serial monitor
Serial.print("Data overwritten: ");
printBuffer(5);

// Wait for a second before repeating
delay(1000);
}

2.2 例子2:Arduino与Arduino通信

在这个例子中,我们将使用SPI协议来实现两个Arduino板之间的通信。一个Arduino板将作为控制器,另一个Arduino板将作为外围设备。我们将使用以下硬件和软件:

  • 两个Arduino微控制器(链接到商店)
  • 跳线
  • 面包板
  • Arduino IDE(在线或离线)
2.2.1 硬件连接

我们需要将两个Arduino板的引脚按照以下方式连接起来:

Controller Pin Peripheral Pin
10 10
11 11
12 12
13 13
GND GND

注意,我们需要将两个Arduino板的CS引脚连接在一起,以便控制器可以使能和失能外围设备。我们也需要将两个Arduino板的地线连接在一起,以便建立共同的参考电平。

2.2.2 软件编程

我们需要为控制器和外围设备分别编写不同的代码。对于控制器,我们需要使用SPI库来与外围设备通信。对于外围设备,我们需要使用SPI库的从机模式来响应控制器的请求。

对于控制器,我们需要在setup()函数中初始化SPI通信,并设置CS引脚为输出:

1
2
3
4
5
6
7
8
9
10
11
12
```c
void setup() {
// Initialize serial communication at baud rate of 9600 bps
Serial.begin(9600);

// Initialize SPI communication
SPI.begin();

// Set the CS pin as output
pinMode(csPin, OUTPUT);
}

然后,我们需要在loop()函数中执行以下步骤:

  • 发送一个字节到外围设备,表示要读取或写入的数据类型(例如温度或湿度)
  • 接收一个字节从外围设备,表示数据的值(例如25或50)
  • 打印接收到的数据到串口监视器

为了实现这些步骤,我们需要定义一些常量和变量,例如CS引脚,数据类型和数据值:

1
2
3
4
5
6
7
8
9
10
11
// Define the CS pin for the peripheral device
const int csPin = 10;

// Define the data types
const byte TEMPERATURE = 0x01;
const byte HUMIDITY = 0x02;

// Define a variable to store the data value
byte dataValue;


然后,我们可以在loop()函数中使用SPI.transfer()函数来发送和接收数据:

1
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
void loop() {
// Send the temperature data type to the peripheral device
SPI.transfer(TEMPERATURE);

// Receive the temperature data value from the peripheral device
dataValue = SPI.transfer(0x00);

// Print the temperature data to the serial monitor
Serial.print("Temperature: ");
Serial.print(dataValue);
Serial.println(" C");

// Wait for a second
delay(1000);

// Send the humidity data type to the peripheral device
SPI.transfer(HUMIDITY);

// Receive the humidity data value from the peripheral device
dataValue = SPI.transfer(0x00);

// Print the humidity data to the serial monitor
Serial.print("Humidity: ");
Serial.print(dataValue);
Serial.println(" %");

// Wait for a second
delay(1000);
}


对于外围设备,我们需要在setup()函数中初始化SPI通信,并设置为从机模式:

1
2
3
4
5
6
7
8
9
10
11
void setup() {
// Initialize SPI communication as slave
SPI.begin();
SPI.setSCK(13); // Set SCK pin to pin
SPI.setMISO(12); // Set MISO pin to pin
SPI.setMOSI(11); // Set MOSI pin to pin
SPI.setSS(10); // Set SS pin to pin
SPI.onData(receiveData); // Register a callback function to handle incoming data
}


然后,我们需要定义一个回调函数来处理控制器发送的数据,并根据数据类型返回相应的数据值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Define a callback function to handle incoming data
void receiveData(int byteCount) {
// Declare a variable to store the incoming data type
byte dataType;

// Read the incoming data type from the COPI line
dataType = SPI.transfer(0x00);

// Check the data type and send back the corresponding data value on the CIPO line
switch (dataType) {
case TEMPERATURE:
SPI.transfer(25); // Send a dummy temperature value of
break;
case HUMIDITY:
SPI.transfer(50); // Send a dummy humidity value of
break;
default:
SPI.transfer(0x00); // Send a zero value for unknown data type
break;
}
}


最后,我们可以在loop()函数中什么都不做,因为所有的通信都是由中断驱动的:

1
2
3
4
5
void loop() {
// Do nothing, communication is handled by interrupt
}


3、总结

在本文中,我们介绍了Arduino SPI的概念,解释了它的工作原理,并展示了一些在不同场景中使用Arduino SPI的例子。我们学习了SPI是一种同步串行数据协议,用于微控制器与一个或多个外围设备之间快速地进行短距离通信。我们也学习了Arduino板有不同类型和数量的SPI引脚,取决于它们使用的微控制器。我们还学习了我们可以使用SPI库来与SPI设备通信,但这需要一些对设备数据手册和SPI设置的注意。

我们希望这篇文章能帮助你理解Arduino SPI的基础知识和如何在你的项目中使用它。如果你有任何问题或建议,请随时在下面留言。祝你编程愉快!😊

文章来源: https://iotsmart.blog.csdn.net/article/details/131386277