LinuxのC言語でI2Cデバイスと通信する

## はじめに
Raspberry PiやJetson nanoでI2Cデバイスを使いたい時に、RasPiではpigpioやWiringPiなどのライブラリがありますが、JetsonだとJetson.GPIOぐらいしかなく、C/C++で書きたい時に困ることがあります。（別に困らない？？）

そんな時のためにLinuxの汎用的なI2Cドライバを使用してI2Cデバイスと通信する方法を記事にしました。

## 確認環境

* Raspberry Pi (Raspbian: Linux kernel 4.19)
* Jetson nano (Linux kernel 4.9)

## ソースコード

I2Cデバイスと通信する方法はいくつかあるみたいですが、このコードでは ioctl I2C_RDWR を使用しています。

```c:i2c_example.c
#include <stdint.h>
// ...
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

static const char* dev_name = "/dev/i2c-1";

/*! I2Cスレーブデバイスからデータを読み込む.
 * @param[in] dev_addr デバイスアドレス.
 * @param[in] reg_addr レジスタアドレス.
 * @param[out] data 読み込むデータの格納場所を指すポインタ.
 * @param[in] length 読み込むデータの長さ.
 */
int8_t i2c_read(
    uint8_t dev_addr, uint8_t reg_addr, uint8_t* data, uint16_t length) {
  /* I2Cデバイスをオープンする. */
  int32_t fd = open(dev_name, O_RDWR);
  if (fd == -1) {
    fprintf(stderr, "i2c_read: failed to open: %s\n", strerror(errno));
    return -1;
  }

  /* I2C-Readメッセージを作成する. */
  struct i2c_msg messages[] = {
      { dev_addr, 0, 1, &reg_addr },         /* レジスタアドレスをセット. */
      { dev_addr, I2C_M_RD, length, data },  /* dataにlengthバイト読み込む. */
  };
  struct i2c_rdwr_ioctl_data ioctl_data = { messages, 2 };

  /* I2C-Readを行う. */
  if (ioctl(fd, I2C_RDWR, &ioctl_data) != 2) {
    fprintf(stderr, "i2c_read: failed to ioctl: %s\n", strerror(errno));
    close(fd);
    return -1;
  }

  close(fd);
  return 0;
}

/*! I2Cスレーブデバイスにデータを書き込む.
 * @param[in] dev_addr デバイスアドレス.
 * @param[in] reg_addr レジスタアドレス.
 * @param[in] data 書き込むデータの格納場所を指すポインタ.
 * @param[in] length 書き込むデータの長さ.
 */
int8_t i2c_write(
    uint8_t dev_addr, uint8_t reg_addr, const uint8_t* data, uint16_t length) {
  /* I2Cデバイスをオープンする. */
  int32_t fd = open(dev_name, O_RDWR);
  if (fd == -1) {
    fprintf(stderr, "i2c_write: failed to open: %s\n", strerror(errno));
    return -1;
  }

  /* I2C-Write用のバッファを準備する. */
  uint8_t* buffer = (uint8_t*)malloc(length + 1);
  if (buffer == NULL) {
    fprintf(stderr, "i2c_write: failed to memory allocate\n");
    close(fd);
    return -1;
  }
  buffer[0] = reg_addr;              /* 1バイト目にレジスタアドレスをセット. */
  memcpy(&buffer[1], data, length);  /* 2バイト目以降にデータをセット. */

  /* I2C-Writeメッセージを作成する. */
  struct i2c_msg message = { dev_addr, 0, length + 1, buffer };
  struct i2c_rdwr_ioctl_data ioctl_data = { &message, 1 };

  /* I2C-Writeを行う. */
  if (ioctl(fd, I2C_RDWR, &ioctl_data) != 1) {
    fprintf(stderr, "i2c_write: failed to ioctl: %s\n", strerror(errno));
    free(buffer);
    close(fd);
    return -1;
  }

  free(buffer);
  close(fd);
  return 0;
}
```

## 解説

まずはじめにヘッダのインクルードとデバイス名の定義です。

```c:
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

static const char* dev_name = "/dev/i2c-1";
```

RasPiとJetson nanoの I2C Bus1 のピンアサインは共通で、デバイス名も `/dev/i2c-1` で同じです。

* GPIO 2 ： SDA I2C Bus1
* GPIO 3 ： SCL I2C Bus1 

### Read処理

```c:
  /* I2C-Readメッセージを作成する. */
  struct i2c_msg messages[] = {
      { dev_addr, 0, 1, &reg_addr },         /* レジスタアドレスをセット. */
      { dev_addr, I2C_M_RD, length, data },  /* dataにlengthバイト読み込む. */
  };
  struct i2c_rdwr_ioctl_data ioctl_data = { messages, 2 };

  /* I2C-Readを行う. */
  if (ioctl(fd, I2C_RDWR, &ioctl_data) != 2) {
```
Readの時は i2c_msg 構造体が2つ必要になります。
1つ目にレジスタアドレスをセット、
2つ目に読み込むサイズとデータの格納場所を指定しています。

### Write処理

```c:
  /* I2C-Write用のバッファを準備する. */
  uint8_t* buffer = (uint8_t*)malloc(length + 1);
  buffer[0] = reg_addr;              /* 1バイト目にレジスタアドレスをセット. */
  memcpy(&buffer[1], data, length);  /* 2バイト目以降にデータをセット. */

  /* I2C-Writeメッセージを作成する. */
  struct i2c_msg message = { dev_addr, 0, length + 1, buffer };
  struct i2c_rdwr_ioctl_data ioctl_data = { &message, 1 };

  /* I2C-Writeを行う. */
  if (ioctl(fd, I2C_RDWR, &ioctl_data) != 1) {
```

Writeの時は『書き込むデータの長さ＋１』のバッファが必要になります。
１バイト目にレジスタアドレスをセット、
２バイト目以降にデータをセットします。

i2c_msg 構造体は1つになります。

## おわりに

自分はこのコードでRasPiとJetsonNanoの両方でI2Cデバイスと通信が出来ましたが、コードや内容に間違いがあるかもしれませんのでご注意ください。