戻る

Arduinoでシリアル、I2C、SPIをマスターする — Velxioで学ぶ

はじめに

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

Velxioシミュレーターキャンバス上のマルチプロトコル回路図

回路の解説

この例ではArduino Unoボード自体のみを使用し、外部部品は必要ありません。Arduinoの内蔵ペリフェラルがすべてを提供します。

すべての接続はArduino内部で行われるため、配線は不要です。Velxioキャンバスにはピンラベル付きのUnoが表示され、コンポーネントピッカーを開いて仮想コンポーネントを確認できます。

カテゴリ別に部品をブラウズするVelxioコンポーネントピッカーモーダル

コードの解説

スケッチをステップごとに分解して説明します。

ライブラリと定義

#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つのプロトコルすべてを初期化します。その後、以下の処理を行います。

  1. I2Cバスをスキャンしてアドレス1から126のデバイスを検出。
  2. EEPROMをテストし、アドレス0と1に値42と99を書き込み、読み戻して検証。
  3. SPIをテストし、3バイト(0xAA0x550x42)を送信して受信バイトを表示。

ループ

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がアイドル状態でもシリアル出力が継続されることを示しています。

主要な概念

よくある落とし穴とデバッグのヒント

推奨拡張機能

  1. 実際のセンサーを追加:DS1307 RTCと24Cxx EEPROMをI2Cバスに、SDカードモジュールをSPIに接続。
  2. データをEEPROMに記録:センサー測定値を保存し、リセット後に取得。
  3. 割り込みを使用:ボタン押下でSPI転送をトリガー。
  4. コマンドインターフェースを実装:シリアルコマンドを解析してI2CとSPIの操作を制御。

実際に試してみよう

3つのプロトコルが実際に動作するのを見る準備はできましたか?Velxioでライブサンプルを開き、コンパイルして実行してください。ArduinoがI2Cバスをスキャンし、EEPROMをテストし、SPI転送を行い、その後RTC時刻を連続表示するシリアルモニター出力を確認しましょう。

Velxioでマルチプロトコルデモを起動

コンパイルと実行のコントロールがあるVelxioエディターツールバー シミュレーションビューポート上部のVelxioキャンバスツールバー

ハッピーハッキング!


共有:
David Montero

執筆者

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

関連記事


前の記事
VelxioシミュレーターでRaspberry Pi PicoのLEDを点滅させる
次の記事
SPIループバックテスト:Velxioでプロトコルをマスターする