如何在两个Arduino开发板之间使用SPI总线进行通信


转载自: 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引脚
MOSI11或ICSP-4
MISO12或ICSP-1
SCK13或ICSP-3
SS10

在这里插入图片描述

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编程介绍

  1. 首先,我们需要包含SPI库以使用SPI通信函数。
#include<SPI.h>                             
  1. 在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);     
  1. 在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编程代码介绍

  1. 首先,我们需要包含SPI库以使用SPI通信函数。
#include<SPI.h>
  1. 在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;                       
}
  1. 接下来在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.

好的,这里是一个基本示例程序,可以使用Arduino开发板控制SSD1315显示"Hello": ``` #include <SPI.h> #define SSD1315_DC_PIN 9 #define SSD1315_CS_PIN 10 SPISettings spiSettings(1000000, MSBFIRST, SPI_MODE0); void setup() { // 初始化SPI总线 SPI.begin(); pinMode(SSD1315_DC_PIN, OUTPUT); pinMode(SSD1315_CS_PIN, OUTPUT); // 初始化SSD1315 digitalWrite(SSD1315_CS_PIN, LOW); digitalWrite(SSD1315_DC_PIN, LOW); SPI.beginTransaction(spiSettings); SPI.transfer(0xAE); // 关闭屏幕 SPI.transfer(0xD5); // 设置显示时钟分频 SPI.transfer(0x80); // 默认值 SPI.transfer(0xA8); // 设置COM扫描方向 SPI.transfer(0x3F); // 默认值 SPI.transfer(0xD3); // 设置显示偏移 SPI.transfer(0x00); // 默认值 SPI.transfer(0x40); // 设置起始行 SPI.transfer(0xA0); // 设置列地址 SPI.transfer(0xC0); // 设置扫描方向 SPI.transfer(0xDA); // 设置COM引脚硬件配置 SPI.transfer(0x12); // 默认值 SPI.transfer(0x81); // 设置对比度 SPI.transfer(0x7F); // 默认值 SPI.transfer(0xA4); // 关闭全局显示 SPI.transfer(0xA6); // 设置正/反显示 SPI.transfer(0xAF); // 打开屏幕 SPI.endTransaction(); } void loop() { digitalWrite(SSD1315_CS_PIN, LOW); digitalWrite(SSD1315_DC_PIN, LOW); // DC低电平表示写命令 SPI.beginTransaction(spiSettings); SPI.transfer(0x21); // 设置列地址范围 SPI.transfer(0); // 开始列 SPI.transfer(127); // 结束列 SPI.transfer(0x22); // 设置页地址范围 SPI.transfer(0); // 开始页 SPI.transfer(3); // 结束页 digitalWrite(SSD1315_DC_PIN, HIGH); // DC高电平表示写数据 SPI.transfer(0xFF); // "H" SPI.transfer(0x81); // "e" SPI.transfer(0x81); // "l" SPI.transfer(0x87); // "l" SPI.transfer(0x87); // "o" SPI.transfer(0x00); // 空白像素 SPI.transfer(0x00); // 空白像素 SPI.transfer(0x00); // 空白像素 digitalWrite(SSD1315_CS_PIN, HIGH); SPI.endTransaction(); delay(1000); // 等待1秒钟 } ``` 这个程序使用SPI接口将数据发送到SSD1315,并且使用ArduinoSPI库来进行SPI通信。它将"Hello"的像素数据写入到SSD1315的内存中,并且通过设置列地址和页地址范围来将数据显示在屏幕上。 请注意,在这个示例程序中,我们假设你将SSD1315的DC引脚连接到了Arduino的数字引脚9上,而将CS引脚连接到了数字引脚10上。如果你的引脚连接不同,请相应地修改程序。 希望这个示例程序能够帮助你开始使用SSD1315!
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值