文章目录
转载自: https://www.yiboard.com/thread-1040-1-1.html
微控制器使用许多不同的协议与各种传感器和外围设备进行通信。有许多用于无线和有线通信的协议,并且最常用的通信技术是串行通信。串行通信是通过通信信道或总线一次一位地发送数据的过程。有许多类型的串行通信,如UART、CAN、USB、I2C和SPI通信。
在本篇文章中,我们将了解SPI协议以及如何在Arduino中使用它。我们将使用SPI协议在两个Arduino开发板之间进行通信。在这里,一个Arduino开发板将充当主机Master,另一个将充当从机Slave,分别有两个LED和按钮连接到两个arduino开发板。为了演示SPI通信,我们将使用SPI串行通信协议通过从站侧的按钮控制主机的LED,反之亦然。
1. 什么是SPI?
SPI(串行外设接口)是一种串行通信协议。摩托罗拉在1970年发明了SPI接口。SPI具有全双工连接,这意味着数据可以同时发送和接收。即主设备可以将数据发送到从设备,从设备可以同时向主设备发送数据。 SPI是同步串行通信意味着通信需要时钟。
2. SPI的工作过程
SPI使用四条线进行主/从通信。 SPI只能有一个主站,并且可以有多个从站。主设备通常是微控制器,从设备可以是微控制器、传感器、ADC、DAC、LCD等。
· SPI主机带单个从机的框图表示。
SPI有四条线MISO、MOSI、SS和CLK
-
MISO(主进从出) - 用于向主设备发送数据的从设备线。
-
MOSI(主出从入) - 用于向外设发送数据的主线。
-
SCK(串行时钟) - 同步主机产生的数据传输的时钟脉冲。
-
SS(从机选择)-Master可以使用此引脚来启用和禁用特定设备。
· SPI主站带多个从站的框图
要启动主机和从机之间的通信,我们需要将所需设备的从机选择(SS)引脚设置为低电平,以便它可以与主机通信。当该引脚为高电平时,它会忽略主机。这允许您让多个SPI设备共享相同的MISO、MOSI和CLK主线。如上图所示,有四个从器件,其中SCLK、MISO,MOSI与主器件共用,每个器件的SS分别连接到主器件的各个SS引脚(SS1、SS2、SS3)。通过将所需的SS引脚设置为低电平,主器件可以与该从器件通信。
3. Arduino UNO中的SPI引脚
下图显示了Arduino UNO中的SPI引脚(红色框中)。
SPI线 | Arduino引脚 |
---|---|
MOSI | 11或ICSP-4 |
MISO | 12或ICSP-1 |
SCK | 13或ICSP-3 |
SS | 10 |
4. 在Arduino中使用SPI
在开始编程两个Arduinos之间的SPI通信之前。我们需要了解Arduino IDE中使用的SPI库。
库<SPI.h>包含在程序中,用于使用以下SPI通信函数。
1. SPI.begin()
用途:通过将SCK、MOSI和SS设置为输出来初始化SPI总线,将SCK和MOSI拉低,SS为高电平。
2. SPI.setClockDivider(divider)
用途:设置相对于系统时钟的SPI时钟分频器。可用的分频器为2,4,8,16,32,64或128。
divider参数:
- SPI_CLOCK_DIV2
- SPI_CLOCK_DIV4
- SPI_CLOCK_DIV8
- SPI_CLOCK_DIV16
- SPI_CLOCK_DIV32
- SPI_CLOCK_DIV64
- SPI_CLOCK_DIV128
3. SPI.attachInterrupt(handler)
用途:当从设备从主设备接收数据时,将调用此函数。
4. SPI.transfer(val)
用途:此函数用于在主站和从站之间同时发送和接收数据。
现在让我们从Arduino中SPI协议的实际演示开始。在本篇文章中,我们将使用两个arduino中的一个作为master,另一个作为slave。两个Arduino都分别带有一个LED和一个按钮。主LED可以通过使用从机Arduino的按钮来控制,从机Arduino的LED可以通过主Arduino的按钮使用arduino中的SPI通信协议来控制。
5. 需要的组件
● Arduino UNO开发板
● LED指示灯
● 按钮
● 电阻10k
● 电阻2.2k
● 面包板
● 连接导线
· Arduino SPI通信电路图
6. 编程说明
本教程有两个程序,一个用于主arduino,另一个用于从机arduino。在本文的末尾处,提供了两个Arduino开发板的完整程序。
· 主机Arduino编程介绍
- 首先,我们需要包含SPI库以使用SPI通信函数。
#include<SPI.h>
- 在void setup()函数中,我们以波特率115200启动串行通信。
Serial.begin(115200);
将LED连接至引脚7,将按钮连接至引脚2,并分别设置这些引脚OUTPUT和INPUT。
pinMode(ipbutton,INPUT);
pinMode(LED,OUTPUT);
接下来我们开始SPI通信
SPI.begin();
接下来,我们设置Clockdivider进行SPI通信。这里我们设置了分频系数8。
SPI.setClockDivider(SPI_CLOCK_DIV8);
然后将SS引脚置为高电平,因为我们没有启动任何传输到从机arduino。
digitalWrite(SS,HIGH);
- 在void loop()函数中:
我们读取了连接到pin2(Master Arduino)的按钮引脚的状态,用于将这些值发送到从Arduino。
buttonvalue = digitalRead(ipbutton);
根据引脚2的输入设置逻辑以设置x值(发送到从机)
if(buttonvalue == HIGH)
{
x = 1;
}
else
{
x = 0;
}
在发送值之前,我们需要将从机选择值设置为LOW以开始从主设备传输到从设备。
digitalWrite(SS, LOW);
以下是重要的一步,在下面的语句中,我们将存储在Mastersend变量中的按钮值发送到从机arduino,并从slave中接收将存储在Mastereceive变量中的值。
Mastereceive=SPI.transfer(Mastersend);
之后,根据Mastereceive值,我们将点亮或熄灭主机Arduino上的LED灯。
if(Mastereceive == 1)
{
digitalWrite(LED,HIGH); //Sets pin 7 HIGH
Serial.println("Master LED ON");
}
else
{
digitalWrite(LED,LOW); //Sets pin 7 LOW
Serial.println("Master LED OFF");
}
注意:我们使用serial.println()在Arduino IDE的串行监视器中查看结果。
· 从机Arduino编程代码介绍
- 首先,我们需要包含SPI库以使用SPI通信函数。
#include<SPI.h>
- 在void setup()函数中,我们以波特率115200启动串行通信。
Serial.begin(115200);
将LED连接至引脚7,将按钮连接至引脚2,并分别设置这些引脚OUTPUT和INPUT。
pinMode(ipbutton,INPUT);
pinMode(LED,OUTPUT);
这里重要的一步是
pinMode(MISO,OUTPUT);
以上语句将MISO设置为OUTPUT(必须将数据发送到主IN)。所以数据是通过Slave Arduino的MISO发送的。
现在使用SPI控制寄存器在从模式下打开SPI
SPCR |= _BV(SPE);
然后打开SPI中断的中断。如果从主站接收数据,则调用中断例程,并从SPDR(SPI数据寄存器)获取接收值
SPI.attachInterrupt();
来自master的值取自SPDR并存储在Slavereceived变量中。这发生在以下中断例程函数中。
ISR (SPI_STC_vect)
{
Slavereceived = SPDR;
received = true;
}
- 接下来在void loop()函数中,我们根据Slavereceived值将从机arduino上的LED灯设置为ON或OFF。
if (Slavereceived==1)
{
digitalWrite(LEDpin,HIGH); //Sets pin 7 as HIGH LED ON
Serial.println("Slave LED ON");
}
else
{
digitalWrite(LEDpin,LOW); //Sets pin 7 as LOW LED OFF
Serial.println("Slave LED OFF");
}
接下来,我们读取从机Arduino按钮的状态,并将值存储在Slavesend中,通过给SPDR寄存器赋值将值发送给主机Arduino。
buttonvalue = digitalRead(buttonpin);
if (buttonvalue == HIGH)
{
x=1;
}
else
{
x=0;
}
Slavesend=x;
SPDR = Slavesend;
注意:我们使用serial.println()在Arduino IDE的串行监视器中查看结果。
7. 测试硬件
8. 完整代码
Master Arduino Code:
//SPI MASTER (ARDUINO)
//SPI COMMUNICATION BETWEEN TWO ARDUINO
//CIRCUIT DIGEST
#include<SPI.h> //Library for SPI
#define LED 7
#define ipbutton 2
int buttonvalue;
int x;
void setup (void)
{
Serial.begin(115200); //Starts Serial Communication at Baud Rate 115200
pinMode(ipbutton,INPUT); //Sets pin 2 as input
pinMode(LED,OUTPUT); //Sets pin 7 as Output
SPI.begin(); //Begins the SPI commnuication
SPI.setClockDivider(SPI_CLOCK_DIV8); //Sets clock for SPI communication at 8 (16/8=2Mhz)
digitalWrite(SS,HIGH); // Setting SlaveSelect as HIGH (So master doesnt connnect with slave)
}
void loop(void)
{
byte Mastersend,Mastereceive;
buttonvalue = digitalRead(ipbutton); //Reads the status of the pin 2
if(buttonvalue == HIGH) //Logic for Setting x value (To be sent to slave) depending upon input from pin 2
{
x = 1;
}
else
{
x = 0;
}
digitalWrite(SS, LOW); //Starts communication with Slave connected to master
Mastersend = x;
Mastereceive=SPI.transfer(Mastersend); //Send the mastersend value to slave also receives value from slave
if(Mastereceive == 1) //Logic for setting the LED output depending upon value received from slave
{
digitalWrite(LED,HIGH); //Sets pin 7 HIGH
Serial.println("Master LED ON");
}
else
{
digitalWrite(LED,LOW); //Sets pin 7 LOW
Serial.println("Master LED OFF");
}
delay(1000);
}
Slave Arduino Code:
//SPI SLAVE (ARDUINO)
//SPI COMMUNICATION BETWEEN TWO ARDUINO
//CIRCUIT DIGEST
//Pramoth.T
#include<SPI.h>
#define LEDpin 7
#define buttonpin 2
volatile boolean received;
volatile byte Slavereceived,Slavesend;
int buttonvalue;
int x;
void setup()
{
Serial.begin(115200);
pinMode(buttonpin,INPUT); // Setting pin 2 as INPUT
pinMode(LEDpin,OUTPUT); // Setting pin 7 as OUTPUT
pinMode(MISO,OUTPUT); //Sets MISO as OUTPUT (Have to Send data to Master IN
SPCR |= _BV(SPE); //Turn on SPI in Slave Mode
received = false;
SPI.attachInterrupt(); //Interuupt ON is set for SPI commnucation
}
ISR (SPI_STC_vect) //Inerrrput routine function
{
Slavereceived = SPDR; // Value received from master if store in variable slavereceived
received = true; //Sets received as True
}
void loop()
{ if(received) //Logic to SET LED ON OR OFF depending upon the value recerived from master
{
if (Slavereceived==1)
{
digitalWrite(LEDpin,HIGH); //Sets pin 7 as HIGH LED ON
Serial.println("Slave LED ON");
}else
{
digitalWrite(LEDpin,LOW); //Sets pin 7 as LOW LED OFF
Serial.println("Slave LED OFF");
}
buttonvalue = digitalRead(buttonpin); // Reads the status of the pin 2
if (buttonvalue == HIGH) //Logic to set the value of x to send to master
{
x=1;
}else
{
x=0;
}
Slavesend=x;
SPDR = Slavesend; //Sends the x value to master via SPDR
delay(1000);
}
}
9. 关于_BV()
BV=Bit Value.
If you want to change the state of bit 6 in a byte you can use _BV(6) which is is equivalent to 0x40. But a lot us prefer the completely STANDARD method and simply write (1<<6) for the same thing or more specifically (1<<
For example if I want to set bit 6 in PORTB I’d use:
PORTB |= (1 << PB6);
though I guess I could use:
PORTB |= _BV(6);
or
PORTB |= _BV(PB6);
But, like I say, personally I’d steer clear of _BV() as it is non standard and non portable. After all it is simply:
#define _BV(n) (1 << n)
http://forum.arduino.cc/index.php?topic=283916.0
I’ve discovered that the “_BV()” macro (located in “arduino-1.0.x/hardware/tools/avr/lib/avr/include/avr/sfr_defs.h”) doesn’t work if the bit value is 15 or higher.
The macro is simple:
Code: [Select]
#define _BV(bit) (1 << (bit))
My theory as to why it fails is that the value shifted (1) is by default an int (i.e. a signed 16 bit number which cannot be larger than +32767). To test my theory, I changed the macro to this…
Code: [Select]
#define _BV(bit) ((uint64_t) 1 << (bit))
…and ta-daa it works.
My questions are:
Will this still operate as before (i.e. expanded by the pre-processor and only be a value, not code)?
Should I use uint64_t, uint32_t or size_t?
Should I replace the macro with a bunch of pre-defines (like this):
Code: [Select]
#define _BV(0) 0x0000000000000001
#define _BV(1) 0x0000000000000002
#define _BV(2) 0x0000000000000004
// . . .
#define _BV(61) 0x2000000000000000
#define _BV(62) 0x4000000000000000
#define _BV(63) 0x8000000000000000
Do something else?
Leave it alone, as is with the flaw?
Any input will be appreciated. Thanks!
Use bit() instead.
In arduino.h:
#define bit(b) (1UL << (b))
_BV is part of Libc. The intended use is to create bitmasks for manipulating register values. The bitmask is 16 bits which is reasonable for the intended use.
bit is part of the Arduino interface. I assume the intended use is general purpose bit manipulation. The bitmask is 32 bits which seems reasonable to me for the intended use.
I want a bit mask for 8-bit register values. It seems like bit(N) or _BV(N) or 1<<N will work just fine. It’s a matter of preference I suppose, but _BV() seems awkward and not very intuitive to me. I like bit() better. Both bit() and _BV() suffer from being cloaked in a macro. What’s wrong with just writing 1<<N? It’s not longer than writing bit(N) or _BV(N). It seems obvious what it does.
I think I’ll go with 1<<N unless there’s a compelling argument to use one of the macros.