SPIとは?ArduinoのMOSI、MISO、SCK、CSピンをわかりやすく解説
SPIとは何か、どのように動くのかを解説します。MOSI、MISO、SCK、CSピン、SPIモードと速度、複数デバイスの接続、Uno、Mega、ESP32、Picoのピン、SDカードとRFIDの競合まで扱います。
SPI(Serial Peripheral Interface)は、マイコンがSDカード、RFIDリーダー、カラーTFTディスプレイ、無線モジュールなどのデバイスと4本の線で非常に高速にデータをやり取りするための通信方式です。クロック線(SCK)、方向ごとに分かれた2本のデータ線(MOSIとMISO)、そして通信相手のデバイスを選ぶCS線があります。I2Cより数十倍速くできるため、大量のデータを扱うモジュールでよく使われます。
この記事では、SPIのしくみ、ピン名と新しい呼び方、4つのSPIモード、速度の上限、Arduino Uno、Mega、ESP32(VSPIとHSPI)、Raspberry Pi Picoのピン、同じバスに複数のデバイスをつなぐ方法、そしてSDカードとRC522 RFIDリーダーを一緒に使うとトラブルが起きる理由を説明します。最後のセクションでは、SPIをI2CやUARTと比較します。
SPIとは?どのように動くのか
SPIには必ず1つのコントローラー(以前はマスターと呼ばれていました)と、1つ以上のペリフェラル(以前はスレーブと呼ばれていました)があります。クロックを作るのはコントローラーだけです。つまり、Arduinoが通信を始め、速度を決め、通信を終わらせます。クロックパルスが1回出るたびに、コントローラーはMOSIで1ビットを送り、同時にペリフェラルはMISOで1ビットを返します。両側のシフトレジスタは一種の輪のようになっていて、8回のクロックパルスのあとには、コントローラーのバイトがペリフェラルへ、ペリフェラルのバイトがコントローラーへ移っています。そのためSPIは全二重(full duplex)で、読み取りと書き込みが同時に行われます。
| 信号線 | 旧名称 | 新名称 | 方向 | 役割 |
|---|---|---|---|---|
| SCK / SCLK | Serial Clock(シリアルクロック) | SCK | コントローラー → ペリフェラル | クロック信号。パルスごとに1ビットが移動する |
| MOSI | Master Out Slave In(マスター出力・スレーブ入力) | COPI / SDO | コントローラー → ペリフェラル | Arduinoからモジュールへ送るデータ |
| MISO | Master In Slave Out(マスター入力・スレーブ出力) | CIPO / SDI | ペリフェラル → コントローラー | モジュールからArduinoへ戻るデータ |
| CS / SS | Slave Select(スレーブ選択) | CS(Chip Select、チップセレクト) | コントローラー → ペリフェラル | LOWの間そのモジュールが受信する。デバイスごとに専用のCSを使う |
モジュールに印刷されている表記は統一されておらず、これがいちばん混乱しやすいポイントです。RC522 RFIDモジュールではCS線がSDAと表記され(I2CのSDAとは関係ありません)、nRF24L01ではCSN、MCP3008ではCS/SHDN、ディスプレイによってはDIN(MOSI)、CLK(SCK)、DOUT(MISO)といった名前も見かけます。ルールはシンプルで、データ入力(DIN、SDI、MOSI)はArduinoのMOSIへ、データ出力(DOUT、SDO、MISO)はArduinoのMISOへつなぎます。
- コントローラーは、通信したいデバイスのCS線をLOWにします。ほかのデバイスはすべてCSがHIGHのままで、MISO線を手放します。
- コントローラーがSCKでクロックパルスを出し始めます。
- パルスごとにMOSIから1ビットが出て、MISOから1ビットが入ります。ビットの順番は、たいてい最上位ビットから(MSB first)です。
- 必要なバイト数(コマンド、アドレス、データ)を転送し終えると、コントローラーはCSをHIGHに戻し、デバイスが処理を完了します。
SPIモード:CPOLとCPHA
SPIには単一の規格がなく、アイドル時にクロックがどのレベルで止まっているか(CPOL、クロック極性)と、クロックのどちらのエッジでデータを読むか(CPHA、クロック位相)がデバイスによって異なります。この2つの設定の4通りの組み合わせが、4つのSPIモードです。コントローラーとデバイスのモードが合っていないと、データが1ビットずれて意味のない値が読めてしまいます。幸い、ほとんどのモジュールはモード0を使います。
| モード | CPOL | CPHA | アイドル時のクロック | データを読むエッジ | デバイスの例 |
|---|---|---|---|---|---|
| SPI_MODE0 | 0 | 0 | LOW | 立ち上がり | SDカード、RC522、nRF24L01、MAX7219、MCP3008、74HC595 |
| SPI_MODE1 | 0 | 1 | LOW | 立ち下がり | 一部のADCやドライバIC |
| SPI_MODE2 | 1 | 0 | HIGH | 立ち下がり | ほとんど使われない |
| SPI_MODE3 | 1 | 1 | HIGH | 立ち上がり | ADXL345加速度センサー、CSピンのない一部のST7789ディスプレイ |
SPIの速度とケーブルの長さ
SPIでは速度を決めるのはコントローラーで、上限はたいていデバイス側にあります。Arduino Unoは16 MHzのクロックの半分にあたる8 MHzまで出せます。SPIライブラリのデフォルトは4 MHzです。ESP32は専用のSPIピン(IO_MUX)で最大80 MHzのクロックを出せますが、GPIOマトリクス経由でほかのピンに信号を回すと上限が下がります。Raspberry Pi Picoは125 MHzのシステムクロックで約62.5 MHzまで出せます。高速なSPI信号には短い配線が必要です。ブレッドボードと10〜20 cmのジャンパーワイヤーなら数MHzは問題ありませんが、20〜30 MHz以上では配線を短くし、できればはんだ付けで接続してください。数メートルの長さはSPIには向いていません。
| デバイス | 役割 | 最大SPIクロック(データシート) | SPIモード |
|---|---|---|---|
| microSDカード | データ記録 | 起動時は400 kHz、その後25 MHzまで | モード0 |
| RC522 (MFRC522) | RFIDリーダー | 10 MHz | モード0 |
| nRF24L01 | 2.4 GHz無線 | 10 MHz | モード0 |
| MAX7219 | LEDマトリクス / 7セグメント | 10 MHz | モード0 |
| MCP3008 | 8チャンネル10ビットADC | 5 Vで3.6 MHz、2.7 Vで1.35 MHz | モード0または3 |
| ILI9341 | 2.4〜3.2インチのカラーTFT | 書き込み時10 MHz(実際にはもっと高速で駆動される) | モード0 |
SPIピン:Arduino Uno、Mega、ESP32、Raspberry Pi Pico
| 信号線 | Arduino Uno / Nano | Arduino Mega | ESP32 VSPI(デフォルト) | ESP32 HSPI | Raspberry Pi Pico SPI0 |
|---|---|---|---|---|---|
| SCK | D13 | D52 | GPIO 18 | GPIO 14 | GP18 |
| MOSI | D11 | D51 | GPIO 23 | GPIO 13 | GP19 (TX) |
| MISO | D12 | D50 | GPIO 19 | GPIO 12 | GP16 (RX) |
| CS(デフォルト) | D10 | D53 | GPIO 5 | GPIO 15 | GP17 |
| ロジックレベル | 5 V | 5 V | 3.3 V | 3.3 V | 3.3 V |
Unoでは、同じSPI信号がボードの端にある6ピンのICSPヘッダーにも出ています。LeonardoやMicroのようなボードでは、SPIはICSPからしか取れません。CSにD10を使う必要はなく、好きなデジタルピンを使えます。ただし、UnoではD10を入力のままにしないでください。D10が入力の状態でSPIハードウェアがLOWを読むと、コントローラーモードから外れてしまいます。SPI.begin()はこのピンを自動的に出力にするので、ほかの用途のために入力に変えないようにしましょう。ESP32では、ArduinoのSPIオブジェクトはデフォルトでVSPIを使います。GPIOマトリクスのおかげで、SPIをほぼ好きなピンに移すこともできます。Picoでは、SPI0とSPI1をいくつかの異なるピングループに割り当てられます(例:SPI1ならGP10、GP11、GP12)。詳しくは「ピン配置」ツールを見てください。
ArduinoのSPIライブラリ:基本の関数
| 関数 | 何をするか |
|---|---|
| SPI.begin() | SPIハードウェアを開始し、SCK、MOSI、SSピンを出力にします。 |
| SPISettings(速度, MSBFIRST, SPI_MODE0) | デバイスの速度、ビット順、モードを1つのオブジェクトにまとめます。 |
| SPI.beginTransaction(設定) | 設定を適用し、バスをこのデバイス用に確保します。設定の異なるデバイス間の切り替えを安全にします。 |
| SPI.transfer(バイト) | 1バイトを送信し、同時に受信したバイトを返します。 |
| SPI.transfer16(値) | 16ビットの値を一度に送受信します。 |
| SPI.transfer(バッファ, 長さ) | 配列を送信します。受信したバイトは同じ配列に上書きされます。 |
| SPI.endTransaction() | バスを解放します。CSをHIGHにしたあとに呼び出します。 |
最初の例:SPIで74HC595にデータを送る
SPIを理解するいちばん簡単な方法は、74HC595シフトレジスタを使うことです。595は実はMISOのない、もっともシンプルなSPIペリフェラルだからです。ArduinoのMOSIピンを595のDS入力に、SCKをSH_CPに、D10をラッチ(latch)の役割をするST_CPにつなぎます。shiftOut()関数と違い、ハードウェアSPIはバイトを数マイクロ秒で送ります。
1// Donanım SPI ile 74HC595: 8 LED'i iki kabloyla sürmek / 74HC595 over hardware SPI: 8 LEDs from two data wires
2// D11 (MOSI) -> DS (14), D13 (SCK) -> SH_CP (11), D10 -> ST_CP / latch (12)
3#include <SPI.h>
4
5const int LATCH_PIN = 10; // 595'te CS gibi davranır / acts like CS on the 595
6
7void write595(byte value) {
8 SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); // 4 MHz, mod 0 / 4 MHz, mode 0
9 digitalWrite(LATCH_PIN, LOW);
10 SPI.transfer(value); // 8 biti gönder / shift out 8 bits
11 digitalWrite(LATCH_PIN, HIGH); // yükselen kenarda çıkışlara aktar / latch on the rising edge
12 SPI.endTransaction();
13}
14
15void setup() {
16 pinMode(LATCH_PIN, OUTPUT);
17 digitalWrite(LATCH_PIN, HIGH);
18 SPI.begin();
19}
20
21void loop() {
22 for (int i = 0; i < 8; i++) { // yürüyen ışık / running light
23 write595(1 << i);
24 delay(100);
25 }
26}SPIでデータを読む:MCP3008 ADC
全二重通信を確かめるために、MCP3008を使ってみましょう。このICはArduinoに10ビットのアナログ入力を8つ追加します。ESP32の非線形なADCや、3つしかないPicoのアナログ入力の代わりとしても優れています。アナログ読み取りの基本はADCとは?の記事で説明しました。配線:CLK → D13、DOUT → D12、DIN → D11、CS → D10、VDDとVREF → 5V、AGNDとDGND → GND。
1// MCP3008 (8 kanal, 10 bit ADC) okuma / Reading an MCP3008 (8-channel 10-bit ADC)
2// D13 -> CLK, D12 <- DOUT, D11 -> DIN, D10 -> CS/SHDN
3#include <SPI.h>
4
5const int CS_PIN = 10;
6SPISettings mcpSettings(1000000, MSBFIRST, SPI_MODE0); // 1 MHz, mod 0 / 1 MHz, mode 0
7
8int readMCP3008(byte channel) {
9 SPI.beginTransaction(mcpSettings);
10 digitalWrite(CS_PIN, LOW); // cihazı seç / select the chip
11 SPI.transfer(0x01); // başlangıç biti / start bit
12 byte high = SPI.transfer(0x80 | (channel << 4)); // tek uçlu + kanal / single-ended + channel
13 byte low = SPI.transfer(0x00); // kalan 8 bit / remaining 8 bits
14 digitalWrite(CS_PIN, HIGH); // seçimi bırak / release the chip
15 SPI.endTransaction();
16 return ((high & 0x03) << 8) | low; // 10 bitlik sonuç / 10-bit result
17}
18
19void setup() {
20 Serial.begin(9600);
21 pinMode(CS_PIN, OUTPUT);
22 digitalWrite(CS_PIN, HIGH); // başta seçili değil / not selected at start
23 SPI.begin();
24}
25
26void loop() {
27 int raw = readMCP3008(0);
28 float volts = raw * 5.0 / 1023.0;
29 Serial.print("CH0: ");
30 Serial.print(raw);
31 Serial.print(" ");
32 Serial.print(volts, 2);
33 Serial.println(" V");
34 delay(500);
35}ここでは3バイトを送り、同時に3バイトを受け取っています。1バイト目(0x01)はスタートビットだけです。2バイト目ではシングルエンド測定とチャンネル番号を送り、その間にMCP3008が結果の上位2ビットを返します。3バイト目は0x00で、中身に意味はありません。クロックを出して結果の残り8ビットを受け取るためだけに送ります。SPIでは「読む」ことは、つねに何かを送ることを意味します。結果は0〜1023の範囲で、VREF(ここでは5 V)を基準に電圧に換算します。
複数のSPIデバイス:CS線を分ける
SPIとI2Cのいちばん大きな違いは、SPIがアドレスを使わないことです。デバイスはSCK、MOSI、MISOの線を共有し、それぞれにArduinoから専用のCS線を1本ずつ引きます。CSがLOWになっているデバイスだけが受信し、そのデバイスだけがMISOにデータを書き込みます。デバイス3台なら3 + 3 = 6本、5台なら3 + 5 = 8本の線が必要で、デバイスの数に応じてピン数が増えます。一方、74HC595やMAX7219のような一部のICは縦続接続(デイジーチェーン)ができます。1つの出力を次の入力につなぎ、すべてを1本のCSで駆動します。
- 同時にLOWにするCSは1本だけにしてください。2つのデバイスを同時に選ぶとMISO線で衝突し、両方から壊れたデータを読むことになります。
- setup()では、まずすべてのCSピンを出力にしてHIGHにし、それからライブラリを開始します。浮いた状態のCSピンは偶然LOWに見えて、バスを占有してしまうことがあります。
- デバイスごとにbeginTransactionを使いましょう。SDカードは25 MHz、MCP3008は1 MHzを必要とすることがありますが、トランザクションを使えば切り替えのたびに正しい速度とモードが自動で適用されます。
- 割り込みの中でSPIを使う場合は、SPI.usingInterrupt()でライブラリに知らせるか、割り込みの中でSPIの処理をしないようにしてください。
- 各デバイスのCS線に10 kΩのプルアップ抵抗を追加すると、Arduinoの起動中やリセット中にデバイスが勝手に選択されるのを防げます。
SDカードとRFIDを同じバスに:なぜ動かないのか
Arduinoのフォーラムでよく見る質問があります。RC522 RFIDは単体で動き、SDカードモジュールも単体で動くのに、両方を同じSPIバスにつなぐと片方、または両方が応答しなくなるというものです。ソフトウェアでCSピンが正しく設定されていても、原因はたいていハードウェアにあります。安価なSDカードモジュールの多くには、5 V信号を3.3 Vに変換する74LVC125バッファICが載っていて、このICのMISO線を駆動するチャンネルは常に有効になっています。そのため、SDカードのCSがHIGHでもモジュールはMISO線を手放さず、RC522が応答しようとすると、2つの出力が線を取り合うことになります。
- 適切なモジュールを選ぶ:CSがHIGHのときにMISOをハイインピーダンスにするモジュールなら、問題なくバスを共有できます。モジュールが線を手放すかどうかは、CSをHIGHにした状態でMISOを10 kΩで最初はGNDへ、次に5 Vへ引き、テスターで測ると確認できます。
- バッファのイネーブル端子をCSにつなぐ:はんだ付けに慣れているなら、74LVC125のMISOチャンネルのOE端子をGNDから外し、モジュールのCS線につなぐことで根本的に解決できます。
- MISOに直列抵抗を入れる:SDモジュールのMISO出力に1 kΩ程度の直列抵抗を入れると、RC522が線の電位を押し切れるようになります。簡単ですが、速度が制限される応急処置です。
- 別のSPIバスを使う:ESP32なら、SDカードをVSPIに、RC522をHSPIにつなげば2つのデバイスが干渉することはありません。Unoには2つ目のハードウェアSPIがないので、この方法は使えません。
- 初期化の順番に注意する:まずすべてのCSをHIGHにし、次にSPI.begin()、そのあとSD.begin()とPCD_Init()を呼び出します。下の例はこの順番に従っています。
1// Aynı SPI hattında SD kart ve RC522 RFID / SD card and RC522 RFID on the same SPI bus
2// Ortak / shared: D11 MOSI, D12 MISO, D13 SCK. CS: SD -> D4, RC522 SDA(SS) -> D10, RC522 RST -> D9
3#include <SPI.h>
4#include <SD.h>
5#include <MFRC522.h>
6
7const int SD_CS = 4;
8const int RFID_CS = 10;
9const int RFID_RST = 9;
10
11MFRC522 rfid(RFID_CS, RFID_RST);
12
13void setup() {
14 Serial.begin(9600);
15 // Önce TÜM CS pinlerini HIGH yap: hiçbir cihaz hattı meşgul etmesin
16 // First drive ALL CS pins HIGH so no device grabs the bus
17 pinMode(SD_CS, OUTPUT);
18 digitalWrite(SD_CS, HIGH);
19 pinMode(RFID_CS, OUTPUT);
20 digitalWrite(RFID_CS, HIGH);
21
22 SPI.begin();
23 if (!SD.begin(SD_CS)) {
24 Serial.println(F("SD bulunamadi / SD not found"));
25 }
26 rfid.PCD_Init();
27 Serial.println(F("Karti okut / Scan a card"));
28}
29
30void loop() {
31 if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return;
32
33 String uid = "";
34 for (byte i = 0; i < rfid.uid.size; i++) {
35 if (rfid.uid.uidByte[i] < 0x10) uid += "0";
36 uid += String(rfid.uid.uidByte[i], HEX);
37 }
38 rfid.PICC_HaltA();
39
40 File f = SD.open("kartlar.txt", FILE_WRITE); // kütüphane CS'yi kendisi yönetir / the library handles CS itself
41 if (f) {
42 f.print(millis());
43 f.print(";");
44 f.println(uid);
45 f.close();
46 Serial.println("Kaydedildi / Saved: " + uid);
47 } else {
48 Serial.println(F("Dosya acilamadi / File open failed"));
49 }
50}このコードでは、SDライブラリとMFRC522ライブラリがそれぞれ自分のCSピンをLOWやHIGHにします。必要なのは、最初に両方をHIGHにしておくことと、ハードウェアがMISOを正しく共有できるようにしておくことだけです。コードは読み取ったカードのUIDを、時刻の情報と一緒にkartlar.txtファイル(kartlarはトルコ語で「カード」)に書き込みます。Unoではこの2つのライブラリを合わせるとRAMの約半分を使います。F()マクロは定数の文字列をフラッシュに置くことで空きを作ります。
ESP32のVSPIとHSPI:2つの独立したSPIバス
ESP32の内部には4つのSPIユニットがあります。SPI0とSPI1は内蔵フラッシュメモリ用に予約されているため、使えるのはVSPI(SPI3)とHSPI(SPI2)です。両方を使うのは、速度やモードが大きく異なるデバイスを分けたり、上で説明したMISOの競合を避けたりするいちばんすっきりした方法です。Arduinoコアでは、2つ目のバス用にSPIClassオブジェクトを作り、begin()でピンを指定します。下のコードは、SDカードをデフォルトのVSPIで、MCP3008をHSPIで動かし、毎秒の測定値をCSVファイルに追記します。
1// ESP32: iki ayrı SPI hattı / two separate SPI buses
2// VSPI (varsayılan SPI / default SPI): SD kart / SD card -> SCK 18, MISO 19, MOSI 23, CS 5
3// HSPI: MCP3008 -> SCK 14, MISO 12, MOSI 13, CS 15
4#include <SPI.h>
5#include <SD.h>
6
7const int SD_CS = 5;
8const int ADC_CS = 15;
9
10SPIClass hspi(HSPI); // ikinci donanım SPI / second hardware SPI
11SPISettings mcpSettings(1000000, MSBFIRST, SPI_MODE0);
12
13int readMCP3008(byte channel) {
14 hspi.beginTransaction(mcpSettings);
15 digitalWrite(ADC_CS, LOW);
16 hspi.transfer(0x01);
17 byte high = hspi.transfer(0x80 | (channel << 4));
18 byte low = hspi.transfer(0x00);
19 digitalWrite(ADC_CS, HIGH);
20 hspi.endTransaction();
21 return ((high & 0x03) << 8) | low;
22}
23
24void setup() {
25 Serial.begin(115200);
26 pinMode(ADC_CS, OUTPUT);
27 digitalWrite(ADC_CS, HIGH);
28 hspi.begin(14, 12, 13, ADC_CS); // SCK, MISO, MOSI, SS
29
30 if (!SD.begin(SD_CS)) { // SD varsayılan VSPI'yi kullanır / SD uses the default VSPI
31 Serial.println("SD bulunamadi / SD not found");
32 }
33}
34
35void loop() {
36 int raw = readMCP3008(0);
37 File f = SD.open("/olcum.csv", FILE_APPEND); // ekle kipinde aç / open in append mode
38 if (f) {
39 f.printf("%lu,%d\n", millis(), raw);
40 f.close();
41 }
42 Serial.printf("CH0: %d\n", raw);
43 delay(1000);
44}Raspberry Pi PicoでSPIを使う(MicroPython)
Picoには2つのハードウェアSPIユニット(SPI0とSPI1)があり、MicroPythonではmachine.SPIクラスで使います。polarityとphaseのパラメーターはCPOLとCPHAに対応し、両方が0ならモード0です。CSは普通の出力ピンとして自分で管理します。write_readinto()関数は、SPIの全二重のしくみをいちばんよく表す関数で、1つの配列を送りながら、受信したバイトを別の配列に書き込みます。同じMCP3008を、今度は3.3 Vで給電して読んでみましょう。
1# Raspberry Pi Pico: SPI0 ile MCP3008 okuma / reading an MCP3008 on SPI0
2# GP18 -> CLK, GP19 -> DIN (MOSI), GP16 <- DOUT (MISO), GP17 -> CS. MCP3008 VDD ve VREF 3V3 / VDD and VREF on 3V3
3from machine import Pin, SPI
4import time
5
6spi = SPI(0, baudrate=1_000_000, polarity=0, phase=0, # mod 0 / mode 0
7 sck=Pin(18), mosi=Pin(19), miso=Pin(16))
8cs = Pin(17, Pin.OUT, value=1) # başta seçili değil / not selected at start
9
10def read_mcp3008(channel):
11 tx = bytearray([0x01, 0x80 | (channel << 4), 0x00])
12 rx = bytearray(3)
13 cs.value(0) # cihazı seç / select the chip
14 spi.write_readinto(tx, rx) # aynı anda gönder ve al / send and receive at once
15 cs.value(1)
16 return ((rx[1] & 0x03) << 8) | rx[2]
17
18while True:
19 raw = read_mcp3008(0)
20 print("CH0:", raw, "{:.2f} V".format(raw * 3.3 / 1023))
21 time.sleep(0.5)SPI、I2C、UARTの比較
| 特徴 | SPI | I2C | UART |
|---|---|---|---|
| 線の数 | 4本(デバイスを1台追加するごとにCSが1本増える) | 2本(SDA、SCL) | 2本(TX、RX) |
| 一般的な速度 | 1〜80 MHz | 100 kHz・400 kHz・1 MHz | 9600〜115200ボー(数Mbit/sまで) |
| クロック | あり(コントローラーが生成) | あり(コントローラーが生成) | なし。両側が同じボーレートに合わせる |
| 方向 | 全二重 | 半二重 | 全二重 |
| デバイスの選択 | 専用のCS線 | 7ビットのアドレス | 2台のデバイスのみ(1対1) |
| 追加の部品 | 不要 | プルアップ抵抗 | 不要 |
| 距離 | 短い(基板上、数十cm程度) | 短い(1 m程度) | 数メートル。RS-485ならはるかに長い |
| 代表的なモジュール | SDカード、RC522、TFT、nRF24L01、MAX7219 | OLED、I2C LCDバックパック、RTC、BME280、MPU6050 | GPS、HC-05、SIM800L、ESP-01、パソコン |
どれを使うかはたいていモジュールで決まりますが、自分で設計するときは次のルールが役立ちます。速度とデータ量が重要なら(ディスプレイの画像、ファイル、音声)SPI、少ない線で遅いセンサーをたくさんつなぐならI2C、2台のデバイスをテキストベースのシンプルな接続でつなぎたいならUARTです。UARTの詳細はUARTのシリアル通信の記事にあります。1つのプロジェクトで3つすべてを使うこともよくあります。たとえば、I2Cでセンサーを読み、SPIでSDカードに書き込み、UARTでGPSから位置を取得するデータロガーです。
SPIを使うモジュールとプロジェクトのアイデア
- カード式のドアロックと入室記録:RC522でカードを読み、日付とUIDをSDカードに書き込みます。
- 温度ロガー:センサーのデータをCSVとしてmicroSDカードに記録し、Excelでグラフにします。
- スクロール表示の電光掲示板:縦続接続したMAX7219 LEDマトリクスモジュールに時刻やメッセージを表示します。
- カラーのインターフェース:ILI9341 TFTディスプレイにグラフィックやメニューを描き、タッチパネルも同じSPIバスにつなぎます。
- ワイヤレスリモコン:2つのnRF24L01モジュールで、ロボットカーにコマンドを送ります。
- 多チャンネルの測定:MCP3008を使って、ポテンショメーター8個のMIDIコントローラーや、土壌水分センサー8個の水やりシステムを作ります。
よくある間違いと対処法
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| いつも0xFFか0x00が読める | MISOが未接続、MOSIとMISOが逆、またはデバイスに電源が来ていない | DINをMOSIに、DOUTをMISOにつなぎ、デバイスのVCCを測る |
| 値がでたらめ、または1ビットずれている | SPIモードが違う、またはデバイスにとってクロックが速すぎる | データシートのモードを使い、速度を1 MHzに下げて再度試す |
| 単体では動く2つのデバイスが一緒だと動かない | 片方のモジュールがMISOを手放さない、または起動時にCSピンがHIGHになっていない | まずすべてのCSをHIGHにする。MISOを手放さないモジュールを交換するか、別のSPIバスを使う |
| UnoでSPIが突然止まる | D10を入力として使い、LOWに引いていた | D10は出力のままにする。CSとして使うこともできる |
| HSPIをつなぐとESP32が起動しない | 起動時にGPIO 12がHIGHになっている | プルアップ付きのMISOをVSPIに移すか、MISOに別のピンを選ぶ |
| 長い配線でランダムにエラーが出る | 高速な信号での反射とノイズ | 配線を短くし、速度を下げ、GND線を信号線に沿わせて配線する |
SPIは、SDカードからカラーディスプレイまで、Arduinoの世界の高速モジュールに共通する言葉です。4本の線、CSの考え方、4つのモードを覚えれば、新しいSPIモジュールの接続は数分で終わります。BlueGraysアプリでは、RFIDカードの読み取りとドアロック、microSD温度ロガー、MAX7219ドットマトリクス時計のプロジェクトを、回路図とコード付きでステップごとに紹介しています。
よくある質問
SPIは何の略ですか?
SPIはSerial Peripheral Interface(シリアル・ペリフェラル・インターフェース)の略です。コントローラーがSCK、MOSI、MISO、CSの4本の線で、ペリフェラルと高速かつ同期的にデータをやり取りするための方式です。
MOSIとMISOとは何ですか?
MOSI(Master Out Slave In)はコントローラーからペリフェラルへ向かうデータ線で、MISO(Master In Slave Out)はペリフェラルからコントローラーへ戻るデータ線です。新しい呼び方ではCOPIとCIPOとも呼ばれます。モジュールでは、たいていDINをMOSIに、DOUTをMISOにつなぎます。
Arduino UnoのSPIピンはどれですか?
Arduino UnoとNanoでは、SCKがD13、MISOがD12、MOSIがD11、デフォルトのCSがD10です。同じ信号はボード上の6ピンのICSPヘッダーにも出ています。Arduino Megaでは、SCKがD52、MISOがD50、MOSIがD51、CSがD53です。
SPIとI2Cの違いは何ですか?
SPIは4本の線を使う全二重の方式で、数十MHzまで速くできますが、デバイスごとに専用のCS線が必要です。I2Cは2本の線だけを使い、アドレスでデバイスを選びますが、100〜400 kHzのようにずっと低い速度で動作し、プルアップ抵抗が必要です。
1つのSPIバスに何台のデバイスをつなげますか?
理論上の上限はありません。デバイスごとにコントローラー側の空きCSピンが1本あれば十分です。ただし実際には、バスの容量と配線の長さが増えるにつれて、高速時に問題が出てきます。ピンが足りなくなったら、74HC138デコーダーでCS線を増やすか、縦続接続できるデバイスを使ってください。
SDカードとRFIDリーダーが同じSPIバスで動かないのはなぜですか?
安価なSDカードモジュールの多くに載っているレベル変換回路は、CSがHIGHでもMISO線を手放さず、RC522の応答を壊してしまいます。MISOを正しく手放すモジュールを使う、バッファICのイネーブル端子をCSにつなぐ、またはESP32で2つのデバイスを別々のSPIバスに分けることで解決できます。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
RFIDの値の読み取り
建物のキーフォブや入館カードには、見えない識別番号が保存されています。このプロジェクトでは、その番号を読み取ります。カードをRFIDリーダーに近づけると、Arduinoがカードの番号をコンピューターの画面に表示します。カード式のドアや錠のプロジェクトで、どのカードに解錠を許可するかを決めるための第一歩です。

