今回は2つのArduino間でSPIによるデータ送受信プログラムを紹介します。前回紹介したI2Cと同様に、クロック信号を持ち、シリアル通信と比べてノイズに強い通信方式です。ArduinoでSPI通信をするためのポートやライブラリの使用方法などについて説明します。
SPI通信の概要
マイコンレベルのデータ通信インタフェースとして、SPI(Serial Peripherial Interface)は1987年に開発されました。I2Cと同様に、コンポーネントはマスターかスレーブの役割を持ちます。マスターはクロック信号(シリアルクロック-SCL)を生成します。このクロックのタイミングに合わせてデータ通信を行います。ただし、I2Cバスとは異なり、データの送受信それぞれを専用の通信線で行います。マスターからスレーブの方向の通信線MOSI(Master Out, Slave In)と、スレーブからマスター方向の通信線MISO(Master In, Slave Out)があります。
SPI通信ではスレーブはアドレスを持たず、個別の物理的なアドレスピンを介して、マスターが通信する相手となるスレーブを指定します(スレーブ選択-SSピン)。ごくまれに、複数のスレーブのスレーブ選択ピンが一つに(カスケード)接続される場合もあります。
マスターは、通信したいスレーブのSSピンを使用して通信に参加させます。他のすべてのスレーブは、通信相手として指定されていなければマスターから届いた信号を無視するようにふるまいます。しかし、カスケード接続ではすべてのスレーブが常にアクティブで、データの受け渡しを行います。
図1の「SS」という文字の上に横線がひいてありますが、これは負論理であることを示しています。つまり、電圧レベルがHiなら非アクティブ(通信しない)になり、電圧レベルがLoなら対応するスレーブがアクティブ(通信する)になります。
ArduinoのSPI通信ポート
SPIインタフェースについてもArduinoには専用のピンがありません。また、Arduinoではマイクロコントローラにブートローダーを書き込む際にもSPI通信を使用するため、基板上のICSPコネクタにもつながっています。 SPI通信を使用する際にはD11~D13と、SS信号線として使用するピンはデジタル出力・デジタル入力ピンとして使用できなくなります。
このような論理の逆転現象は、デジタルの世界ではたくさん見られます。その原因は、ゲート回路で構成された部品の構造にあります。これらはトランジスタ回路を使って、2進(電圧)信号でブール関数(AND、OR、NOTなど)を実現しています。このようなトランジスタ回路では負論理の回路とすることで正論理の場合よりも回路がよりシンプルになります。例えば、押しボタンを押すと入力はLow、押さないとHighになる場合、これも負論理です。
負論理は、信号がプルアップ抵抗によってHi(0)に保持され、バスにつながった参加者が信号をLo(1)に「プル」するバスシステムにおいて使用されることがあります。例として、自動車業界でよく使われているCANバスが挙げられます。
以前のI2C記事で取り上げた、2つのArduinoで可変抵抗の数値を送信する例を、I2CではなくSPIを使用して実現することもできます。
2つのArduinosが両方とも USB 経由で電源供給されている場合は、図の通りですが、片側だけ電源供給する場合は、Arduinos 間の 5V 同士を接続することで動作します。2つのArduinoをつなぐ黒線のGND接続は、信号伝送のためのグランドとして必要です。I2Cの時と同様の動作となり、可変抵抗を回すことでLEDの明るさを調整できます。通信インタフェースがSPIになったので、スケッチも変更します。
マスター側
まず、マスター側(図3の左側)のArduinoに以下のスケッチを書き込みます。
#include <SPI.h> // (1) void setup(){ SPI.begin(); // (2) digitalWrite(SS, HIGH); // (3) } int knob = 0; void loop(){ knob = analogRead(0); knob /= 4; digitalWrite(SS, LOW); // (4) SPI.transfer(knob); // (5) digitalWrite(SS, HIGH); // (6) delay(500); }
スケッチの内容の解説
(1) I2Cの時と同様に、SPI通信に必要な機能を提供するライブラリを読み込みます。
(2) 関数「SPI.begin()」はSPI通信の動作を開始させる命令です。
(3) まず、SSラインにHighを出力して、スレーブが非アクティブになるように(通信しない状態になるように)します。スケッチ内では「SS」という定数を定義していませんが、SPIライブラリの中で定数「SS」=10と定義されています。また、digitalWrite()関数の前にpinMode()関数で出入力設定をしていませんが、これもSPI.begin()関数の中で処理されているのでここでは不要なのです。
(4) SSラインの信号をLoに落とすことで、D10ピンをSSピンとするスレーブが通信可能な状態(アクティブ)になります。
(5) 「SPI.transfer()」関数を実行し、int型の変数knobの値をSPIで送信します。
(6) D10ピンをSSピンとするスレーブとの通信を終了(非アクティブ)しました。
スレーブ側
スレーブ(図3の右側)となるArduinoのスケッチも変更します。
SPI.hライブラリは主にArduinoをマスターとして使用するために設計されているため、スレーブとして動作させるための機能がほとんど提供されていません。
そのため、以下のスケッチは初心者にはかなり難しいものになっていますが、Arduinoどうしの通信の実験を行うために示しています。詳しい内容が理解できなくても大丈夫です。
#include <SPI.h> volatile byte receiveValue; void setup(){ pinMode(9,OUTPUT); SPCR |= bit(SPE); // (1) pinMode(MISO,OUTPUT); // (2) SPI.attachInterrupt(); // (3) } void loop(){ analogWrite(9,receiveValue); } ISR(SPI_STC_vect){ // (4) receiveValue = SPDR; // (5) }
スケッチの内容の解説
(1) ここではArduinoのマイクロコントローラで用意されているSPI制御レジスタを直接操作しています。SPCRレジスタとSPE(SPI Enable)レジスタとのORを取ります。SPEの値はb01000000となっているので、SPCRの下から7ビット目を1にセットし、それ以外のビットは設定変更なしという動作をします。マイコンのメモリの中で、特定の機能のスイッチや設定値として作用する場所(ビット)をレジスタといい、様々な意味・名前を持ったレジスタがあります。通常、ライブラリを使用すれば関係するレジスタの操作をライブラリが自動で行ってくれているのですが、SPIライブラリは「ArduinoをSPIのスレーブにする」機能を持たないため、今回は直接レジスタを操作しました。通常、Arduinoに使われているATMega328などのマイコン(マイクロコントローラ)を使うには、この「レジスタ」を操作します。一つの動作をさせるためにたくさんのレジスタを書き換えたり読み取ったりという作業が発生することもあります。Arduinoのライブラリは自動でレジスタ周りの作業を行ってくれるので、レジスタのことを深く考えなくても簡単に使えるようになっています。
(2) マスターへデータ送信するためのデータ端子MISO(Master In Slave Out:D10ピン)をデジタル出力ピンに設定します。
しかし、この例ではスレーブからマスターにデータ送信しないので、配線も含めて省略することもできます。
(3) 割り込みサービスルーチン(ISR)を定義します。割り込みの名前はSPIライブラリが指定してくれるので空白です。
(4) ISRの内容を定義します。関数なのに型を定義していませんが、ISR()という関数は型を定義しなくても使えるので問題ありません。ISR()は、Arduino IDEで使用しているプログラミング言語のC++で標準の割り込み関数として用意されている、特別な関数です。
(5) プリプロセッサ定数SPDRにSPI通信で送信するデータ・受信したデータが入っているので、受信値receiveValueに代入します。
まとめ
この実験から、ArduinoはSPIのスレーブとして使用するのには適さないとわかります。Arduinoをマスターとして使うのであれば、アドレスを指定しさえすれば他のスレーブを動作させることができます。 センサやディスプレイの中にはI2C接続でしか使えないものもありますが、RFIDレシーバやメモリデバイスの中にはSPI接続でしか使えないものもあります。I2CとSPIは両方を同時に使用できます。ピンの割り当てやArduino内での機能が重複しないためです。
また、SPI・I2Cいずれも、マスターとスレーブや、スレーブ同士の配線距離をあまり長くできません(数メートル以内)。SPIもI2Cもクロック信号を持っているため、シリアル通信に比べるとはるかに通信エラーが起きにくい通信方式ですが、ケーブルが長くなれば交流電界や磁界によるノイズの影響も大きくなり、正常に通信できなくなる場合があります。
SPI・I2Cのいずれも、誤り訂正機能(送信したデータが間違いなく受信できたかどうかを確認する機能)がありません。 受信した側でデータが正しく受信できたかどうかを確認して、必要に応じた処理を行う方法を考えてスケッチに盛り込むことが必要になります。
コメントを残す