2015年3月14日土曜日

Physical Computing (特に Intel Edison) における I2C による通信について(その2)

 前回 Physical Computing (特に Intel Edison) における I2C による通信について(その1)として,physical computing で使われるシリアル通信としての I2C とその派生系である SMBus の一般論について少しだけ書いた。 今回は,それを yocto Linux が動いている Intel Edison でどう扱われているか,について書いていこう。
 項目番号は前回からの続きということで (4) から始まる。

(4) Intel Edison における I2C 通信
  Intel Edison で I2C 通信を行って,センサー等の機器と通信をするには幾つかの方法(プログラム言語)がある。 一つは,Arduino IDE を使う方法であり,他には Linux 上で C 言語や C++ でプログラミングを行う,などである。 (Pythonnode.js なども使えるみたいだが…) ここでは Linux 上での C 言語や C++ の場合について見てみよう。

 まず,Intel Edison の OS は yocto linux であり,SMBus (I2C) 通信は ioctl という Linux カーネルが持つコマンドとして C 言語を通して実行できる。 具体的な事については,https://www.kernel.org/doc/Documentation/i2c/の中のdev-interface に記載がある。
 その中の Full interface description という項目の中に幾つかの具体的な(?)手法が書かれている。 その中で3つほどがこれから述べる mraa というライブラリで使われている。 (以下は dev-interface からの抜粋である)
ioctl(file, I2C_SLAVE, long addr)
  Change slave address. The address is passed in the 7 lower bits of the
  argument (except for 10 bit addresses, passed in the 10 lower bits in this
  case).

ioctl(file, I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset)
  Do combined read/write transaction without stop in between.
  Only valid if the adapter has I2C_FUNC_I2C.  The argument is
  a pointer to a

  struct i2c_rdwr_ioctl_data {
      struct i2c_msg *msgs;  /* ptr to array of simple messages */
      int nmsgs;             /* number of messages to exchange */
  }

  The msgs[] themselves contain further pointers into data buffers.
  The function will write or read data to or from that buffers depending
  on whether the I2C_M_RD flag is set in a particular message or not.
  The slave address and whether to use ten bit address mode has to be
  set in each message, overriding the values set with the above ioctl's.

ioctl(file, I2C_SMBUS, struct i2c_smbus_ioctl_data *args)
  Not meant to be called  directly; instead, use the access functions
  below.

 この「ioctl(file, I2C_SLAVE, long addr)」,「ioctl(file, I2C_RDWR, struct i2c_rdwr_ioctl_data *msgset)」,「ioctl(file, I2C_SMBUS, struct i2c_smbus_ioctl_data *args)」のパターンが mraa ライブラリで使われる。いずれも ioctl を使っているが,第2引数が「I2C_SLAVE」,「I2C_RDWR」,「I2C_SMBUS」と異なっている。 これらの値は linux システムの /usr/include/linux/i2c-dev.h で定義され,
   ・I2C_SLAVE = 0x0703 /* Use this slave address */
   ・I2C_RDWR = 0x0707 /* Combined R/W transfer (one STOP only) */
   ・I2C_SMBUS = 0x0720 /* SMBus transfer */
となっている。
 ちなみに,mraa や Arduino IDE では,「I2C_SLAVE」の代わりに
   ・I2C_SLAVE_FORCE = 0x0706 /* Use this slave address, even if it is already in use by a driver! */
が使われている。


(5) Intel Edison における I2C 通信:I2C_RDWR のみで送受信を行う作戦:書込みルーチン
  これは,上記 (4) における「I2C_RDWR」を使う方法で,書込みも読込みもやってしまおう,という作戦である。 ここでの記載は私が考えたわけではなく,SparkFun Block for Intel Edison - 9 Degrees of Freedom の下の方にある「Reviews」の中に書いてあった「jku」氏によるサンプルプログラムで使われているものある。 まぁ,いつものように他人の受け売り,ってやつですね。

 ここでは,いわゆる I2C 的にデータのやりとりをしている感じがしている。 dev-interface の中では,SMBus を使えと書いてあるが…。 以下,すこし具体的に見てみよう。 まず,下記を見て欲しい。 これは LSM9DS0/edison-9dof-i2c.c の中の一部分である。 コメントは私が書き加えている。
int write_bytes(int file, uint8_t address, uint8_t *data, uint8_t count)
{
  struct i2c_rdwr_ioctl_data packets;  // ioctl に渡す I2C_RDWR 用の変数の構造体
  struct i2c_msg messages[1];          // ioctl で送り出すデータ変数の型の配列

  messages[0].addr = address;  // スレーブアドレス
  messages[0].flags = 0;       // 書込みを表す
  messages[0].len = count;     // data に含まれるバイト数
  messages[0].buf = data;      // 書込みたい byte data の配列

  packets.msgs = messages;
  packets.nmsgs = 1;

  return ioctl(file, I2C_RDWR, &packets) >= 0 ;  // ioctl で書込み処理をしてから return
}
 これは ioctl を使って,I2C bus(SMBus) 上の address というスレーブアドレスを持つ機器に対して,count バイトの data を書き込むルーチンである。 ルーチンの引数は,
   ・「int file」で定義されるファイルハンドラ
   ・uint8_t 型の address
   ・配列 data へのポインター
   ・配列の長さ count
