回答:
シリアル・ペリフェラル・インタフェース(SPI)インターフェースは、短い距離にわたる複数のデバイス間で通信するための、高速で使用されます。
通常、通信を開始し、データ転送速度を制御するクロックを供給する単一の「マスター」デバイスがあります。1つ以上のスレーブが存在する可能性があります。複数のスレーブの場合、各スレーブには独自の「スレーブ選択」信号があります。これについては後述します。
本格的なSPIシステムでは、4つの信号線があります。
複数のスレーブがMISO信号に接続されている場合、スレーブ選択がアサートされて選択されるまで、MISOラインをトライステート(高インピーダンスに維持)することが期待されます。通常、スレーブ選択(SS)はLowになってアサートします。つまり、アクティブローです。特定のスレーブが選択されると、MISOラインを出力として設定し、マスターにデータを送信できるようにします。
この画像は、1バイトが送信されるときにデータが交換される方法を示しています。
3つの信号がマスターからの出力(MOSI、SCK、SS)であり、1つが入力(MISO)であることに注意してください。
イベントのシーケンスは次のとおりです。
SS
アサートしてスレーブをアクティブにするためにローになりますSCK
ラインは、データラインをサンプリングしなければならないときを示すためにトグルSCK
ますSCK
をしますMISO
MOSI
SS
アサートを解除するためにHighになりますご了承ください:
データは同じクロックパルスで送受信されるため、スレーブがすぐにマスターに応答することはできません。SPIプロトコルは通常、マスターが1回の送信でデータを要求し、後続の送信で応答を取得することを想定しています。
ArduinoでSPIライブラリを使用して、単一の転送を行うと、コードでは次のようになります。
byte outgoing = 0xAB;
byte incoming = SPI.transfer (outgoing);
送信のみの例(着信データを無視する):
#include <SPI.h>
void setup (void)
{
digitalWrite(SS, HIGH); // ensure SS stays high
SPI.begin ();
} // end of setup
void loop (void)
{
byte c;
// enable Slave Select
digitalWrite(SS, LOW); // SS is pin 10
// send test string
for (const char * p = "Fab" ; c = *p; p++)
SPI.transfer (c);
// disable Slave Select
digitalWrite(SS, HIGH);
delay (100);
} // end of loop
上記のコード(送信のみ)を使用して、出力シリアルシフトレジスタを駆動できます。これらは出力専用デバイスであるため、着信データを心配する必要はありません。その場合、SSピンは「ストア」または「ラッチ」ピンと呼ばれることがあります。
この例としては、74HC595シリアルシフトレジスタ、およびいくつかのLEDストリップがあります。たとえば、MAX7219チップによって駆動されるこの64ピクセルLEDディスプレイは次のとおりです。
この場合、ボードメーカーがわずかに異なる信号名を使用していることがわかります。
ほとんどのボードは同様のパターンに従います。DINは単なるDI(データ入力)である場合があります。
別の例を次に示します。今回は7セグメントLEDディスプレイボード(MAX7219チップにも基づいています):
これは、他のボードとまったく同じ信号名を使用します。どちらの場合も、ボードに必要な配線は5本、SPI用は3本、さらに電源とグランドのみです。
SPIクロックをサンプリングする方法は4つあります。
SPIプロトコルでは、クロックパルスの極性を変えることができます。CPOLはクロック極性で、CPHAはクロック位相です。
これらはこのグラフィックで説明されます:
位相と極性を正しくするには、デバイスのデータシートを参照してください。通常、クロックのサンプリング方法を示す図があります。たとえば、74HC595チップのデータシートから:
ご覧のとおり、クロックは通常は低く(CPOL = 0)、リーディングエッジでサンプリングされる(CPHA = 0)ため、SPIモード0です。
次のようなコードでクロックの極性と位相を変更できます(もちろん、1つだけ選択してください)。
SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);
このメソッドは、Arduino IDEのバージョン1.6.0以降では非推奨です。最近のバージョンではSPI.beginTransaction
、次のように呼び出しでクロックモードを変更します。
SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0)); // 2 MHz clock, MSB first, mode 0
デフォルトは最上位ビットが最初ですが、次のように最下位ビットを最初に処理するようにハードウェアに指示できます。
SPI.setBitOrder (LSBFIRST); // least significant bit first
SPI.setBitOrder (MSBFIRST); // most significant bit first
繰り返しになりますが、これはArduino IDEのバージョン1.6.0以降では非推奨です。最近のバージョンではSPI.beginTransaction
、次のように呼び出しのビット順序を変更します。
SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2)); // 1 MHz clock, LSB first, mode 2
SPIのデフォルト設定では、システムクロック速度を4で割った値、つまり16 MHzのCPUクロックを想定して250 nsごとに1つのSPIクロックパルスを使用します。次のsetClockDivider
ように使用して、クロック分周器を変更できます。
SPI.setClockDivider (divider);
「divider」は次のいずれかです。
16 MHzのCPUクロックを想定した場合、最速のレートは「2で割る」または125 nsごとに1つのSPIクロックパルスです。したがって、1バイトを送信するには8 * 125 nsまたは1 µsかかります。
このメソッドは、Arduino IDEのバージョン1.6.0以降では非推奨です。最近のバージョンではSPI.beginTransaction
、次のようにコールの転送速度を変更します。
SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0)); // 4 MHz clock, MSB first, mode 0
ただし、経験的なテストでは、バイト間に2つのクロックパルスが必要であることが示されているため、バイトをクロックアウトできる最大レートはそれぞれ1.125 µsです(クロック分周器が2の場合)。
要約すると、各バイトは1.125 µs(16 MHzクロック)ごとに最大レートで送信でき、理論上の最大転送速度は1 / 1.125 µs、つまり888,888バイト/秒(SSを低く設定するなどのオーバーヘッドを除く)オン)。
デジタルピン10から13を介して接続:
ICSPヘッダーを介した接続:
デジタルピン50〜52を介した接続:
上記のUnoと同様に、ICSPヘッダーを使用することもできます。
LeonardoとMicroは、UnoとMegaとは異なり、デジタルピン上のSPIピンを公開しません。唯一のオプションは、上記のUnoで示したように、ICSPヘッダーピンを使用することです。
マスターは複数のスレーブと通信できます(ただし、一度に1つのみ)。これは、1つのスレーブに対してSSをアサートし、他のすべてのスレーブに対してSSをディアサートすることによりこれを行います。SSがアサートされているスレーブ(通常これはLOWを意味します)は、MISOピンを出力として設定し、スレーブとそのスレーブのみがマスターに応答できるようにします。SSがアサートされていない場合、他のスレーブは着信クロックパルスを無視します。したがって、次のように、各スレーブに1つの追加信号が必要です。
この図では、MISO、MOSI、SCKが両方のスレーブ間で共有されていますが、各スレーブには独自のSS(スレーブ選択)信号があります。
SPI仕様はプロトコル自体を指定していないため、データの意味について合意するのは個々のマスター/スレーブの組み合わせです。バイトを同時に送信および受信できますが、受信バイトは送信バイトに対する直接の応答にはなりません(同時に組み立てられるため)。
そのため、一方の側が要求を送信し(たとえば、4は「ディスクディレクトリを一覧表示する」ことを意味する)、完全な応答を受信するまで転送を行う(おそらくゼロを外部に送信する)方が論理的です。応答は、改行または0x00文字で終了する場合があります。
スレーブデバイスのデータシートを読んで、期待されるプロトコルシーケンスを確認してください。
前の例では、Arduinoをマスターとして使用して、データをスレーブデバイスに送信しています。この例は、Arduinoがスレーブになる方法を示しています。
2つのArduino Unosを互いに接続された次のピンで接続します。
13(SCK)
+ 5v(必要な場合)
Arduino Megaでは、ピンは50(MISO)、51(MOSI)、52(SCK)、および53(SS)です。
いずれにせよ、一方のMOSIはもう一方のMOSIに接続されており、スワップしません(つまり、MOSI <-> MISO がありません)。ソフトウェアは、MOSIの一方の端(マスターエンド)を出力として設定し、もう一方の端(スレーブエンド)を入力として設定します。
#include <SPI.h>
void setup (void)
{
digitalWrite(SS, HIGH); // ensure SS stays high for now
// Put SCK, MOSI, SS pins into output mode
// also put SCK, MOSI into LOW state, and SS into HIGH state.
// Then put SPI hardware into Master mode and turn SPI on
SPI.begin ();
// Slow down the master a bit
SPI.setClockDivider(SPI_CLOCK_DIV8);
} // end of setup
void loop (void)
{
char c;
// enable Slave Select
digitalWrite(SS, LOW); // SS is pin 10
// send test string
for (const char * p = "Hello, world!\n" ; c = *p; p++)
SPI.transfer (c);
// disable Slave Select
digitalWrite(SS, HIGH);
delay (1000); // 1 seconds delay
} // end of loop
#include <SPI.h>
char buf [100];
volatile byte pos;
volatile bool process_it;
void setup (void)
{
Serial.begin (115200); // debugging
// turn on SPI in slave mode
SPCR |= bit (SPE);
// have to send on master in, *slave out*
pinMode (MISO, OUTPUT);
// get ready for an interrupt
pos = 0; // buffer empty
process_it = false;
// now turn on interrupts
SPI.attachInterrupt();
} // end of setup
// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR; // grab byte from SPI Data Register
// add to buffer if room
if (pos < sizeof buf)
{
buf [pos++] = c;
// example: newline means time to process buffer
if (c == '\n')
process_it = true;
} // end of room available
} // end of interrupt routine SPI_STC_vect
// main loop - wait for flag set in interrupt routine
void loop (void)
{
if (process_it)
{
buf [pos] = 0;
Serial.println (buf);
pos = 0;
process_it = false;
} // end of flag set
} // end of loop
スレーブは完全に割り込み駆動型であるため、他のことを実行できます。着信SPIデータはバッファに収集され、「重要なバイト」(この場合は改行)が到着するとフラグが設定されます。これにより、スレーブにデータの処理を開始して開始するよう指示します。
SPIマスターからスレーブにデータを送信する上記のコードに続いて、以下の例では、データをスレーブに送信し、それを使用して応答を返します。
マスターは上記の例に似ています。ただし、重要な点は、わずかな遅延(20マイクロ秒など)を追加する必要があることです。そうしないと、スレーブは着信データに反応してそれを処理する機会がありません。
この例は、「コマンド」の送信を示しています。この場合、「a」(何かを追加)または「s」(何かを減算)。これは、スレーブが実際にデータを処理していることを示すためです。
スレーブ選択(SS)をアサートしてトランザクションを開始した後、マスターはコマンドに続いて任意のバイト数を送信し、SSを上げてトランザクションを終了します。
非常に重要な点は、スレーブが同時に着信バイトに応答できないことです。応答は次のバイトにある必要があります。これは、送信中のビットと受信中のビットが同時に送信されているためです。したがって、4つの数値に何かを追加するには、次のように5つの転送が必要です。
transferAndWait ('a'); // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);
最初に10番のアクションを要求します。しかし、次の転送(17の転送)まで応答を受け取りません。ただし、「a」は10への応答に設定されます。最後に、42の応答を取得するために「ダミー」番号0を送信することになります。
#include <SPI.h>
void setup (void)
{
Serial.begin (115200);
Serial.println ();
digitalWrite(SS, HIGH); // ensure SS stays high for now
SPI.begin ();
// Slow down the master a bit
SPI.setClockDivider(SPI_CLOCK_DIV8);
} // end of setup
byte transferAndWait (const byte what)
{
byte a = SPI.transfer (what);
delayMicroseconds (20);
return a;
} // end of transferAndWait
void loop (void)
{
byte a, b, c, d;
// enable Slave Select
digitalWrite(SS, LOW);
transferAndWait ('a'); // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);
// disable Slave Select
digitalWrite(SS, HIGH);
Serial.println ("Adding results:");
Serial.println (a, DEC);
Serial.println (b, DEC);
Serial.println (c, DEC);
Serial.println (d, DEC);
// enable Slave Select
digitalWrite(SS, LOW);
transferAndWait ('s'); // subtract command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);
// disable Slave Select
digitalWrite(SS, HIGH);
Serial.println ("Subtracting results:");
Serial.println (a, DEC);
Serial.println (b, DEC);
Serial.println (c, DEC);
Serial.println (d, DEC);
delay (1000); // 1 second delay
} // end of loop
スレーブのコードは、基本的に、割り込みルーチンのほとんどすべてを実行します(着信SPIデータの到着時に呼び出されます)。着信バイトを受け取り、記憶された「コマンドバイト」に従って加算または減算します。応答は次回ループで「収集」されることに注意してください。これが、マスターが最後の「ダミー」転送を送信して最終応答を取得する必要がある理由です。
この例では、メインループを使用して、SSがHighになるタイミングを検出し、保存されたコマンドをクリアします。これにより、SSが次のトランザクションのために再びLowにプルされると、最初のバイトがコマンドバイトと見なされます。
より確実に、これは割り込みで行われます。つまり、SSを割り込み入力の1つに物理的に接続します(たとえば、Unoで、ピン10(SS)をピン2(割り込み入力)に接続するか、ピン10でピン変更割り込みを使用します)。
次に、割り込みを使用して、SSがLowまたはHighにプルされていることを通知できます。
// what to do with incoming data
volatile byte command = 0;
void setup (void)
{
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);
// turn on SPI in slave mode
SPCR |= _BV(SPE);
// turn on interrupts
SPCR |= _BV(SPIE);
} // end of setup
// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;
switch (command)
{
// no command? then this is the command
case 0:
command = c;
SPDR = 0;
break;
// add to incoming byte, return result
case 'a':
SPDR = c + 15; // add 15
break;
// subtract from incoming byte, return result
case 's':
SPDR = c - 8; // subtract 8
break;
} // end of switch
} // end of interrupt service routine (ISR) SPI_STC_vect
void loop (void)
{
// if SPI not active, clear current command
if (digitalRead (SS) == HIGH)
command = 0;
} // end of loop
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
これは、上記のコードの送信と受信のタイミングを示しています。
IDEのバージョン1.6.0では、SPIの動作がある程度変更されています。あなたはまだやる必要がある SPI.begin()
SPIを使用する前に。これでSPIハードウェアがセットアップされます。ただし、現在、スレーブとの通信を開始しようとしているときに、正しい(このスレーブの)SPIをセットアップすることも行いSPI.beginTransaction()
ます。
スレーブとの通信が完了したら、を呼び出しますSPI.endTransaction()
。例えば:
SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));
digitalWrite (SS, LOW); // assert Slave Select
byte foo = SPI.transfer (42); // do a transfer
digitalWrite (SS, HIGH); // de-assert Slave Select
SPI.endTransaction (); // transaction over
予備的な質問を1つ追加します。いつ、なぜSPIを使用するのですか。マルチマスター構成または非常に多数のスレーブが必要な場合、スケールをI2Cに傾けます。
これは素晴らしい質問です。私の答えは:
どちらの方法にもそれぞれの場所があります。I 2 Cでは、多数のデバイスを1つのバス(2本のワイヤとアース)に接続できるため、かなりの数のデバイスを照会する必要がある場合(おそらくほとんど使用しない場合)に選択することをお勧めします。ただし、SPIの速度は、高速で出力する必要がある場合(LEDストリップなど)または高速で入力する必要がある場合(ADCコンバーターなど)に関連する可能性があります。
SPIに関する私のページ -また、ビットバンギングSPIの詳細と、USARTを使用してAtmega328チップ上で2番目のハードウェアSPIを取得します。
Are you going to cover the weirdness that is the Due's SPI?
-DueのSPIについては何も知りません(プロトコル全体が同じであると仮定する以外は)。その側面をカバーする返信を追加してください。