やること
Arudino UNO同士でSPI通信を行います。
1台をメイン(マスター)、もう1台をサブ(スレーブ)とします。
SPI通信は送受信が同時という特徴があります。センサーなどとのSPI通信はライブラリなどで簡単に実行することができますが、Arduino自身をサブとして使うには少しコツがいるようです。
理解のために、ArduinoのSPI用レジスタについて学んでいきます。
準備物・実施環境
- Arduino UNO 2個
- Arduino IDE
とりあえず動かすスケッチ
マスター側から168を送信し、スレーブ側から95を送信します。
メイン側の Arduino UNO(SPI通信のきっかけを発信する側)
/* Arduino uno SPI-Main */
#include <SPI.h>
void setup() {
Serial.begin(115200);
Serial.println("\nBegin SPI Main.");
SPI.begin(); // 規定のピンでSPI通信を開始する
digitalWrite(SS, HIGH); // 相手のSPI通信を非アクティブに
}
byte txdata = 168;
byte rxdata = 0;
void loop() {
digitalWrite(SS, LOW); // 相手のSPI通信をアクティブに
rxdata =SPI.transfer(txdata); // txdataを送信し、受信結果をrxdataに入れる
digitalWrite(SS, HIGH); // 相手のSPI通信を非アクティブに
Serial.print("transmit:");
Serial.print(txdata);
Serial.print(" receive:");
Serial.println(rxdata);
delay(1000);
}
SPIのメイン側はライブラリがそのまま使えてシンプルです。
サブ側の Arduino UNO(SPI通信のきっかけを待ち受けする側)
/* Arduino uno SPI-Sub */
#include <SPI.h>
byte txdata = 95; //SPI送信データ用変数
byte rxdata = 0; // SPI受信データ用変数
void setup() {
Serial.begin(115200);
Serial.println("\nBegin SPI Sub.");
pinMode(MISO, OUTPUT);
SPCR |= bit(SPE); // SPIコントロールレジスタを操作し、SPI通信を有効に
SPI.attachInterrupt(); //ISR(SPI_STC_vect)の割り込みを開始
}
void loop() {
}
ISR(SPI_STC_vect) { //ISR(SPI用の割り込み)が、SPI_STC_vect(通信完了時)に実行される
rxdata = SPDR; // 通信レジスタSPDRの内容をrxdataに代入する
SPDR = txdata; // 通信レジスタSPDRにtxdataに代入する
Serial.print("receive:");
Serial.print(rxdata);
Serial.print(" transmit:");
Serial.println(txdata);
}
SPCRやSPE,SPDRやISR(SPI_STC_vect)といったライブラリともちょっと違う見慣れない呪文が現れてすごく気になりますが、とりあえず書き込んで動かしてみます。
接続
Arduinoメイン | 機能 | Arduinoサブ |
---|---|---|
5v | 5v | |
GND | GND | |
10 | SS | 10 |
11 | MOSI(COPI) | 11 |
12 | MISO(CIPO) | 12 |
13 | SCK | 13 |
実行結果


