○ ArduinoでI2C通信

I2C通信とは、シリアル通信の部品版みたいなもので、近距離でセンサーや集積回路同士で接続します。
通信するには、マスターとスレーブに別れていて、一台のマスターに複数のスレーブが接続されます。
スレーブの識別には固有のアドレスが割り振られており、そのアドレスによりマスターが命令を出してスレーブが動作するという仕組みになっています。

接続には最低3本の線を接続する必要があります。
SDA : データ
SCL : クロック
GND

SDAはSDA同士で、SCLはSCL同士で接続します。
シリアル通信のように送信と受信を交互に接続する必要はないです。
SDAとSCLはVccに対してプルアップする必要があり、適当な抵抗を使用します。
(プルアップですので、データの流れていない時は常に1になります。)

Arduinoではポートの内部プルアップを使って、抵抗を省く事も出来ます。


■ 送受信の大まかな流れ
▼ スレーブにデータを送信する場合


▼ スレーブからデータを受信する場合

前後の水色の 0 はスタート・ストップビットです。
ACKは一つのビットであり、 0 です。
データを受信した場合には、必ずACKビットを出力します。

いずれにせよ、マスターが中心となって送受信する方法となっていますので、スレーブから呼ばれるという事は無いです。
(そもそも、マスターにはアドレスが無いです)


■ ArduinoのI2Cポート

Arduino UNO R3 では、A4:SDA A5:SCL となっています。
また、裏面を見ると個別のピンとしても用意されています。(A4 A5とつながっている)


Arduino Mega 2560 Rev3 では、D20:SDA、D21:SCL となっており、表の印刷にも書かれています。



■ Arduino同士でI2C通信を行う
まずは、2台のArduinoを用意してSDAとSCLとGNDを接続しました。
公式のWireライブラリを用いるとArduinoの内部でプルアップが行われるのでプルアップ抵抗は必要ないです。



■ スレーブ側のプログラムの作成
スレーブ側では、自信のスレーブアドレスを0x00としました。
受信した値に2を足して保持しリクエストが来たら値を返します。
#include <Wire.h>
//スレーブアドレス
#define ADDRESS 0x00

int i;
//マスタからデータのリクエストが来た(送信)
void wire_onRequest() {
  Wire.write(i);
}

//マスタからデータが送られてきた(受信)
void wire_onReceive(int c) {
  if (Wire.available()) {//読み取る事ができるバイト数が0以上なら
    i = Wire.read();     //値を一つ読む
    i = i + 2;
  }
}

void setup() {
  Wire.begin(ADDRESS);
  Wire.onRequest(wire_onRequest);
  Wire.onReceive(wire_onReceive);
}

void loop() {}


■ マスター側のプログラムの作成
アドレス0x00のスレーブに接続し値を送信します、その後、受信した値と共にシリアルモニタに出力します。
値に1を足しながらループします。
#include <Wire.h>
//接続先のアドレス
#define ADDRESS 0x00

int i;
void setup() {
  Serial.begin(9800);
  Wire.begin();
  Wire.endTransmission();
}

void loop() {
  //アドレス0x00のスレーブに接続
  Wire.beginTransmission(ADDRESS);
  Wire.write(i);
  Wire.endTransmission();

  Wire.requestFrom(ADDRESS, 1);//アドレス0x00のスレーブに接続し1byte取得する
  if (Wire.available()) {
    Serial.print(i);
    Serial.print(" : ");
    Serial.println(Wire.read());
  }
  i++;
  delay(1000);
}
シリアルモニタを開くと、送信した値に+2されて受信しているのが確認できます。



▼ 一度に複数の値を送信する
Wire.endTransmission() を呼ぶ前に Wire.write() された値はまとめて送信されます。
  Wire.beginTransmission(ADDRESS);
  Wire.write(1);
  Wire.write(2);
  Wire.write(3);
  Wire.write(4);
  Wire.endTransmission();

▼ 一度に受信した値をすべて読む
Wire.availableでread() で読み取ることができるバイト数を取得し、 0 になるまで Wire.read() します。
//マスタからデータが送られてきた(受信)
void wire_onReceive(int c) {
  while(Wire.available()) {//読み取る事ができるバイト数が0以上なら
    i = Wire.read();       //値を一つ読む
    Serial.print(i);
    Serial.print(" ");
  }
  Serial.print("\n");
}
上の"一度に複数の値を送信する"と組み合わせると、このような出力結果になります。



■ マスター側のプログラムで通信に障害が起きても復旧するようにする
2台のArduinoでI2Cの通信中に、スレーブ側の電源を抜くと固まってしまいました。
そこで、タイムアウトを設定して復旧するようにします。
前回作成したマスター側のプログラムを改造し、1行毎にシリアルモニタに値を表示するように改造しました。
赤文字部分にて、タイムアウトの時間1000msとタイムアウトした場合の処理を書いています。
#include <Wire.h>
//接続先のアドレス
#define ADDRESS 0x00

int i;
void setup() {
  Serial.begin(9800);
  Wire.begin();
  Wire.endTransmission();
  Wire.setWireTimeout(1000);//タイムアウトする時間を設定
}

void loop() {
  //タイムアウトした場合には初期化する
  if (Wire.getWireTimeoutFlag()) {
    Wire.clearWireTimeoutFlag();
    Wire.end();
    setup();
  }
  //アドレス0x00のスレーブに接続
  Serial.print("1");
  Wire.beginTransmission(ADDRESS);
  Serial.print("2");
  Wire.write(i);
  Serial.print("3");
  Wire.endTransmission();
  Serial.print("4");
  Wire.requestFrom(ADDRESS, 1);//アドレス0x00のスレーブに接続し1byte取得する
  Serial.print("5");
  if (Wire.available()) {
    Serial.print("6");
    /*Serial.println(*/Wire.read()/*)*/;
  }
  Serial.print("\n");
  delay(1000);
}

( 1 ) の部分でスレーブ側のArduinoの電源をOFF/ONしました、その後、固まる事なく ( 2 )
UNO R3 をマスターにした場合には10秒ほどで復帰しました。
Mega 2560 Rev3 をマスターにした場合には即復帰しました。



▲トップページ > マイコンなど