はじめに
通信プロトコルは組み込みシステムの基盤です。センサーの読み取り、SDカードとの通信、デバッグメッセージの出力など、これらはすべてシリアル(UART)、I2C(TWI)、SPIの3つの主要プロトコルのいずれかを使用しています。このチュートリアルでは、Velxioブラウザ内シミュレーターを使ってArduino Uno上でこれら3つを同時に実行します。I2Cバスのスキャン、EEPROMの読み書き、SPI転送の実行、そしてすべてをシリアル経由でログ出力する方法を学びます。

回路の解説
この例ではArduino Unoボード自体のみを使用し、外部部品は必要ありません。Arduinoの内蔵ペリフェラルがすべてを提供します。
- シリアル(UART):ピン0(RX)と1(TX)をシリアルモニターとの通信に使用。
- I2C(TWI):ピンA4(SDA)とA5(SCL)を使用。Wireライブラリによって内部プルアップ抵抗が有効になります。
- SPI:ピン10(SS)、11(MOSI)、12(MISO)、13(SCK)を使用。ピン10はスレーブセレクト出力として設定されます。
すべての接続はArduino内部で行われるため、配線は不要です。Velxioキャンバスにはピンラベル付きのUnoが表示され、コンポーネントピッカーを開いて仮想コンポーネントを確認できます。