シリアルモニタの表示例です。
SPIメインから168を送信、SPIサブからは95を送信し、互いに値を受信します。
リセットボタンを押した直後はデータの受信に失敗することもあるようです。
なぞのコマンドを理解する
SPCR, SPE, SPDR, ISR(SPI_STC_vect)の正体について解説します。
ArduinoのSPIでは、専用のデータアドレスを使って通信を制御しているようです。
専用のデータアドレスは説明が簡単な順に以下の3つになります。
①SPDR – SPI Data Register : データを格納するレジスタ
②SPSR – SPI Status Register : ステータスフラグ用のレジスタ
③SPCR – SPI Control Register : 制御コマンド用のレジスタ
それぞれについて調べながら解説していきます。
① SPDR – SPI Data Register
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
0x2E (0x4E) |
MSB | LSB |
送受信するデータを格納するレジスタです。
1バイト(8bit)のデータとなっており、SPI通信の実行でこのレジスタに格納されたデータを送信し、同時に受信データを同じ場所に格納します。
MSB、LSBはそれぞれ、最上位ビット(Most Significant Bit)、最下位ビット(Least Significant Bit)を表します。
② SPSR – SPI Status Register
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
0x2D (0x4D) |
SPIF | WCOL | – | – | – | – | – | SPI2X |
フラグの意味 | 割り込み | 書込衝突 | – | – | – | – | – | 通信速度2倍 |
SPSRはSPI通信の状態で変化するビットフラグです。
bit7:SPIF(SPI Interrupt Flag)
シリアル転送が完了するとセットされます。メインモードでSSがロー(アクティブ)になるとき、このフラグもセットされます。割り込み処理の実行時などにクリアされます。
bit6:WCOL(Write COLlision Flag)
書き込み衝突フラグです。データ転送中にSPIデータレジスタ (SPDR) が書き込まれると、WCOL ビットがセットされます。SPIステータスレジスタを読み出し、次にSPIデータレジスタにアクセスするとクリアされます。
bit0:SPI2X(Double SPI Speed Bit)
SPI速度の設定フラグです。このビットにロジック 1 が書き込まれると、SPIがマスターモードのときにSPI速度 (SCK周波数)が最小でCPUのクロック周期の2倍になります。
bit1-bit5は他の種類のマイコンのための予約ビットとなり、常に0が入ります。
③ SPCR – SPI Control Register
SPCRはSPI通信の制御用のビットフラグです。
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
0x2C (0x4C) |
SPIE | SPE | DORD | MSTR | CPOL | CPHA | SPR1 | SPR0 |
bit7:SPIE(SPI Interrupt Enable)
SPIEビットは、割り込みの管理に使われます。
前述②のSPIFがセットされているときに、さらにSREG(Status Register)内のグローバル割り込みが有効である場合、SPI割り込みが発生します。こ
SPI通信完了などのイベントで自動的に処理を行うことが可能になります。
bit6:SPE(SPI Enable)
SPEビットがセットされることでSPIハードウェアが有効となり、データの送受信が可能になります。
bit5:DORD(Data Order)
通信データのビット順序を設定します。
DORDが1はLSB最下位ビットからデータを送信、0の場合はMSB最上位ビットから送信します。接続するデバイスに依存します。
bit4:MSTR(Main/Sub Select)
SPIの動作モードを選択します。
MSTRが1であればメインモード、0であればサブモードです。SSピン(SubSelect)がアクティブになると、動作モードが変更されることがあります。
bit3:CPOL(Clock Polarity)
クロック信号のアイドル時の極性を制御します。
CPOLが0の場合、アイドル時に低電位、つまり正論理。
CPOLが1の場合、クロック信号はアイドル時に高電位、つまり負論理となります。
接続するデバイスのクロック要件に合わせて設定します。
bit2:CPHA(Clock Phase)
クロックの位相。電位の切り替わりのどのタイミングでデータを読むかの設定。
CPHAが0の場合、電位が0から1へ切り替わる時にデータ読み取り。
CPHAが1の場合、電位が1から0へ切り替わる時にデータ読み取り。
bit1,0:SPR1, SPR0(SPI Clock Rate Select 1 and 0)
メインとして設定されたデバイスのシリアルクロック(SCK)のレートを制御します。
SCKと発振機クロック周波数foscとの関係は、次の表に示されています。
Arduino UNOはクロック周波数16MHzですが最速で8Mhzの通信速度を設定できるようなので、SPIライブラリ的にはこのSPR設定と先ほどのSPI2Xを組み合わせて実行しているのでしょう。
SPR1 | SPR0 | SCK周波数(foscの分数として) |
---|---|---|
0 | 0 | fosc/4 |
0 | 1 | fosc/16 |
1 | 0 | fosc/64 |
1 | 1 | fosc/128 |
コードの解読
という感じで、それぞれのビットフラグなどについてざっと調べました。
ここまで調べると、さきほどのSPIサブ用のコードも詳しく理解できそうです。
SPCR |= bit(SPE);
というコードは、SPCR(SPIコントロールレジスタ)を操作し、SPE(SPI Enable)にあたるSPCRのbit6を1にしていることがわかります。
つまり「SPI Enable」を命令したということになります。
rxdata = SPDR;
SPDR = txdata;
というコードは、SPDR(SPI Data Register)にあるデータをrxdataに代入し、改めてSPDRにtxdataを代入し、次のSPI送受信に備える命令であるということがわかります。
ISR(SPI_STC_vect) {...}
というコードについては特に説明がありませんでしたが、こちらも調べながら解説します。
ISRは"Interrupt Service Routine"の略で、外部ピン割り込みやタイマー割り込みなどを設定するための関数です。
またSPI_STC_vectは"SPI Serial Transfer Complete vector"の略で、通信でデータ転送が完了したことを示す割り込みベクタです。
このコードはSPI通信が完了する度に呼び出され、実行される関数になります。
SPIサブのコードも、概要が少しわかってきました。
次回記事
複数バイトのデータの送受信を試してみようと思います。
またESP32の場合についても調べてみたいと思います。
参考
いろいろ違っていたらすみません。ご指摘ください。