RFID認証
マンションのカード式入館システムと同じ仕組みです。カードをRFIDリーダーに近づけると、Arduinoがカードの番号を登録済みの番号と比較します。登録済みのカードなら緑色、未登録のカードなら赤色のLEDが3秒間点灯し、結果はコンピューターの画面にも表示されます。カード式のドアや錠のプロジェクトの基礎をここで学べます。

ESP32 RFID RC522カードリーダー(LCD付き)
このプロジェクトでは、MFRC522を搭載したRC522 RFIDモジュールをSPIでESP32に接続し、カードやタグのUIDを読み取ります。ドアロック、会員システム、入退室管理などの基礎となる仕組みです。読み取ったUIDはLCDディスプレイに表示します。このバージョンでは、許可されたカードのリストとの照合は行いません。注意:RC522は3.3V専用です。5Vを供給するとモジュールが焼損します!

ESP32のRFIDドアロック(RC522 + サーボモーター + OLED + ブザー + LED)
ESP32、RC522 RFIDモジュール、SG90サーボモーター、SSD1306 OLEDディスプレイ、アクティブブザー、LED2個を使ってスマートドアロックを作ります。カードを読み取ると、UIDをコード内に登録した許可済みカードの一覧と照合します。許可されたカードなら、サーボモーターを回して解錠し、緑色のLEDが点灯して短い音が2回鳴ります。5秒後に自動で施錠します。許可されていないカードでは赤色のLEDが点灯し、短い間隔で3回音を鳴らして警告し、読み取ったUIDをOLEDに表示します。実際のIoTセキュリティの仕組みを実践的に学べます。

