2023年02月28日 | 更新。 |
SPIとは、IC間の通信に使われるクロック同期方式のシリアルインターフェースの一種で、モトローラ(現NXPセミコンダクターズ)が提唱し、広く普及しました。マイコンと周辺デバイスの接続によく使われます。パラレルインターフェースと比べると低速ですが、配線が少なくて済むという特長があります。また、SPIと同様、IC間の通信を少ない信号線で行うために使われるI2Cというインターフェース比較すると、配線量は若干増えるものの、通信速度が速いという特長があります。
SPIでIC同士を接続する場合、図1に示す様に、1対多(または1対1)の接続となります。通信するICは、1つのマスタと、複数(または1つ)のスレーブに分けられます。マイコンで周辺デバイスを制御する場合は、マイコンがマスタ、周辺デバイスがスレーブになります。マイコン同士の通信にSPIを使う事も出来ますが、その場合は、どれか1つをマスターに、残りをスレーブにする必要があります。
通信は必ずマスタ・スレーブ間で行われます。スレーブ同士が直接通信する事は出来ません。また、マスタは、同時には1つのスレーブとしか通信できません。
通信の制御はマスタが行い、マスタがスレーブに送信する場合でも、スレーブがマスタに送信する場合でも、常に通信はマスタが開始します。スレーブが自発的にマスタに送信する事は出来ません。
SPIは、表1に示す様に、SCLK、MISO、MOSI、SSの4種類の信号線からなります。
信号線の名称 | 信号線の本数 | マスターから見た信号の方向 | 働き |
---|---|---|---|
SCLK | 1本 | 出力 | シリアルクロック。MISOやMOSIのデータは、動作モードによりSCKの立ち上がりまたは立ち下がりに同期して転送されます。 |
MISO | 1本 | 入力 | スレーブからマスタに転送するデータを伝える信号線です。 |
MOSI | 1本 | 出力 | マスタからスレーブに転送するデータを伝える信号線です。 |
SS | スレーブと同じ数 | 出力 | マスタが通信相手のスレーブを選択するための信号線です。SSがLになったスレーブが、マスタと通信を行います。 |
SCLK(Serial CLocK)は、MISOやMOSIの信号線でデータ転送を行う際に参照する、クロック信号です。全てのスレーブが1本のSCLK信号線を共有します。動作モード(後述)により、SCLKの立ち上がりエッジまたは立ち下りエッジに同期してデータが転送されます。ICの種類によっては、SCKなどと表記される場合もあります。
MISO(Master In Slave Out)は、スレーブからマスタに転送するデータを伝える信号線です。全てのスレーブが1本のMISO信号線を共有します。SS信号により選択されていないスレーブは、MISO信号をハイインピーダンスに保ち、信号の衝突を防ぎます。スレーブ専用ICでは、DO(Data Out)やSDO(Serial Data Out)などと表記される事があります。
MOSI(Master Out Slave In)は、マスタからスレーブに転送するデータを伝える信号線です。全てのスレーブが1本のMOSI信号線を共有します。スレーブ専用ICでは、DI(Data In)やSDI(Serial Data In)などと表記される事があります。
SS(Slave Select)は、マスタが通信相手のスレーブを選択するための信号線です。各スレーブに専用のSS信号があります。マスタは、通信に先立って、通信相手のSS信号のみをLにし(他のSS信号はH)、通信を開始します。スレーブ専用ICでは、CS(Chip Select)や/CSなどと表記される事があります。
信号線の配線の様子を図2に示します。
スレーブが1つ増えるごとにSS信号が1つ増えるので、N個のスレーブを使うときは、N+3本の信号線が必要になります。I2Cでは通信するデバイスが増えても、信号線は必ず2本なので、信号線の数の面ではSPIの方が不利になります。
図3に、SPIでの通信のタイミングチャートの例を示します。
マスターは、SS信号をHからLに切り替えて、通信相手のスレーブを選択した上で、SCLK信号にクロック信号を送信します。また、通信が終了したら、クロックを止めてSS信号をHに戻します。
マスターからスレーブにデータを送る時はMOSI信号に、スレーブからマスターにデータを送る時はMISO信号に、データを送ります。データは、最上位ビットから最下位ビットの順にクロック信号に同期して送信します。
図3の例では、最初の8クロックはマスターからスレーブへの通信で、後の8クロックはスレーブからマスターの通信となっていますが、何クロック目から何クロック目までをマスターからスレーブへの通信に割り当てるかは、スレーブのデバイスの仕様によります。マスターは、スレーブのデバイスの仕様に従ってデータを送受信します。MOSI信号とMISO信号のそれぞれにデータを載せれば、送受信を同時に行う事も可能です。
SPIでは、クロックの極性(CPOL)とクロックの位相(CPHA)により、4つの動作モードが定義されており、それぞれモード0~4と、数字で区別されています。
CPOL(Clock POLarity)は、クロックパルスが正極性か(CPOL=0)、負極性か(CPOL=1)を表します。(図4参照)
クロックパルスが正極性の場合(CPOL=0)、通信を行っていない間はSCLK信号はLに固定し、通信時にHのクロックパルスを生成します。(図4(a)参照)
クロックパルスが負極性の場合(CPOL=1)、通信を行っていない間はSCLK信号はHに固定し、通信時にLのクロックパルスを生成します。(図4(b)参照)
CPHA(Clock PHAse)は、クロックパルスの最初で信号をサンプルするか(CPHA=0)、クロックパルスの最後でサンプルするか(CHPA=1)を表します。(図5参照)
CPHA=0の場合、クロックパルスの最初の部分でMOSIやMISOの信号がサンプリングされます。クロックパルスの最後の部分では、MOSIやMISOのデータが次のビットに切り替わります。(図5(a)参照)
CPHA=1の場合、クロックパルスの最後の部分でMOSIやMISOの信号がサンプリングされます。クロックパルスの最初の部分では、MOSIやMISOのデータが次のビットに切り替わります。(図5(b)参照)
MOSIやMISOの信号がサンプリングされるタイミングと、データが次のビットに切り替わるタイミングが半クロックずれているので、セットアップタイムやホールドタイムが確保しやすい仕様になっています。
動作モードは、CPOLとCPHAの組み合わせで、次の様に定義されています。
動作モード | CPOL | CPHA | データのサンプリングのタイミング | 備考 |
---|---|---|---|---|
モード0 | 0 | 0 | クロックの立ち上がりエッジ | クロックは正極性。クロックパルスの最初でサンプル。 |
モード1 | 0 | 1 | クロックの立ち下がりエッジ | クロックは正極性。クロックパルスの最後でサンプル。 |
モード2 | 1 | 0 | クロックの立ち下がりエッジ | クロックは負極性。クロックパルスの最初でサンプル。 |
モード3 | 1 | 1 | クロックの立ち上がりエッジ | クロックは負極性。クロックパルスの最後でサンプル。 |
どの動作モードで通信するかは、スレーブ側のデバイスにより規定されます。マスターは、スレーブの仕様に合わせて動作モードを設定する必要があります。
マイコンやDSPなど、周辺機器とSPI接続するプロセッサには、SPIインタフェースのタイミングを生成する専用の周辺回路が内蔵されている場合が多くあります。そのような専用の周辺回路を用いてSPI通信を行う方式をハードウェアSPIと呼びます。
SPIインターフェースはシリアル方式で通信するのに対し、プロセッサのバスはパラレル方式なので、SPIインターフェース用の周辺回路には、それらの差を吸収するためのシリアル-パラレル変換用のシフトレジスタが用いられます。(図6参照)
プロセッサにSPIインタフェース用の周辺回路が内蔵されていない場合は、GPIOを用い、ソフトウェアによりSPIインターフェースの信号を生成する事もあります。この様に、ソフトウェアにより信号を生成する方式のSPI通信をソフトウェアSPIと呼びます。
ArduinoでSPI通信をする場合のプログラム法について説明します。
ArduinoでSPI通信をする場合、原則、Arduinoがマスタ側になります。Arduinoをスレーブにするには、独自のライブラリを作るなどの高度な技術が必要ですので、ここでは説明しません。
SPI通信に使う信号線の内、MOSI、MISO、SCKの各信号線は、図7のSPIヘッダ(SPIと書いた2×3ピンのヘッダ)に出ています。SS信号線は、後述するようにdigitalWrite関数により制御するので、digitalWrite関数で制御できる信号線ならどのピンでも利用できます。
図8に、SPIヘッダの信号の割り当てを示します。
RESET信号は、Lにするとマイコンにリセットがかかる信号です。特にSPI通信に関係のある信号線ではありませんが、8ビットAVRマイコンを使用したArduinoでは、SPIインターフェースを用いてフラッシュメモリの書き込みを行う仕組みになっており、その書き込みの際にCPUのリセットを行う必要があった事のなごりです。
ArduinoでSPI通信をする場合は、Arduino IDEに付属のSPIライブラリを使用します。スケッチの先頭に次のinclude文を挿入し、SPIライブラリを使用する事を宣言します。
#include <SPI.h>
この様に宣言すると、SPIというオブジェクト変数が使える様になり、このSPI変数を用いて、SPI通信を行います。
SPIライブラリは、MOSI信号、MISO信号、SCLK信号の制御は行いますが、SS信号の制御は行いません。SS信号は、SPI.beginTransaction関数(後述)を呼び出す前に、digitalWrite関数でLにし、SPI.endTransaction関数(後述)を呼び出した後に、digitalWrite関数でHに戻します。
SPIバスを使った、ひとまとまりの通信をトランザクションといいます。例えば、SPI接続の温度計から温度を読み取る場合、Arduinoが温度計に対し、温度の読み取りコマンドを発行し、その後温度のデータを受け取るまでがトランザクションです。トランザクションの間は、複数のSPI接続の機器との通信が輻輳(混線)しないように、他の機器へのSPI通信を発生する割り込みは禁止されます。
トランザクションを開始するには、例えば次の様にします。
SPI.beginTransaction(SPISettings(4000000,MSBFIRST,SPI_MODE0));
SPI.beginTransaction関数は、トランザクションを開始します。またSPI.beginTransaction関数は、1つのSPISettingsクラスのオブジェクトの引数を取ります。
SPISettingsクラスは、SPI通信のモードを指定するためのクラスで、コンストラクタには3つの引数を取ります。
第1引数は、SCKラインのクロック周波数です。先ほどの例では4000000を指定していましたので、4MHzになります。
第2引数は、ビット列を送受信する際に、MSB(最上位ビット)からデータを送信するか、LSB(最下位ビット)からデータを送信するかを決めます。第2引数に使う定数を表3に示します。
定数 | 意味 |
---|---|
MSBFIRST | MSB(最上位ビット)からデータを送出する |
LSBFIRST | LSB(最下位ビット)からデータを送出する |
注:SPIインタフェースの規格上、データは最上位ビットから送信する決まりになっていますが、Arduinoの場合、最下位ビットから送信する事も出来る様になっています。
第3引数は、SPI通信のモード(0~3)を指定します。第3引数に使う定数を表4に示します。
定数 | 意味 |
---|---|
SPI_MODE0 | モード0で通信する |
SPI_MODE1 | モード1で通信する |
SPI_MODE2 | モード2で通信する |
SPI_MODE3 | モード3で通信する |
SPI.transfer関数または、SPI.transfer16関数を用いれば、データの送受信を行えます。送受信するデータ長は、8ビットの整数倍です。SPI通信においては、SCLK信号のクロックに同期して、必ず同じビット数のデータの送信と受信が同時に行われる点に注意してください。
次の書式でSPI.transfer関数を呼び出すと、8ビットのデータの送受信ができます。
SPI.transfer関数の引数valは8ビットの整数で、この引数がSPIバスのMOSI信号線に送信されます。また返り値receivedValには、MISO信号線から受信した8ビットのデータが返ってきます。
次の書式でSPI.transfer16関数を呼び出すと、16ビットのデータの送受信ができます。
SPI.transfer16関数の引数val16は16ビットの整数で、この引数がSPIバスのMOSI信号線に送信されます。また返り値receivedVal16には、MISO信号線から受信した16ビットのデータが返ってきます。
次の書式でSPI.transfer関数を呼び出すと、任意のバイト数のデータの送受信ができます。
この書式の場合、引数が2つあります。
第1引数のbufferは、送受信するデータを格納するバッファ変数です。通常は、byte型の配列など、マルチバイトの変数にします。SPI.transfer関数を呼び出す時点で変数bufferに格納されていたデータがMOSI信号線に送出され、MISO信号線から受信したデータは、変数bufferを上書きする形で格納されます。
第2引数は、送受信するデータのバイト数で、通常は変数bufferのサイズを指定します。
SPI.beginTransaction関数でトランザクションを開始した後、1つまたは複数のSPI.transfer関数(またはSPI.transfer16関数)で送受信をし、一連の通信が終わったら、次の様にSPI.endTransaction関数を呼び出してトランザクションを終了します。SPI.endTransaction関数に引数はありません。
SPI.endTransaction();
もし、割り込み処理中にSPIのトランザクションを行うなら、あらかじめSPI.usingInterrupt関数により、使用する割り込み番号を登録しておく必要があります。
SPI.usingInterrupt関数の書式は次の通りです。
引数のinterruptNumberが割り込み番号です。
SPI.usingInterrupt関数で使用する割り込み番号を登録しておくと、SPI.beginTransaction関数を実行する際に、その割り込みがマスクされます。また、SPI.endTransaction関数が実行される際に、マスクが解除されます。この処理により、他の機器とのSPI通信の最中に割り込みがかかる事を防ぐ事ができます。