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

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

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

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

インターフェースは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(どちらがマスターでもOK)
    Amazon:Kuman 40 in 1 Arduino用キット

    色々入ってて便利。
  3. ジャンバー

回路構成を考える(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

 

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中