Arduino同士のSPI通信で配列を送る

![SS 192.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/a41cd967-f549-8677-1efa-2e99bf69518d.png) 今度は実用性のあるやつをつくるぞ

## やること
[前回](https://qiita.com/Ninagawa123/items/bbbf7db33e75c3f2b32b
)の続きです。
Arudino UNO同士でSPI通信を行います。
１台をメイン（マスター）、もう１台をサブ（スレーブ）とします。

ArduinoはSPI通信を標準で備えていますが、サブとして使うライブラリが公式にはありません。
必要な設定については、[前回](https://qiita.com/Ninagawa123/items/bbbf7db33e75c3f2b32b)の記事で探りました。

今回は少し実用的な使い方に挑戦してみます。

## 準備物・実施環境
- Arduino UNO 2個
- Arduino IDE

## 機能
- 50Hz(20ms毎)にデータを送信します。
- サブからShort型の配列データ（長さは５）をメインに送信します。
- データはシリアルモニタから好きなタイミングで書き換え可能とします。
- 送受信のズレが発生しても次の送信では是正されるようにします。
- チェックサムでデータの品質を保ちます。

## とりあえず動かすスケッチ
これでArduinoをSPIサブとして使えるはずです。
Short型4個、Byte型で8個の連続したデータが扱えるので、ちょっとしたアナログのセンサーデータなどを複数取りまとめて中継用するSPIサブとしてもすぐに応用できると思います。

### メイン側の Arduino UNO（SPI通信のきっかけを発信する側）
```JAVA:
/* Arduino uno SPI-Main */
#include <SPI.h>

#define PAD_B_INDEX 10 // 送受信配列のサイズ（Short型）
#define PAD_S_INDEX 5  // 送受信配列のサイズ（Byte型）
#define SPI_STARTBIT 0xFF// フレーム開始シグナル
#define SPI_INTERVAL 10  // 通信安定用のインターバル（マイクロ秒）

typedef union {
  int8_t bval[PAD_B_INDEX];
  short sval[PAD_S_INDEX];
} UnionPad;
UnionPad pad_buff;
UnionPad pad_rcvd;

//SPIの通信速度とモードの設定 Arduino UNO同士は4Mなら安定
SPISettings mySPISettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);

void setup() {
  Serial.begin(115200);
  Serial.println("\nBegin SPI Main.");
  pinMode(SS, OUTPUT);
  digitalWrite(SS, HIGH); // SSを非アクティブにする
  SPI.begin(); // SPIの開始
  delay(10);
}

void loop() {
  SPI.beginTransaction(mySPISettings);//SPI通信を開始
  digitalWrite(SS, LOW); // SSをアクティブにする
  SPI.transfer(SPI_STARTBIT);// フレーム開始シグナルを送信
  delayMicroseconds(SPI_INTERVAL);  // 通信安定用ディレイ
  SPI.transfer(0x00); // ダミーデータを受信
  delayMicroseconds(SPI_INTERVAL);  // 少し待つ
  for (int i = 0; i < PAD_B_INDEX; i++) {
    pad_buff.bval[i] = SPI.transfer(0x00); // ダミーデータを送信して本データを受信
    delayMicroseconds(SPI_INTERVAL);  // 通信安定用ディレイ
  }
  digitalWrite(SS, HIGH); // SSを非アクティブにして通信を終了
  SPI.endTransaction();

  /*受信データ（バイト型）の表示*/
  Serial.print("Raw :");
  for (int i = 0; i < PAD_B_INDEX; i++) {
    Serial.print(pad_buff.bval[i], DEC);
    if (i < PAD_B_INDEX - 1) {
      Serial.print("/");
    }
  }
  Serial.println();

  /*チェックサムと合格データ（ショート型）の表示*/
  if (cksm_rslt(pad_buff.sval, PAD_S_INDEX)) {
    Serial.print("Rcvd:");
    memcpy(pad_rcvd.bval, pad_buff.bval, PAD_B_INDEX);
  } else {
    Serial.print("*NG*:");
  }
  for (int i = 0; i < PAD_S_INDEX; i++) {//受信失敗なら前回データを表示
    Serial.print(pad_rcvd.sval[i], DEC);
    if (i < PAD_S_INDEX - 1) {
      Serial.print("/");
    }
  }
  
  Serial.println();
  Serial.println("--");
  delay(50); // 次のデータリクエストまで待つ
}

/*チェックサム*/
bool cksm_rslt(short arr[], int len) 
{
  int _cksm = 0;
  for (int i = 0; i < len - 1; i++)
  {
    _cksm += int(arr[i]);
  }
  if (short(~_cksm) == arr[len - 1])
  {
    return true;
  }
  return false;
}
```
いくつかの工夫点がありました。
・フレーム開始シグナルを送信し、サブ側のタイミングを整える。
・受信したダミーデータを捨てる。
・通信速度を指定する。UNOは4Mで安定。
・通信安定のためデータ送信の間にディレイをちょい足しする。
・共用体を使うことでByte型、Short型の変換を容易に。

<br>

### サブ側の Arduino UNO（SPI通信のきっかけを待ち受けする側）

```JAVA:
/* Arduino uno SPI-Sub */
#include <SPI.h>
#define PAD_B_INDEX 10 // 送受信配列のサイズ（Short型）
#define PAD_S_INDEX 5  // 送受信配列のサイズ（Byte型）
#define SPI_STARTBIT 0xFF  // フレーム開始シグナル

typedef union {
  short sval[PAD_S_INDEX];
  int8_t bval[PAD_B_INDEX];
} UnionPad;
UnionPad pad_spi = {.bval = {11, 22, 33, 44, 55, 66, 77, 88, 127, 127}}; // ui64valを使用して全体を0で初期化

volatile int inputDataIndex = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("\nBegin SPI Sub.");
  Serial.println("Please enter a number between -128 and 127.");
  pinMode(MISO, OUTPUT);    // MISOピンを出力に設定
  pinMode(SS, INPUT_PULLUP); // SSピンを入力プルアップに設定
  SPCR |= bit(SPE) | bit(SPIE); // SPIをスレーブとして有効にし、割り込みを有効にする
}

void loop() {
  int inputData = 0;
  String inputString = "";

  /*シリアルからの入力受付*/
  if (Serial.available() > 0) {
    inputString = Serial.readStringUntil('\n');
    inputData = inputString.toInt();
    if (inputData >= -128 && inputData <= 127) {
      // 受け取った数値を表示します。
      Serial.print("RcvdNumber: ");
      Serial.println(inputData);
      pad_spi.bval[random(8)] = byte(inputData);//入力データを配列のどこかにランダムに挿入
    } else {
      Serial.println("Number out of range.");
    }
  }
  pad_spi.sval[PAD_S_INDEX - 1] = cksm_val(pad_spi.sval, PAD_S_INDEX);//末尾にチェックサムを挿入
  delay(1);
}

/*SPI送受信割り込み*/
ISR(SPI_STC_vect) {
  byte received = SPDR;  // マスターからのデータを受信
  if (received == SPI_STARTBIT) {
    inputDataIndex = 0;  // フレーム開始シグナルを受け取ったらインデックスをリセット
  } else {
    if (inputDataIndex < PAD_B_INDEX) {
      SPDR = pad_spi.bval[inputDataIndex];  // 次のデータを送信バッファに設定
      inputDataIndex++;
    } else {
      inputDataIndex = 0;
    }
  }
}

/*チェックサムデータの作成*/
short cksm_val(short arr[], int len)
{
  int _cksm = 0;
  for (int i = 0; i < len - 1; i++)
  {
    _cksm += int(arr[i]);
  }
  return short(~_cksm);
}
```
こちらはあまり工夫がありません。
シリアルモニタからのインプットで値がランダムなインデックスに入るようにしています。

## 接続

|Arduinoメイン|機能|Arduinoサブ|
|:--:|:-:|:--:|
|5v||5v|
|GND||GND|
|10|SS|10|
|11|MOSI(COPI)|11|
|12|MISO(CIPO)|12|
|13|SCK|13|



## 実行結果
接続すると自動的にSPIの送受信が始まります。


#### メイン側
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/01c6d31a-099c-ea18-698e-5d2cc60f46a4.png" width =320>

Rawはバイト型のローデータ、Rcvdは５要素のショート型で、５番目のデータはチェックになります。
<br>

#### サブ側
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/f4477d45-3f1e-92b7-62f8-20c9953d220a.png" width =360>

サブ側のシリアルモニタに-128〜127を入力することで、ランダムなインデックスにその値が入り、メイン側に渡されます。
<br>

## 課題
現状は動いていますが、高速化や、データの書き換えタイミングなどについては、もうチューニングが必要になりそうです。

## 次回
これで鬼門のArduinoによるSPI通信サブ側もなんとか動くようになりました。
次回はESP32でこれが動くように調整してみたいと思います。

## 前回記事
https://qiita.com/Ninagawa123/items/bbbf7db33e75c3f2b32b

いろいろ違っていたらすみません。ご指摘ください。