先日とある工作をする際にI2Cで外付けのデバイスを動かす必要があったのだが
めんどくさいのでとりあえずプラットフォームはArduinoを使うことにした。
その際の七転八倒カットアンドトライの話を
一部の人にしかありがたくないネタだが、一部の人にはありがたいと思うので書く。
まぁ、備忘録だ。
実際に使ったのは手元にあった『Arduino Uno』
|
フィジカルコンピューティング!【メール便速達可能】電子工作Arduino Uno R3 (アルドィーノ ウ… |
ちなみにI2Cで書き込む時ってデータのやりとりはこんなカンジで、
最初のコントロールバイトは
7bitのアドレス+リードならHigh, ライトならLowだ。
ライトのときは
int device_address = 0x12; // I2C通信するデバイス依存の値なのでここではテキトー
byte control_byte = (device_address << 1) & 0xFE;
リードのときは
int device_address = 0x34; // I2C通信するデバイス依存の値なのでここではテキトー
byte control_byte = (device_address << 1) | 0x01;
まぁこんなとこだろう。
データを8bit送るごとにマスターは一回バスから手を離してスレーブのACKを受け取る。
で、ひと通り送り終わったらSTOPビットを送る。
さて、こんなことイチイチ記述するのは非常にかったるいな。間違いなし。
ということで、
こういうことをArduinoでやる際には都合の良いライブラリが用意してある。
“Wire”というライブラリだ。
http://arduino.cc/en/Reference/Wire
ちなみにそのWireを使って記述するとこんなカンジだろう。
#include <Wire.h> // 参照を追加する
void setup()
{
Wire.begin();
}
・・・・・
・・・・・
・・・・・
void i2c_write(int device_address, int memory_address, int value)
{
Wire.beginTransmission(device_address);
Wire.write(memory_address);
Wire.write(value);
Wire.endTransmission();
}
デバイスアドレス宛にライトコマンド送って、
書き込むメモリのアドレスを送って
データを送って、
STOPを送る。
完了。
これはけっこー簡単だな。
さて、
で、
問題なのは『リード』だ。
ちなみにArduinoのreferenceにはこんなカンジで書いてある。
int read_byte0;
int read_byte1;
int read_byte2;
・・・・
・・・・
void i2c_read(int device_address, int length)
{
Wire.requestFrom(device_address, length);
*read_byte0 = Wire.read();
*read_byte1 = Wire.read();
*read_byte2 = Wire.read();
・・・・
・・・・
・・・・
}
デバイスアドレス宛にリードコマンドを送って、
length分だけデータを読み出す、、、、
って、あれ?
読み出すメモリのアドレスは?
と気付く。
普通(おれの理解では)一般的なのはこんなカンジで
デバイスアドレス宛にライトコマンドを送って、
メモリアドレスを送って、(メモリの番地を移動させて)
Repeated-startビット(もしくはRestartビット)を送って、
デバイスアドレス宛にリードコマンドを送って、
データを読み出して、
STOPビットを送る
これが普通のプロセスだと思う。
Repeated-startってのは簡単に言うと「まだコネクションキープね」の合図だ。
ちなみに一度でもSTOPビットを送るとI2Cの通信は完全にリセットになる。
ところがArduinoのReferenceをさらっと読むだけではこれに対応しているようには読めないのだ。
これをもって
「Wireライブラリが使えない」
とか
「TWIライブラリを使うべきだ」
とか
「オリジナルのI2Cライブラリを作った」
とかネットではいろいろ見かける。
しかし、良い機会だからここできっちり結論を述べておこう。
Wireライブラリで十分対応できる。
そしてそれはよーーーく読むとReference上でも理解できたりして。
ちなみにこの
http://code.google.com/p/arduino/issues/detail?id=28
やりとりの一番下にあるのが今回の結論だ。
簡単に書くと
Wire.endTransmission(false);
としたライトコマンドを送ることで、アドレス番地の移動ができ、かつSTOPビットを送らずに
処理を続けることができる。
ソース的に書くとこんなカンジだ
void i2c_read(int device_address, int memory_address)
{
Wire.beginTransmission(device_address);
Wire.write(memory_address);
Wire.endTransmission(false);
Wire.requestFrom(device_address, 1, false);
*read_byte = Wire.read();
Wire.endTransmission(true);
}
さっきも言ったがリファレンスをちゃんとよーく読むと書いてある。
でも、まぁそんなのダルいよね。気持ちわかるわかる。
さて、ということでコレが正解だわ。あとは迷わず直進でGO。
Facebook上での「見える」「見えない」のプライバシー対策(グループ、リスト + カスタム投稿、アプリケーション設定)
Arduino用I2C通信のオリジナルライブラリ(サンプルコード)



