ArduinoでSPI通信を行う方法

投稿者: | ↻ : 2019年1月28日

はじめに

ArduinoでSPI(Serial Peripheral Interface)通信を行うために必要な設定と、手順を調べた結果をまとめます。ボードごとに違いがあるので、Arduino UNO R3を対象にします。

SPIの詳細ついては、wikipedia – シリアル・ペリフェラル・インタフェースを参照。

配線

ArduinoのSPIライブラリを利用する場合、Arduino側のSPIに使われるピンはボードごとに決まっていて、SS(SlaveSelect)以外は変更できません。制御するデバイス側のピンは、製品ごとに決められているのでデータシートに従います。

Arduino側のデジタル10番がSS、デジタル11番がMOSI(Master Out Slave In)、デジタル12番がMISO(Master In Slave Out)、デジタル13番がSCK(Serial Clock)になります。SPI対応デバイス側からの送信がなければ、MISOは未接続でも動作します。

それぞれのピンの役割は、MISOがスレーブからマスターへの送信、MOSIがマスターからスレーブへの送信、SCKがマスターとスレーブを同期させるためのクロック信号になります。この3本の信号線には複数のSPI対応デバイスを接続でき、どのデバイスと通信するかをSSにより決定します。SSはHIGH/LOWの極性だけなので、デバイスごとに1本必要となります。また、同時に2つ以上のデバイスを選択できません。

SPI通信に必要な3つの設定

概要

詳細は「Arduino 日本語リファレンス – SPIの概要」に記載されています。ただし、設定するための方法が新しくなっているので原文の確認も必要です。

SPI.setBitOrder、SPI.setDataMode、SPI.setClockDividerの3つがSPISettingsクラスで設定する方法に変わっています。

ビットオーダー

最上位ビット(MSBFIRST)と最下位ビット(LSBFIRST)の、どちらから転送を行うか?

転送モード

クロック極性とクロック位相の組み合わせで4つのモードがあります。

クロック極性とは、アイドリング状態を示すクロックはHIGHかLOWか?

クロック位相とは、サンプリングがクロックの立ち上がりエッジが立ち下がりエッジか?

SPIの動作スピード

同期させるためのクロック信号の周波数を設定します。

説明

ビットオーダー

制御するデバイスごとに最上位ビット、最上位ビットのどちらから転送を行うか決まっているので、デバイスの仕様に合わせて決定します。

  • 最上位ビットから転送する場合は、MSBFIRST
  • 最下位ビットから転送する場合は、LSBFIRST

転送モード

SPI通信を行っていない間、クロック信号はアイドリング状態でHIGHかLOWに固定されます。このアイドリング状態の極性が、クロック極性です。

SPI通信はクロック信号に合わせて1ビットつづ転送が行われます。クロック位相は、クロック信号のどのタイミングで転送を行うかを決めます。調べていると「立ち上がり、立ち下がりのエッジ」との説明が多いのですが分かりにくいです。

クロック信号は、アイドリング状態の極性から反対の極性に変化し、またアイドリング状態の極性に戻り、次の変化までが1つのクロックとなります。クロック位相は、クロックの始めの変化(A)で転送するか、後の変化(B)で転送するかを決めます。

本職ではないのでフランクだけど感覚として、こんな感じです。

SPIの転送モードは0から3まで4つあり、各モードでクロック極性とクロック位相の組み合わせが決められています。転送モードは、制御するデバイスの仕様に合わせて決定します。

転送モード定数クロック極性
(CPOL:Clock Polarity)
クロック位相
(CPHA:Clock Phase)
0SPI_MODE0LOW(A)で送受信
1SPI_MODE1LOW(B)で送受信
2SPI_MODE2HIGH(A)で送受信
3SPI_MODE3HIGH(B)で送受信

SPIの動作スピード

SPIの動作スピードは、指定された周波数以下の最大スピードが、SPIライブラリにより自動的に設定されます。

Arduino UNO R3はマイコン(ATmega328p)のハードウェアSPIを利用しています。

