Arduinoを30分でI2C通信する

  • 16
    Like
  • 4
    Comment
More than 1 year has passed since last update.

ArduinoでI2C通信をする

I2Cで通信するICを買ってきたのでArduinoで動かそうと思ったけど,よく考えたらサンプルコードのコピペで動かしたことしかなかったので,自分で考えながらコードを書いてみることにした.

I2Cは正確には(アイスクエアシー)と発音するらしいけど長いので(アイツーシー)と読んでいる.
細かい仕様とかのうんちくは他のサイトで調べてください.今日はとにかく動かす.

使うデバイス

記事の内容は以下のデバイスで確認しているが,なるべく一般的な話をするために個別のデバイスに特別な話は極力避けることにする.

master

  • Arduino UNO(互換機freaduinoを3.3V動作)

slave

  • HMC5883L(3軸地磁気センサ) 8ピンDIP化キット(3.3V)

接続するピンを確認する

I2C通信では,例えば1対1で接続する場合,必ずmaster(マスター,主人)とslave(スレーブ,奴隷)という役割に分かれて通信する.
一つのシステムの中ではマイコンなどの主導的なデバイスがmasterで,センサICなどの補助的なデバイスがslaveとなることが多い.ただし一概には言えないし,マイコン同士で通信することもあるので時と場合による.

ここではArduinoをmasterとする.

Arduino側で接続に使うピンは次の通り

  • 5V,3V3 などICの電源となるピン
  • GND
  • SDA(シリアルデータ)
  • SCL(シリアルクロック)

slaveとなるIC側で接続に使うピンは次の通り

  • VDD(電源)
  • GND
  • SDA
  • SCL

なお,ピンの呼び方はデバイスによって多少異なり,これら以外にオプション的なピンが存在する場合があるので詳しくはデータシートを参照.
ちなみにNCと書かれているものはどこにも接続しないピン.

ArduinoのSDA/SCLピン

実はArduinoのSDAピンとSCLピン配置は機種によって異なる.全く不便である.
Arduinoの機種名で画像検索すればピン配列を解説した画像が出てくるので,それを頼りにSDA/SCLを確かめる.
ピン配列が大部分で共通しているUNOとMEGA,leonaldoやpro miniとpro microなどでも異なるので自作シールドの開発をする際などは注意が必要.

デバイスの電圧

必ず注意しなければいけないのはデバイス双方の動作電源電圧のレンジ.
例えば Arduino:5V で IC:3.3V のとき,場合によってはそのままつなぐとIC側が破損する.この場合はIC側の入力電源,入力信号のレベル(電圧)を指定のレンジ内に収めなければいけない.
レベルシフタを使用するなどしてデバイスに適した電圧で入出力する環境が必要である.

slaveデバイスのアドレス

masterにはないが,slaveにはアドレスというものが存在してそれぞれを識別できるようにしてある.これにより複数のslaveデバイスを同じ端子に接続したままmasterから使い分けができる.
ICでは固有で変更ができないが,Arduinoをslaveとして利用するときなどはアドレスを任意に指定することができる.
writeとreadでアドレスを区別する場合,最下位bitを0とするか1とするかでwriteとreadを区別する.
7bitで表現する場合,この最下位bitを無視して右にシフトしたものをアドレスとする.Arduinoでは7bitで入力する.
例えば8bitでwriteのアドレスが(0x3C:0b0011 1100)のデバイスをArduinoで接続する場合,7bitでは(0x1E:0b0001 1110)と変換する.

Arduinoのスケッチ

I2C通信にはWireライブラリを使用する.
ここからは実際のコードを読みながら理解していく.
流れを理解するためのものであり処理として意味はない.

masterからの一方的な書き込み

とりあえず投げっぱなしのコード

test_I2C_master.ino
#include <Wire.h>

void setup() {
  Wire.begin();
}

byte val = 0;

void loop() {
  Wire.beginTransmission(0x1E);
  Wire.write(val);
  Wire.endTransmission();

  val++;
  delay(500);
}

0x1Eというアドレスのslaveデバイスに接続し,valを送っては加算し,また送るというコード.

Wire.begin();

Wireを開始する.引数を省略するとmasterとして,7bitで入力すると自身をslaveとして開始し引数がslaveアドレスとなる.
今はArduinoがmasterとして動作するので引数については考えない.

Wire.beginTransmission(0x1E);

slaveデバイスへ通信を開始する.
引数はslaveデバイスに対応するアドレス.ここでは0x1E(0b00011110)がslaveのアドレス.16進数で指定しているが,10進数でも2進数でも構わない.

Wire.write(val);

