ソフト I2C ライブラリ SoftWire の使い方と、それを用いた I2C ブリッジの製作

[ インスタレーションサバイバルガイド ]
ソフトウェア I2C - SoftWire の使い方

# はじめに
Arduino には I2C の機能が備わっていますが、1 チャンネルのみです。I2C はパーティライン方式でもあり、また、各デバイスはアドレスを有していますので、1 チャンネルのみでも複数の I2C デバイスをスレーブとして操作する場合、それほど支障はないかと思います。

しかし、I2C デバイスの数が多く、ひとつのパーティラインでは、もうこれ以上 I2C デバイスをぶら下げられない場合や、Arduino 自体がスレーブでもありマスターでもある場合などはもうひとつ I2C チャネルが欲しくなります。

そのような場合、ソフトウェアによる I2C 通信という手段があります。ソフトウェア I2C については、[SoftI2CMaster](https://playground.arduino.cc/Main/SoftwareI2CLibrary) を使うのが定番のようです。

このライブラリは I2C のマスターとして機能するライブラリで、i2c_ という接頭語で始まる関数 - i2c_init() など - が用意されています。これらの関数についてはすでに分かりやすい解説文書が書かれています。しかし、SoftI2CMaster に同梱されている SoftWire については日本語の記事自体もすくないようですので、本稿で少し解説を試みます（Wire 互換モジュールなので、そもそも説明の必要が無い、という話なのかもしれませんが…）。

# SoftWire とは何か？
SoftI2CMaster のページでも説明されていますし、パッケージ（master.zip）にも同梱されているソフトウェアによる I2C ライブラリです。i2c_* 系の関数群と異なるのは、SoftWire クラスを経由して使用するところです。

この SoftWire はハードウェア I2C の Wire モジュールと同じ使い方ができるように設計されたライブラリ（互換ライブラリ）です。とは言え、完全互換ではないので少し注意が必要です。

# SoftWire の使い方
SoftWire は SoftI2CMaster を利用するので、SoftWire.h のインクルードの前に SCL_PIN や SCL_PORT など、ソフトウェア I2C で使用する SCL と SDA の設定を define を用いて済ませておきます。ちなみに、SoftWire.h より SoftI2CMaster.h をインクルードするので、SoftWire.h のみをインクルードすれば十分です。

その後、グローバル変数として SoftWire のインスタンスを生成しておきます。これが結構重要で、例えば setup 関数内で SoftWire のインスタンスを生成してしまうと、そのインスタンスは setup 終了時に破棄されてしまいます。また、SoftWire は Wire 同様、メソッドして各種の機能を呼び出す方式ですが、setup 関数内で宣言された変数には、そもそも loop などの関数からアクセスすることはできません。

グローバル変数としてインスタンスを生成しておき、begin メソッドで初期化し、beginTransmission で送信開始処理を行い、write でデータを送り、endTransmission で送信の終了処理を行います。なお、SoftWire もマスター側のみでありスレーブとして動作することはできませんので、begin メソッドの引数に I2C アドレスを指定することはできません。ここは標準の Wire モジュールとは異なる部分となります。

# 使用例 - Arduino による I2C ブリッジの例
ハードウェア I2C でスレーブとして受信したデータを、ソフトウェア I2C を使用して、マスターとして、送信先アドレスだけ変更して転送します（下図参照）。もちろん、使用する I2C の信号線が異なるので、全く同じアドレスでも問題ありません。

    I2C IN  +--------------------------+
    ------->+ HW I2C                   |
            |         Arduino          |
            |        I2C Bridge        | I2C OUT
            |                    SW I2C+----------->
            +--------------------------+

ソースコードを以下に示します。

```c:I2CBridge.ino
#include <Wire.h>

// B0 を SCL に、B1 を SDA にする
#define SCL_PIN 0
#define SCL_PORT PORTB
#define SDA_PIN 1
#define SDA_PORT PORTB
#define I2C_PULLUP 1
// ソフトウェア I2C で使用するピンを設定してから、インクルードする
#include <SoftWire.h>

static SoftWire gSWire=SoftWire();  // SoftWire のインスタンス生成

const int I2C_SLAVE_ADDRESS=0x04;  // スレーブとしてのアドレスは 0x04
const int I2C_TO_ADDRESS=0x12;     // 転送先アドレスは 0x12

void setup() {
  Serial.begin(9600);  // 動作状況をシリアルに出力するために初期化

  // (Hardware) I2C
  Wire.begin(I2C_SLAVE_ADDRESS);
  Wire.onReceive(onRecv);

  // Software I2C
  gSWire.begin();

  Serial.print("I2C BRIDGE STARTUP -- OUT\n");
}

void loop() {
  // empty
}

static char gBuf[255];
static unsigned char gI2CDataBuf[32];

void onRecv(int inNumOfRecvBytes) {
  sprintf(gBuf,"Recv NumOfBytes=%d : ",inNumOfRecvBytes);
  Serial.print(gBuf);
  
  if(inNumOfRecvBytes>sizeof(gI2CDataBuf)) {
    Serial.print("NOT ENOUGH BUFFER MEMORY.\n");
    return;
  }
  
  int index=0;
  while(Wire.available()>0) {
    unsigned char c=Wire.read();
    sprintf(gBuf,"%02X ",c);
    Serial.print(gBuf);
    gI2CDataBuf[index++]=c;
  }
  Serial.print("DONE\n");
  
  const int numOfSend=index;  // 説明のため
  gSWire.beginTransmission(I2C_TO_ADDRESS);
    if(gSWire.write(gI2CDataBuf,numOfSend)!=numOfSend) {
      Serial.print("BRIDGE ERROR\n");
    }
  gSWire.endTransmission();
}
```

# まとめ
Wire と（ほぼ）互換の SoftWire クラスを紹介し、その使い方とサンプルプログラムを示しました。実際にブリッジされているかどうかについては、複数の Arduino を用いて確認する方法があります。ただ、これは、複数の Arduino が連携するシステムとなりますので、少々コツが必要です。これについては別記事で説明したいと思います。

間違っている部分などありましたら、ご指摘いただければ幸いです。