Arduino SPI マスターとスレーブでデータ通信

シェアする

あけましておめでとうございます。

新年と言えばまずご挨拶から。

と言うことでArduino nanoとArduino UNO同士で通信し言葉を交わして操作してみます。

インターフェースはArduino nanoとArduino UNOどちらにもあるSPIで行います。
他にもいくつかありますが別途行うこととします。

テストの前にSPIとは

SPIとはSerial Peripheral Interfaceの略で3本または4本で構成される通信インターフェイスです。

過去記事でPCとArduinoとの間で使っていたのはUART(シリアル通信)で、これは送信線(TDx)と受信線(RDx)の2本だけでやり取りが出来ます。但し、お互い周波数やパリティなどが一致していないと正常にデータを送受信できません。

それに対してSPIはUARTと同じく送信線(MOSI:Master Out Slave In)と受信線(MISO:Master In Slave Out)にもう一本クロック(SCK)を追加することで周波数を可変させることができます。また、SPIには親(マスター)と子(スレーブ)の関係がありクロックを送信するのが親で、子は親からのクロックが来て初めてデータの送受信ができます。更に信号線(SS:Slave Select pin)を追加することで子の数を複数に増やすことも可能です。

ここ(TOSHIBA:SPIフォーマット)で信号の動きをシミュレーションできます。

用意するもの

  1. Arduino nano
  2. Arduino UNO
  3. ジャンバー
[amazonjs asin=”B01L5GW0KK” locale=”JP” title=”Rasbee Mini USB Nano V3.0 ATmega328P CH340G 5V 16M マイクロコントローラーボード Arduino用”] [amazonjs asin=”B06XF2HZGT” locale=”JP” title=”Elegoo UNO キット レベルアップ  チュートリアル付 uno mega2560 r3 nanoと互換 Arduino用”]

回路構成を考える(1)

はじめはマスター側の動作テストを行います。

MOSIとMISOをジャンパで折り返してデータが来ていることを確認し、次はジャンパを抜いてデータが来ないことを確認します。Atmega328PはD12がMISO、D11がMOSI。

回路

プログラミングする(1)

まずは、一台でのデータ折り返し確認。

/**********************************************
Arduino nano SPI(Master)
**********************************************/
#include <SPI.h>
#define SS_PIN 10
int inByte  = 0;
int SPIdata = 0;
int data    = 0;
byte MOSI_Byte = 0;
byte MISO_Byte = 0;
void setup() {
 Serial.begin(9600);
 while (!Serial)
 
 pinMode(SS_PIN,OUTPUT);
 SPI.setClockDivider(SPI_CLOCK_DIV128);  //fosc/128
 
 SPI.begin();    //SPI有効
 Serial.println("/-----START Arduino UNO-----/");
 Serial.println("/---------SPI Master--------/");
 Serial.println("/-------Default DIV128------/");
 delay(100);
}
void loop() {
 if(Serial.available() > 0) {
   inByte = Serial.read();
   /**************************************
     送信速度変更
     '1' = fosc / 2 ※スレーブ側対応しません
     '2' = fosc / 4
     '3' = fosc / 8
     '4' = fosc / 16
     '5' = fosc / 32
     '6' = fosc / 64
     '7' = fosc / 128
     上記以外はMOSI送信に使用
     **************************************/
   switch(inByte){
     case '1':
       SPI.setClockDivider(SPI_CLOCK_DIV2);
       Serial.println("DIV2");
       break;
      case '2':
       SPI.setClockDivider(SPI_CLOCK_DIV4);
       Serial.println("DIV4");
       break;
      case '3':
       SPI.setClockDivider(SPI_CLOCK_DIV8);
       Serial.println("DIV8");
       break;
      case '4':
       SPI.setClockDivider(SPI_CLOCK_DIV16);
       Serial.println("DIV16");
       break;
      case '5':
       SPI.setClockDivider(SPI_CLOCK_DIV32);
       Serial.println("DIV32");
       break;
      case '6':
       SPI.setClockDivider(SPI_CLOCK_DIV64);
       Serial.println("DIV64");
       break;
      case '7':
       SPI.setClockDivider(SPI_CLOCK_DIV128);
       Serial.println("DIV128");
       break;
      default:
       MOSI_Byte = inByte;
   }
 }
 if(MOSI_Byte){
   Serial.print("sent:");
   Serial.write(MOSI_Byte);
   
   digitalWrite(10, LOW);    //SS 有効
   MISO_Byte = SPI.transfer(MOSI_Byte);    //送受信処理
   digitalWrite(10, HIGH);   //SS 終了
   Serial.print(" : read:");
   Serial.write(MISO_Byte);
   Serial.println();
   MOSI_Byte = 0;
   MISO_Byte = 0;
 }
}

