1. I2C通信とは
I2CとはInter-Integrated Circuitの略でフィリップス社(半導体部門が独立して現在はNXPセミコンダクターズ社)が提唱する通信方式でセンサ等に広く採用されている。以前採り上げた非同期シリアル通信であるUARTと異なりI2Cは同期式のシリアル通信の1つである。概略としては、マスターとスレーブに役割を分け、データ入出力用の信号線:SDAと同期するためのクロック信号線:SCLの2本の信号線を用いて通信を行う。1つのマスターに対して複数のスレーブを接続することができる。マスターからはスレーブのアドレスを指定し、指定したアドレスと一致するスレーブがデータの送受信を行うことができる。詳細については、 2-17 I2Cとは | ヨースケ先生のラピステクノロジー マイコン豆知識!などが参考になる。
2. ArduinoによるI2C通信
Arduino同士を接続して、I2C通信させるための配線図を下に示す。基本的には同期信号線:SCLとデータ通信用信号線:SDAを接続すればよい。
次にスケッチについてはArduinoの公式HPのReferencce:https://www.arduino.cc/en/Tutorial/MasterWriterを用いている。処理の概要はマスター内で変数xをカウントアップし、xの値をスレーブに送信し、スレーブ側ではxの値を受信してシリアルモニタに表示するというものである。
// Master #include <Wire.h> void setup() { Wire.begin(); // join i2c bus (address optional for master) } byte x = 0; void loop() { Wire.beginTransmission(2); // transmit to device #2 Wire.write("x is "); // sends five bytes Wire.write(x); // sends one byte Wire.endTransmission(); // stop transmitting x++; delay(500); }
//Slave #include <Wire.h> void setup() { Wire.begin(2); // join i2c bus with address #2 Wire.onReceive(receiveEvent); // register event Serial.begin(9600); // start serial for output } void loop() { delay(100); } // function that executes whenever data is received from master // this function is registered as an event, see setup() void receiveEvent(int howMany) { while(1 < Wire.available()) // loop through all but the last { char c = Wire.read(); // receive byte as a character Serial.print(c); // print the character } int x = Wire.read(); // receive byte as an integer Serial.println(x); // print the integer }
まずマスター側のスケッチについて説明する。I2Cを利用するためにWireライブラリをロードし、Setup()
内でWire.begin()
とする。スレーブにデータを送信するためには、loop()
内にWire.beginTransmission(2)
とすることでアドレス番号2のSlaveに対してデータを送信するという設定になる。なおI2C対応のセンサであればアドレスは固有の値として、説明書等に記されている。例えばI2Cインターフェースのセンサを接続する(1)温度センサTMP102 | 電子工作(MAKE)では0x49というアドレスを設定している。データを送信するところはWire.write()
として、最後にWire.endTransmission()
で一連の送信を終了する。
次にデータを受信するスレーブ側について説明する。マスター側と同様にWireライブラリをロードし、Setup()
内でWire.begin(2)
とすることでアドレス番号を2に設定する。またWire.onReceive(receiveEvent)
とするとマスターから通信があった場合にrecieveEvent関数(別途定義する)を呼び出して実行する。receiveEvent関数の中身は、Wire.available()
によりWire.read()
で読み出せるバイト数が1より大きければWire.read()
でデータの読み出しを行い、シリアルモニタに表示する。
3. I2C通信の信号
I2C通信中のSDA,SCLの信号波形を調べる。マスターとスレーブの間にブレットボードを入れて、オシロスコープに接続したのが下図になる。
次にI2C通信により"42"をアドレス2のスレーブへ送信しているときのSCLおよびSDAの信号を示す。SDAの波形ではまずアドレス番号が上位ビットから送られ(UARTの場合は下位ビットからだった)、アドレスが正しければ続けてデータが送信される。今回はアドレス番号である"2"(00000001)が最初に送信されて、次に"42"(00101010)が送信されている。SCLは同期用のパルス信号でデフォルトの周波数は100kHz(Standard mode)となる。100kHz以外の設定するにはWire.setClock()
関数で周波数を変更できるが、指定できる周波数は400kHz(Fast mode)の他、プロセッサーによって10kHz(Low speed mode),1MHz(Fast mode plus),3.4MHz(High speed mode)をサポートしている場合がある。
なおMaster側のアドレスとSlave側のアドレスが一致しない場合のSDA波形を下に示す。アドレス番号である2は送信されているが、スレーブ側を2以外のアドレスに設定しておくとデータが送信されないことが確認できる。
4. 複数スレーブでのI2C通信
最後にスレーブが複数ある場合のI2C通信の例を示す。下図はマスターとしているArduinoから2台のスレーブとするArduinoを接続している。マスター側から送信するアドレスを切り替えることで2台のスレーブに対して通信することができる。 以下のスケッチでは、カウントアップするxが偶数のときアドレス番号2に設定したSlave1、奇数のときにアドレス番号3に設定したSlave2と通信を行い、マスターからデータ受信したスレーブのオンボードLEDを点灯するようにしている。
//Master #include <Wire.h> void setup() { Wire.begin(); // join i2c bus (address optional for master) } byte x = 0; int addr=0; void loop() { if(x%2==0){ //送信するアドレスを切り替え addr=2; } else{ addr=3; } Wire.beginTransmission(addr); // transmit to device #4 Wire.write("x is "); // sends five bytes Wire.write(x); // sends one byte Wire.endTransmission(); // stop transmitting x++; delay(500); }
//Slave1 #include <Wire.h> void setup() { Wire.begin(2); // join i2c bus with address #2 Wire.onReceive(receiveEvent); // register event Serial.begin(9600); // start serial for output pinMode(13,OUTPUT); } void loop() { delay(500); digitalWrite(13,LOW); } // function that executes whenever data is received from master // this function is registered as an event, see setup() void receiveEvent(int howMany) { digitalWrite(13,HIGH); //オンボードLED点灯 while(1 < Wire.available()) { char c = Wire.read(); Serial.print(c); } int x = Wire.read(); Serial.println(x); }
// Slave3 #include <Wire.h> void setup() { Wire.begin(3); // join i2c bus with address #3 Wire.onReceive(receiveEvent); // register event Serial.begin(9600); // start serial for output pinMode(13,OUTPUT); } //以下Slave1と同じ
5. まとめ
今回はArduinoでのI2C通信について紹介した。Arduinoや一般的なマイコンと接続されるセンサとの通信ではI2Cがよく採用されているので参考になれば幸いである。