コードの解説
スケッチをステップごとに分解して説明します。
ライブラリと定義
#include <Wire.h>
#include <SPI.h>
#define DS1307_ADDR 0x68
#define EEPROM_ADDR 0x50
#define SS_PIN 10
I2C用のWireライブラリとSPI用のSPIライブラリをインクルードします。2つのI2Cアドレスを定義しています。0x68はDS1307 RTC用(物理的には存在しませんがスキャン対象)、0x50は24Cxx EEPROM用です。SPIスレーブセレクトピンはデジタルピン10に設定します。
ヘルパー関数
byte bcdToDec(byte val) {
return ((val >> 4) * 10) + (val & 0x0F);
}
void readRTC(byte &hr, byte &min, byte &sec) {
Wire.beginTransmission(DS1307_ADDR);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(DS1307_ADDR, 3);
sec = bcdToDec(Wire.read() & 0x7F);
min = bcdToDec(Wire.read());
hr = bcdToDec(Wire.read() & 0x3F);
}
bcdToDec関数はBCD(Binary‑Coded Decimal)を10進数に変換します。readRTCはDS1307 RTCから秒、分、時を読み取ります。& 0x7Fと& 0x3Fのマスクで未使用ビットをクリアします。
void writeEEPROM(byte reg, byte value) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
delay(5);
}
byte readEEPROM(byte reg) {
Wire.beginTransmission(EEPROM_ADDR);
Wire.write(reg);
Wire.endTransmission();
Wire.requestFrom(EEPROM_ADDR, 1);
return Wire.available() ? Wire.read() : 0xFF;
}
これらの関数は、アドレス0x50のI2C EEPROMに対して1バイトの書き込みと読み取りを行います。delay(5)はEEPROMが書き込みサイクルを完了する時間を確保します。
byte spiTransfer(byte data) {
digitalWrite(SS_PIN, LOW);
byte result = SPI.transfer(data);
digitalWrite(SS_PIN, HIGH);
return result;
}
この関数は全二重SPI転送を行います。SSをLowにし、1バイト送信して応答を受信し、SSをHighに戻します。
セットアップ
void setup() {
Serial.begin(9600);
Wire.begin();
pinMode(SS_PIN, OUTPUT);
digitalWrite(SS_PIN, HIGH);
SPI.begin();
Serial.println("===================================");
Serial.println(" Multi-Protocol Demo");
Serial.println(" Serial (USART) + I2C (TWI) + SPI");
Serial.println("===================================");
Serial.println();
// ── I2C: バススキャン ──
Serial.println("[I2C] Scanning bus...");
int found = 0;
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print(" Found device at 0x");
if (addr < 16) Serial.print("0");
Serial.println(addr, HEX);
found++;
}
}
Serial.print(" ");
Serial.print(found);
Serial.println(" device(s) on I2C bus.");
Serial.println();
// ── I2C: EEPROMの書き込み/読み取り ──
Serial.println("[I2C] EEPROM write/read test:");
writeEEPROM(0, 42);
writeEEPROM(1, 99);
byte v0 = readEEPROM(0);
byte v1 = readEEPROM(1);
Serial.print(" Wrote 42, read ");
Serial.print(v0);
Serial.println(v0 == 42 ? " [OK]" : " [FAIL]");
Serial.print(" Wrote 99, read ");
Serial.print(v1);
Serial.println(v1 == 99 ? " [OK]" : " [FAIL]");
Serial.println();
// ── SPI: 転送テスト ──
Serial.println("[SPI] Transfer test:");
byte spiData[] = {0xAA, 0x55, 0x42};
for (int i = 0; i < 3; i++) {
byte rx = spiTransfer(spiData[i]);
Serial.print(" TX=0x");
if (spiData[i] < 16) Serial.print("0");
Serial.print(spiData[i], HEX);
Serial.print(" RX=0x");
if (rx < 16) Serial.print("0");
Serial.println(rx, HEX);
}
Serial.println();
Serial.println("Setup complete. Reading RTC...");
Serial.println();
}
setup()では3つのプロトコルすべてを初期化します。その後、以下の処理を行います。
- I2Cバスをスキャンしてアドレス1から126のデバイスを検出。
- EEPROMをテストし、アドレス0と1に値42と99を書き込み、読み戻して検証。
- SPIをテストし、3バイト(
0xAA、0x55、0x42)を送信して受信バイトを表示。
ループ
void loop() {
// ── シリアル: 2秒ごとにRTC時刻を表示 ──
byte hr, min, sec;
readRTC(hr, min, sec);
Serial.print("[RTC] ");
if (hr < 10) Serial.print("0");
Serial.print(hr);
Serial.print(":");
if (min < 10) Serial.print("0");
Serial.print(min);
Serial.print(":");
if (sec < 10) Serial.print("0");
Serial.print(sec);
Serial.print(" | Uptime: ");
Serial.print(millis() / 1000);
Serial.println("s");
delay(2000);
}
2秒ごとにループがRTC時刻を読み取り、システム稼働時間とともに表示します。I2CとSPIがアイドル状態でもシリアル出力が継続されることを示しています。
主要な概念
- シリアル(UART):非同期、ポイントツーポイント。デバッグ出力やPC、GPSモジュールなどとの通信に使用。
- I2C:同期、マルチマスター、2線式(SDA、SCL)。各デバイスに固有のアドレス。センサーやEEPROMに最適。
- SPI:同期、全二重、4線式(MOSI、MISO、SCK、SS)。I2Cより高速だが、より多くのピンを必要とする。
- バススキャン:I2Cアドレスを順に調べて接続デバイスを発見する方法。
- EEPROMの読み書き:電源を切ってもデータを保持する不揮発性メモリ。
- SPI転送:データの送信と受信を同時に行う。
よくある落とし穴とデバッグのヒント
- I2Cプルアップ抵抗:Wireライブラリは内部プルアップを有効にしますが、長いバスでは外部抵抗(4.7kΩ)を推奨します。
- SPIのSSピン:SSは常に出力として設定し、未使用時はHighに保つことで誤ってスレーブモードになるのを防ぎます。
- EEPROM書き込みタイミング:書き込み後、次の操作まで最低5ms待機してください。
- シリアルモニターのボーレート:シリアルモニターを9600ボーに設定してください。
- 外部デバイスなし:このシミュレーションでは、I2Cスキャンでデバイスは見つかりません(Arduino内部のTWIを除く)。EEPROMテストはシミュレーターに組み込まれた仮想EEPROMを使用します。
推奨拡張機能
- 実際のセンサーを追加:DS1307 RTCと24Cxx EEPROMをI2Cバスに、SDカードモジュールをSPIに接続。
- データをEEPROMに記録:センサー測定値を保存し、リセット後に取得。
- 割り込みを使用:ボタン押下でSPI転送をトリガー。
- コマンドインターフェースを実装:シリアルコマンドを解析してI2CとSPIの操作を制御。
実際に試してみよう
3つのプロトコルが実際に動作するのを見る準備はできましたか?Velxioでライブサンプルを開き、コンパイルして実行してください。ArduinoがI2Cバスをスキャンし、EEPROMをテストし、SPI転送を行い、その後RTC時刻を連続表示するシリアルモニター出力を確認しましょう。

ハッピーハッキング!