から成っている。 file というファイルハンドラは,これ以前に
    #define I2C_DEV_NAME "/dev/i2c-1"
    file = open(I2C_DEV_NAME, O_RDWR);
のようにして I2C デバイスに対応付けられていないといけない。
 このルーチンでは,i2c_msg 型の変数 messages にコメントにあるような情報を書込み,それを i2c_rdwr_ioctl_data 型変数 packets に代入して,ioctl でデバイスに書き出している。

 ルーチンの先頭部分で2つの構造体変数が定義されているが,これらの構造体 i2c_rdwr_ioctl_datai2c_msg は,それぞれ /usr/include/linux/i2c-dev.h と /usr/include/linux/i2c-dev-user.h で定義されている。
 i2c_rdwr_ioctl_data は /usr/include/linux/i2c-dev.h で以下のように定義されている。
/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
 struct i2c_msg *msgs; /* pointers to i2c_msgs */
 __u32 nmsgs;          /* number of i2c_msgs */
};
 これは,次に示す i2c_msg 型の配列変数のポインターと,その大きさを表す nmsgs からなる構造体ということを示している。

 i2c_msg は /usr/include/linux/i2c-dev-user.h の中で以下のように定義されている。
/*
 * I2C Message - used for pure i2c transaction, also from /dev interface
 */
struct i2c_msg {
 __u16 addr;  /* slave address */
 unsigned short flags;  
#define I2C_M_TEN  0x10 /* we have a ten bit chip address */
#define I2C_M_RD  0x01
#define I2C_M_NOSTART  0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK  0x0800
 short len;  /* msg length */
 char *buf;  /* pointer to msg data */
};
 間に定数の定義が挟まっていてわかりにくいが,addressflagslen*buf からなる構造体である。

 もし,スレーブ機器が内部レジスタを持っている場合は,送り出すデータ配列 data の0番目に内部レジスタを,1番目に書き込むデータを書き込んで,上記の write_bytes ルーチンでデータを書き出せばよい。具体的には以下のようになる。
int write_byte(int file, uint8_t address, uint8_t reg, uint8_t data)
{
  uint8_t buf[2];
  buf[0] = reg;   // 内部レジスタ番号
  buf[1] = data;  // 書き込むデータ
  return write_bytes(file, address, buf, 2);
}


(6) Intel Edison における I2C 通信:I2C_RDWR のみで送受信を行う作戦:読込みルーチン
  次に,同じ「I2C_RDWR」を使う方法での読込みルーチンを示そう。 ここでは内部レジスタを指定した書込みを考えているので,多少手間が増えている。 以下にルーチンを示そう。 コメントは私が書き加えている。
int read_bytes(int file, uint8_t address, uint8_t reg, uint8_t *dest, uint8_t count)
{
  struct i2c_rdwr_ioctl_data packets;
  struct i2c_msg messages[2];

  /* secret handshake for multibyte read */
  reg = reg | 0x80;  // LSM9DS0 では必要なのだが,一般に必要なのだろうか…?

  /* write the register we want to read from */
  messages[0].addr = address;   // スレーブアドレス
  messages[0].flags = 0;        // 書込みを表す
  messages[0].len = 1;          // データ配列の長さ
  messages[0].buf = ®       // 内部レジスタをデータとして書き込む

  /* read */
  messages[1].addr = address;   // もう一度スレーブアドレス
  messages[1].flags = I2C_M_RD; // 今度は読込み
  messages[1].len = count;      // 読込みバイト数
  messages[1].buf = dest;       // 読み込んだデータの書込み先

  packets.msgs = messages;
  packets.nmsgs = 2;            // 今回は内部レジスタの書込みとデータの読込みで message は2個

  return ioctl(file, I2C_RDWR, &packets) >= 0;
}
 基本は (5) で示した書込みルーチンと同じである。 違いは,まず「内部レジスタ」を指定するために書込み,その後,データを読みだしている。 そのため i2c_msg 型変数 messages が2個のデータを持っている。 これら messages の中の2個の送信の間にはデバイスとの通信が切れないらしい (最初の messages[0] の後に STOP ではなく,ReSTART bit が送られるみたい)。 そのおかげで,内部レジスタ指定してからの読込みが可能となるらしい。 逆に,書込みと読込みの間で一度通信が切られると,うまく内部レジスタの情報を取り出せないらしい。 処理の内容についてはコメントを見て欲しい。

 一点,特別な事をしているのが,reg = reg | 0x80; の部分である。 これは LSM9DS0 のマニュアルに,連続で読み出す時には内部レジスタの指定時に,内部レジスタ番号の一番上の桁の bit を1にせよ,と書いてあるからなのだが,これって他の I2C 機器でも一般にそうなのだろうか…? 今後別のセンサーで調べてみようと思っている。 ちなみに mraa ルーチンを見ると,このような処理はなされていないので,一般的ではないのかもしれない。

追記)実は,このことを知りたくていろいろ I2C について調べたのだった。 他の機器でも試してみて,結果がわかればここに追記したいと考えている。

Physical Computing (特に Intel Edison) における I2C による通信について(その3)に続く)

0 件のコメント: