1台のarduinno nanoで割込みとSPIの同時使用

2021年8月13日

どもです。
先日手に入れたArduino nano(中華)で遊んでいます。
今回、この1台のArduino nanoで割込みとSPI通信を行ってみたので、その内容について書いてみます。

1. 作業環境と使用デバイス

割込みとSPIを行うので、割り込み信号を発生させるデバイスとSPI通信を行うデバイスをそれぞれ用意します。
(もちろん1台で全て完結させることもできる…かもしれませんが、今回は別デバイスで行っています。)
で、実際に用意した構成が下記。

arduino_spi_and_interrupt

各使用機材は、下表の通りです。

PC
HW CPU Intel(R) Core(TM) i7-3770L
3.5GHz
メモリ 16.0(FG)
SW OS Windows7
Professional SP1 (64bit)
arduino IDE Ver.1.8.9
Eclipse Ver.2019-12(4.10.0)
(Build:20181214-0600)
Adruino
Arduino Arduino nano(中華版)
RaspberryPi
HW Raspberry Pi 3 model B
OS Stretch
[2018-04-18-raspbian-stretch]

2. 何のために?

これまで書いてきたエントリで、ArduinoからRaspberryPi3に対してSPI通信でデータを送信する方法を書いてきました。
なので、次は組み込みでよく使う機能である「割込み」について調べてみることにしました。
なお、実際の開発現場では、「通信」と「割込み」は当然ながら1つのデバイスに実装されます。
Arduinoでそれを実現できるか、実現するためにはどうしたらいいか、ということを調べるためです。

3. 何をした?

Arduinoが2台手元(いや、実際にはもう数台ありますが…あ、どーでもいいですね)にありますので、一方で割込み発生用の信号を作ります。
もう一方のArduinoでは割込み発生回数をカウントし、かつSPI通信で割込みの発生回数をRaspberryPi3に送信します。
RaspberryPi3では、単純に受信した割込み回数を表示します。

4. やってみた

今回作成したコードですが、「キモ」になる割り込み/SPIの両方を実施するArduinoのもののみを載せます。
(他のコードは、これまで紹介してきたものとほとんど変わらない、あるいはとてもシンプルなので、割愛します。)

/**
 *  Arduinoで発生した割り込み回数を、SPIで送信する。
 *  ただし、Arduinoはスレーブとして動作する。
 *  マスターは、RaspberryPi3
 */
#include "Arduino.h"
#include <SPI.h>

#define INTERRUPT_PIN   (2)
#define SPI_BUFF_SIZE   (4)

int interruptCount;
int interruptCountPrev;
int spiCount;
int spiCountPrev;
byte spiBuffer[SPI_BUFF_SIZE];
int spiBufferIndex;
int isDataChanged;

/**
 * @brief   セットアップを実施する。
 */
void setup()
{
    //割込み設定
    pinMode(INTERRUPT_PIN, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), interrupHandle, RISING);

    //SPI設定:ピン
    pinMode((unsigned char)MISO, OUTPUT);
    pinMode((unsigned char)MOSI, INPUT);
    pinMode((unsigned char)SCK, INPUT);
    pinMode((unsigned char)SS, INPUT);

    //SPI設定:モード
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
    SPI.setClockDivider(SPI_CLOCK_DIV128);  //125kHz
    SPI.begin();

    //SPIレジスタ
    SPCR &= ~_BV(MSTR);
    SPCR |= _BV(SPE);
    SPI.attachInterrupt();

    //シリアル設定(デバッグ用)
    Serial.begin(9600);

    //変数の初期化
    spiCount = 0;
    spiCountPrev = 0;
    interruptCount = 0;
    interruptCountPrev = 0;
    SPDR = interruptCount;
    isDataChanged = 0;

    for (int index = 0;index < SPI_BUFF_SIZE;index++) {
        spiBuffer[index] = 0;
    }
}

/*
 * @brief   SPIの割込みハンドラ
 */
ISR(SPI_STC_vect) {
    SPDR = spiBuffer[spiBufferIndex];
    spiBufferIndex++;
    if (SPI_BUFF_SIZE <= spiBufferIndex) {
        //送信データのインデックスをクリアする
        spiBufferIndex = 0;
    }

    spiCount++;
}

/**
 * @brief   メインループ処理
 */
void loop()
{
    int interruptCountLatch = interruptCount;
    int spiCountLatch = spiCount;
    spiBuffer[0] = (byte)(interruptCountLatch & 0x00FF);
    spiBuffer[1] = (byte)((interruptCountLatch & 0xFF00) >> 8);
    spiBuffer[2] = (byte)(spiCountLatch & 0x00FF);
    spiBuffer[3] = (byte)((spiCountLatch & 0xFF00) >> 8);

    SPDR = spiBuffer[0];
    spiBufferIndex = 1;

    Serial.print("interruptCount = ");
    Serial.print(interruptCountLatch);
    Serial.print(", interruptCountPrev = ");
    Serial.print(interruptCountPrev);
    Serial.print(",  spiCount = ");
    Serial.print(spiCountLatch);
    Serial.print(",  spiCountPrev = ");
    Serial.print(spiCountPrev);
    Serial.print(",  isDataChanged = ");
    Serial.println(isDataChanged);

    interruptCountPrev = interruptCount;
    spiCountPrev = spiCount;

    delay(10);  //10msec待つ
}

/**
 * @brief   割込みハンドラ
 */
void interrupHandle()
{
    interruptCount++;
}

5. 何をしている?

「何をしている?」とは書いていますが、やっていることと言えば、これまでの記事で書いてきた内容を、ただただ結合しただけ、です。
ただ、1つだけ説明しておく箇所があるとすれば、それはloop()内の以下の処理です。

    SPDR = spiBuffer[0];
    spiBufferIndex = 1;

以前のSPI送受信サンプルでも書いたと思いますが、SPI通信が実行された際にバッファの先頭から順番に送信されるためには、SPIのデータピン(SPDR)に事前にデータをセットしておく必要があります。
上記コードは、そのための処理になります。
また、バッファのindexを1にセットしておくことで、SPI割り込みが発生した際に、SPDRにセットされたデータの次のデータが送信されます。

6. まとめ

今回のエントリでは、1台のArduinoで信号割込みとSPI通信(いや、SPIも割込みなんだけどね…)を同時に行うことに挑戦しました。
次回は、割込みを違う方法で発生させてみることに挑戦してみます。

ではっ!