つれづれなる備忘録

日々の発見をあるがままに綴る

TinkercadによるArduinoシミュレーション9 ~ I2C通信

1. I2C通信とは

 I2CとはInter-Integrated Circuitの略でフィリップス社(半導体部門が独立して現在はNXPセミコンダクターズ社)が提唱する通信方式でセンサ等に広く採用されている。以前採り上げた非同期シリアル通信であるUARTと異なりI2Cは同期式のシリアル通信の1つである。概略としては、マスターとスレーブに役割を分け、データ入出力用の信号線:SDAと同期するためのクロック信号線:SCLの2本の信号線を用いて通信を行う。1つのマスターに対して複数のスレーブを接続することができる。マスターからはスレーブのアドレスを指定し、指定したアドレスと一致するスレーブがデータの送受信を行うことができる。詳細については、 2-17 I2Cとは | ヨースケ先生のラピステクノロジー マイコン豆知識!などが参考になる。

2. ArduinoによるI2C通信

 Arduino同士を接続して、I2C通信させるための配線図を下に示す。基本的には同期信号線:SCLとデータ通信用信号線:SDAを接続すればよい。

I2C配線図
I2C配線図

 次にスケッチについては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信号波形の確認
I2C信号波形の確認
次に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)をサポートしている場合がある。
I2C通信の信号波形
I2C通信の信号波形
なおMaster側のアドレスとSlave側のアドレスが一致しない場合のSDA波形を下に示す。アドレス番号である2は送信されているが、スレーブ側を2以外のアドレスに設定しておくとデータが送信されないことが確認できる。
アドレス不一致SDA信号
アドレス不一致SDA信号

4. 複数スレーブでのI2C通信

 最後にスレーブが複数ある場合のI2C通信の例を示す。下図はマスターとしているArduinoから2台のスレーブとするArduinoを接続している。マスター側から送信するアドレスを切り替えることで2台のスレーブに対して通信することができる。

複数I2C通信
複数I2C通信
以下のスケッチでは、カウントアップする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がよく採用されているので参考になれば幸いである。