ATmega328pの仕様として、SPIクロック信号はシステムクロックを分周した周波数になります。選択できる分周比は1/2、1/4、1/8/、1/16、1/32、1/64、1/128の7つです。最小の分周比が1/2なので、システムクロックが16MHzで動作しているArduino UNO R3では、設定できるSPIの最大動作スピードは、8MHzとなります。

ATmega328-328P_Datasheetの「Table 23-5. Relationship between SCK and Oscillator Frequency」を参照。

SPI通信の周波数は規格で決まっていないので、Arduino側で生成できる周波数と、制御するデバイスの周波数が一致するとは限りません。なので、制御するデバイスの限界を超えないようにクロック信号の周波数を決定します。

SPI通信を行う手順

SPI通信に必要な設定を、ビットオーダー(MSBFIRST)、転送モード(SPI_MODE0)、SPIの動作スピード(8000000)として最小のコードを例にして手順をまとめます。

最小コード例

#include <SPI.h>

// 1回だけ実行させるためのフラグ
boolean isFirst = true;

// 1. SPIの設定を保持するSPISettingsクラスのインスタンスを生成する
SPISettings mySPISettings = SPISettings(8000000, MSBFIRST, SPI_MODE0);

void setup() {
  // 2. SPIに使用されるピンのpinModeなどを設定する
  SPI.begin();
}

void loop() {
  // 1回だけ実行させるための判定
  if (isFirst) {
    isFirst = false;

    // 3. 現在の設定を退避し、指定した設定をマイコンに反映させSPI通信を開始する
    SPI.beginTransaction(mySPISettings);

    // 4. 制御するデバイスに通信の開始を通知する
    digitalWrite(SS, LOW);
    
    // 5. 1バイトを送受信する
    SPI.transfer(0xaf);

    // 6. 制御するデバイスに通信の終了を通知する
    digitalWrite(SS, HIGH);

    // 7. SPI通信を終了し設定を以前の状態に戻す
    SPI.endTransaction();
  }
}

説明

  1. SPI通信に必要な設定は、SPISettingsクラスのインスタンスとして用意します。
  2. SPI.beginをによりSPI通信を有効にし、SPI通信に使用されるピン(SCK、MOSI、MISO、SS)の入出力設定が行われます。SPI.beginは一度だけ呼び出します。(SPI.endによりSPI通信を無効にしますが常にSPI通信を行う装置を作る場合、呼び出さなくても問題ありません)
  3. SPI通信を行う前にSPI.beginTransactionにより設定をマイコンに反映させます。
  4. SS(Slave Select)をLOWにして、制御するデバイスに通信の開始を通知します。
  5. SPI.transferにより1バイトを送信します。nバイト送信する場合は、複数回呼び出します。
  6. SS(Slave Select)をHIGHにして、制御するデバイスに通信の終了を通知します。
  7. SPI.endTransactionによりSPI通信を終了します。

2018.1.15 SSのHIGH/LOWが逆だったので訂正しました。

補足

SPI.beginTransactionを呼んだ後、SPI.endTransactionを呼び出すまで別のSPI通信を禁止します。

必ずSPI.endTransactionを呼び出し、SPI.beginTransactionからSPI.endTransactionまでの間隔を出来るだけ短くする必要があります。割り込みを利用して複数のデバイスとSPI通信を行う場合に注意が必要です。

SPI.transferは1バイトの送信が完了するまで処理がブロックします。

複数のデバイスを使用せず電源を切るまでSPI通信をオフにしないならば、SPI.beginTransactionsetup()内で1回だけ呼び出せば利用できます。

#include <SPI.h>

boolean isFirst = true;

void setup()
{
  SPI.begin();
  SPI.beginTransaction(SPISetting(8000000, MSBFIRST, SPI_MODE0));
}

void loop()
{
  if (isFirst) {
    isFirst = false;
    digitalWrite(SS, LOW);
    SPI.transfer(0x12);
    digitalWrite(SS, HIGH);
  }
}

まとめ

ArduinoのSPIライブラリは、AVRマイコンのハードウェアSPIを利用する薄い実装で、SPI通信のMasterとしての動作にしか対応していませんでした。

よく理解していなかったSPI自体の仕様も掴めたので、SPI対応のデバイスを制御する際の障害に迷わされずに済みそうです。