折り返し時の動作は、シリアル表示に「sent: X : read Y」とありXとYが一致していれば動作に問題なしです。試しにジャンパーを外すとYには何も表示されなくなります。

また通信速度が変えられるようにプログラムしました。

次にスレーブ側です。

回路構成を考える(2)

今度は2台ですので送信と受信の接続に注意してください。

回路2

UNO(マスター)     nano(スレーブ)

SS         :PIN 10 ————– PIN 10
MOSI  :PIN 11 ————– PIN 11
MISO  :PIN 12 ————– PIN 12
SCK     :PIN 13 ————– PIN 13

送受信線のクロスなどせずそのままピン番号に合わせて接続してください。

プログラミングする(2)

(1)では、マスター側を作成したので今度はスレーブ側となります。

/**********************************************
Arduino nano SPI(sleave)
**********************************************/
#include <SPI.h>
#define SS_PIN 10
#define SCK_PIN 13
#define MISO_PIN 12
#define MOSI_PIN 11
int inByte  = 0;
int SPIdata = 0;
int data    = 0;
byte MOSI_Byte = 0;
byte MISO_Byte = 0;
byte MISO_Byte_ = 0;
int spi_flag = 0;
void setup() {
 Serial.begin(9600);
 while (!Serial)
 
 pinMode(SS_PIN,INPUT);
 pinMode(SCK_PIN,INPUT);
 pinMode(MISO_PIN,OUTPUT);
 pinMode(MOSI_PIN,INPUT);
 SPCR &= ~_BV(4);    //スレーブ設定
 
 SPCR |= _BV(6);   //SPI有効
 SPI.attachInterrupt();    //SPI割り込み有効
 
 Serial.println("/-----START Arduino nano-----/");
 Serial.println("/----------SPI Slave---------/");
}
void loop() {
 if(spi_flag){
   Serial.print("read:");
   Serial.write(MOSI_Byte);
   Serial.print(" : sent:");
   Serial.write(MISO_Byte_);
   Serial.println();
   spi_flag = false;
 }
}
//SPI割り込み
ISR(SPI_STC_vect){
 MOSI_Byte = SPDR;   //受信データ
 MISO_Byte_ = MISO_Byte;   //表示用
 MISO_Byte = MOSI_Byte;    //折り返し
   
 SPDR = MISO_Byte;   //送信データセット
 spi_flag = true;    //表示フラグ
}

スレーブ側のSPIはマスター側と違い突然データが送られてくるため、割り込みを使用しました。参考:https://gist.github.com/chrismeyersfsu/3317769

もっと柔軟なやり方がありそうですがこれで一応動作します。

動作としては、マスター側が送信したデータをスレーブはシリアルで「read : x :sent:y」と受信データを送信します。xは現在受信したデータ。yは前回受信したデータを送り返します。

通信速度の周波数について、ATmega328/Pのデータシートにも書かれているようにfosc/4より速いDIV2ではスレーブ側が正常にデータが受け取れず動作しません。

シリアル通信内容

2018-01-02

[amazonjs asin=”4800711460″ locale=”JP” title=”これ1冊でできる! Arduinoではじめる電子工作 超入門 改訂第2版”]