ESP32温度データロガー(DHT22 + DS1307 RTC + microSD + OLED)
ESP32で日時付きの温度・湿度記録システムを作ります!DHT22で1分ごとに温度と湿度を読み取り、DS1307 RTCから取得した日時とともにmicroSDカードへCSV形式で記録します。OLEDには最新の温度を大きな文字で表示し、湿度のパーセント値、日時、総記録数も表示します。SDカードの/temp_log.csvファイルはExcel、Google Sheets、Pythonのpandasで開いて分析できます。温室の監視、家や部屋の温度管理、実験室のデータ収集、食品の保管環境や養蜂箱の監視に適しています。

ドットマトリクス時計とカレンダー(MAX7219、DS1307)
机や壁に置き、時刻と日付をスクロール表示する光るパネルです。8x32のLEDディスプレイには大きな数字の時計が表示され、コロンの2つの点が毎秒点滅します。毎分の初めに曜日と日付がスクロールし、その後、時計表示に戻ります。時計モジュールには専用の電池があるため、電源を抜いても時刻を保持します。

32x8 LEDマトリクス4枚でアニメーションと文字表示
横長のLEDマトリクスモジュールを4枚上下に並べると、1,000個以上の光の点からなる正方形のディスプレイになります。まず映画のように文字が上から雨のように降り、続いてカーテンが開いて2行の文字がスクロールし、その間を波やひし形が流れていきます。一連の演出はちらつかず滑らかに動き、終わると自動で最初から繰り返します。

ピクセルスネーク:8x8マトリクス
定番のスネークゲームを自分の手で作って遊びましょう。カラフルな8x8のLEDマトリクスで、ジョイスティックを使って緑のヘビを動かし、赤いリンゴを食べます。リンゴを食べるたびにヘビが長くなり、少しずつ速くなって、小さな数字ディスプレイのスコアも増えます。壁や自分のしっぽにぶつかると画面が赤く3回点滅し、ボタンを押すと再び始められます。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