引数は1byte.
オーバーロードしてあり配列とデータ長を引数として複数byteを送ることもできる.

Wire.endTransmission();

データを送信する.
実はこの関数を呼ぶまでデータは送信していない.
boolean型の引数を入力できるがここでは説明省略.

masterからの一方的な読み込み

ひたすら読み込むだけのコード

test_I2C_master.ino
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
}

void loop() {
  Wire.requestFrom(0x1E, 1);

  while (Wire.available()) {
    byte val = Wire.read();
    Serial.print(val);
  }

  delay(500);
}

0x1Eというアドレスのslaveデバイスに接続し,1byteの大きさのデータの返答を待つ.データを受信し,シリアル通信で受信データが尽きるまで内容を送信(転送)する.
slaveから返答するデータがあることを前提としているのでslaveデバイスによっては意味のないコード.

Wire.requestFrom(0x1E, 1);

0x1Eのデバイスに1byte返答するように要求する.

Wire.available();

Wireで読み込めるbyte数を返す.ない場合0を返すのでこれをfalseと読みかえて扱える.
コードでは1byteを要求しているので1byteを越えて返ってこないはずだが,例えば6byte要求して2byteしか返答がない場合などに処理を打ち切ることができる.
返答データ長が確実な場合はwhile文で囲む必要はない.

Wire.read();

受信したデータを1byte返す.ここでは受信したデータを変数valに格納する.

双方向通信(レジスタを指定して読み出す)

上の二つを組み合わせ,レジスタアドレスを指定してそれに応じた情報を受け取るコード

test_I2C_master.ino
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
}

void loop() {
  Wire.beginTransmission(0x1E);
  Wire.write(0x0A);
  Wire.endTransmission(false);

  Wire.requestFrom(0x1E, 1, true);

  byte val = Wire.read();
  Serial.print(val);

  delay(500);
}

通信が成功しているかどうかを確認するために合言葉を確かめる.
Arduinoから既定のレジスタアドレスを送信し,ICがそれに応じた内容のデータを返答するという流れ.
今回使用したICではレジスタアドレス
10 = 0x0A = 0b0000 1010 に対して 72 = 0x48 = 0b0100 1000
が返される事が決まっているので,シリアルモニタに72が表示されれば正しく通信できていることになる.
これらはICに固有のレジスタアドレス,返答値なのでIC毎にデータシートを参照.

Wire.endTransmission(false);

上で省略したが,この関数にboolean型の引数を入力でき,デフォルトでtrueになっている.
falseを入力するとデバイス間の接続を切らない.masterデバイスが複数ある場合に割り込んで処理が始まらないように回線を確保できる.

Wire.requestFrom(0x1E, 1, true);

0x1Eのデバイスに1byte返答するように要求する.
3つめの引数をboolean型で入力するとデータを受信した後にstopメッセージを送るかどうかを指定できる.省略するとtrueとなって接続を切り,falseとすると接続を継続する.
1対1で通信する場合はいずれにせよ特に変わりはない.

I2C関係の参考サイト

ライブラリのリファレンスとしては公式かgarretlabさんが詳しいのでそちらを参考にする.
https://www.arduino.cc/en/Reference/Wire
http://garretlab.web.fc2.com/arduino_reference/libraries/standard_libraries/Wire/index.html

こちらのサイトがI2Cに関する考察が詳しいのでおすすめ.
http://www.geocities.jp/zattouka/GarageHouse/micon/I2C/I2C_1.htm

配線する際の注意点

master,slaveそれぞれの電源,GND,SDA,SCLの4端子を互いに繋げば基本的に動作する.ただし,重ねて言うが動作電圧が異なる場合はGND以外の全ての端子で必ずレベルシフタなどを介して電圧をそろえる必要がある.

おわりに

Arduinoユーザーが使う通信規格として主にRS232C,I2C,SPIがありそれぞれにメリットがあるが,I2Cのメリットは並列接続した際の設計のシンプルさだろう.RS232Cはデバイス間で識別のための決まり事を作れば多数のデバイスを扱えるが,規格としてあるわけではないので通信する全てのデバイスを自身で設計する場合に限られる.SPIではCSのための信号線がslaveデバイスにつき1本必要になるのでマイコンの端子数を超える場合はシリアルパラレル変換ICを使うなど設計が複雑になる.その点I2Cならばマイコンの端子数を気にすることなくslaveデバイスを増やすことが可能である.
それと引き換えに識別のためのslaveアドレスのやりとりなど面倒な信号のやり取りが必要なのでRS232CやSPIと比べて理解にやや手間を要する.

コードや内容に誤りがある場合などはコメントをいただけるとありがたい.