EV3でI2C通信

#初めに

　今回、MINDSTORMS社のEV3で、専用品ではない各種センサーを使う方法を調べました。
　EV3とセンサーの間にArduinoを挟むことで実現しています。

##使用機材
* EV3 
* Arduino互換マイコン
* センサー(今回はカラーセンサーＳ１１０５９－０２ＤＴ)

##使用ライブラリ 
* [Arduino Software I2C](https://wiki.seeedstudio.com/Arduino_Software_I2C_user_guide/)
* [ArduinoWireLibrary](Wire Library) (上で代用する場合は不要。今回は使います)

##使用ツール
* ArduinoIDE
* ROBOT-C

#回路の作成
　以下の回路図を参考に組み立ててください。
  **抵抗間違えていました。10KΩではなく100KΩです。**

Arduino - EV3
![EV3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1531622/f1513554-dcc6-54c1-2efe-f7860e95f8e1.png)

Arduino - S11059-02DT
![Sensor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1531622/09d5de10-f440-0e94-53bf-f8925e65f42c.png)
　組み立てたものがこちらになります。

![P5221279.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1531622/6b0d2e95-8d86-c5fe-3ee6-cd177b34f9d2.jpeg)


#プログラミング
##Arduino Software I2Cのダウンロード
このライブラリはArduinoのI2C専用ピン(SCL,SDA)以外のピンでもI2C通信ができるようになるライブラリです。
センサーとArduinoの通信を担います。

[Arduino Software I2C](https://wiki.seeedstudio.com/Arduino_Software_I2C_user_guide/)
ライブラリのダウンロード方法は[こちら](https://www.mgo-tec.com/arduino-ide-lib-zip-install)へ

標準ライブラリの"Wire.h"の関数と互換性があります。
詳しく知りたい場合はダウンロードフォルダ内のREADME.mdを見てください。
また、I2C通信にさらに詳しく知りたい場合は[こちら](https://monolizm.com/sab/pdf/%E7%AC%AC16%E5%9B%9E_%E3%83%97%E3%83%AC%E3%82%BC%E3%83%B3%E8%B3%87%E6%96%99(IC2%E9%80%9A%E4%BF%A1%E7%B7%A8).pdf)へ

##Arduino側のプログラミング
　例として、カラーセンサの[Ｓ１１０５９－０２ＤＴ](https://akizukidenshi.com/catalog/g/gK-08316/)を使います。

Arduino側のメインプログラム

```c:[ALDUINO]main.ino
#include "sensor.h" // センサーの値を取得するコードが入ったヘッダー
#include "EV3.h" // EV3との通信を行うコードが入ったヘッダー

// センサから取得した値を保持しておく配列のポインタ
byte *sensor;

void setup()
{
  // デバック用シリアル通信
  Serial.begin(9600);

  // センサとArduinoの通信(SDA,SCL)
  initI2CPort(13, 12);

  // カラーセンサの初期化
  initColSensor();

  // 変数の初期化(0で埋める)
  sensor = (byte*)malloc(sizeof(byte)*DATASIZE);
  memset(sensor, 0, sizeof(byte)*DATASIZE);

  // 送信する配列のポインタを指定
  initEV3Connect(sensor);
}

void loop() 
{
  // センサーの値入手
  getColSensor(&sensor[0]);

  /*R,G,Bのそれぞれの値を変数に入れる*/
  uint16_t r,g,b;
  r = *(uint16_t *)&sensor[0];
  b = *(uint16_t *)&sensor[2];
  g = *(uint16_t *)&sensor[4];
  
  delay(200);
}
```

EV3と通信するプログラム

```c:[ALDUINO]EV3.h
#include "Wire.h" 

// ArduinoのI2Cアドレス
#define I2C_ADDRESS 0x31
// 送るデーターサイズ
#define DATASIZE 16

uint8_t getAdres;
byte *data;

void receiveEvent(int DataNum);
void requestEvent();

// 変数の初期化とI2Cの設定
void initEV3Connect(byte *d)
{
  data = d;
  // ArduinoをEV3のスレーブ(子)として設定
  Wire.begin(I2C_ADDRESS);

  Wire.onReceive(receiveEvent); // 自身のアドレスが呼ばれた時に実行する関数
  Wire.onRequest(requestEvent); // データの送信を要求が来た時に実行する関数
}

// I2C通信で自分のアドレスが呼ばれた時の処理
void receiveEvent(int DataNum)
{
  // 受けた命令を保存
  getAdres = Wire.read();
}


// データの送信を要求が来た時に実行する関数

void requestEvent()
{ 
  // 0x10が最後に受けた読み込み命令なら
  if (getAdres == 0x10) 
  {
    // DATASIZE個のデータを送信
    Wire.write(data, DATASIZE);
  }
}
```

センサーの値を取得するプログラム

```c:[ALDUINO]sensor.h
#include "SoftwareI2C.h" // ボード付属のSDA,SDCとは別のポートを使うためのライブラリ

// センサーのI2Cアドレス
#define COL_ADDRESS 0x2A

// I2Cの情報を入れる変数
SoftwareI2C i2c;

// I2Cポートの設定
void initI2CPort(int sda, int scl)
{
  i2c.begin(sda, scl); 
}

// カラーセンサの初期化(各々のデーターシート参照)
void initColSensor()
{
  i2c.beginTransmission(COL_ADDRESS);
  i2c.write(0x00);
  i2c.write(0x8B);
  i2c.endTransmission();

  i2c.beginTransmission(COL_ADDRESS);
  i2c.write(0x00);
  i2c.write(0x0B);
  i2c.endTransmission();
  delay(3000);
}

// カラーセンサーの値を取得
void getColSensor(byte *arry)
{
  // カラーセンサにアドレス0x03のデータを渡すよう要求
  i2c.beginTransmission(COL_ADDRESS);
  i2c.write(0x03);
  i2c.endTransmission();

  // カラーセンサーに0x03から8byte分のデーターを要求
  i2c.requestFrom(COL_ADDRESS, 8);
  if(i2c.available())
  {
    for(int i = 0; i < 4; i++)
    {
      // Arduinoはリトルエンディアンなので注意
      arry[2*i+1] = i2c.read(); //highビット
      arry[2*i+0] = i2c.read(); //lowビット
    }
  }
  i2c.endTransmission();
}
```

##EV3側のプログラミング

Robot-Cを用いています。

```c:[EV3]main.c
#define Arduino_ADDRESS 0x31

// ポートに応答があるか
void checkI2C(tSensors port)
{
  while(nI2CStatus[S1]!=0)
  {}
  return;
}

task main()
{
  // Arduinoに送信するデータ
  char Wbuf[3] = {2, Arduino_ADDRESS << 1, 0x10};
  // Arduinoからの値を入れるバッファ
  char Rbuf[16];

  while(true)
  {
  // ArduinoにWbufの中身を送信
  sendI2CMsg(S1, Wbuf, 16);
  // Arduinoからの応答があるまで待つ
  checkI2C(S1);
  // Arduinoからの値をRBufに入れる
  readI2CReply(S1, Rbuf,16);

  /*以下表示用プログラム*/
  unsigned short *SensorVal = (unsigned short *)&Rbuf[0];
  		
  eraseDisplay();
  for(int i=0;i<4;i++)
  {
  	displayBigTextLine(i*2,"  %5d : %5d",i,SensorVal[i]);
  }

  wait1Msec(500);
  }
}
```
うまくいけばこうなります。

![P5221281.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1531622/02a33b11-c49e-5332-1a09-a3f7b5daf28f.jpeg)

#プログラムの説明
Arduinoでのセンサーの扱いについては、ネットに優れた情報が豊富にあるため省きます。それらを参考にしてください。

## 基本：センサー読み出しのシーケンス
Arduinoに接続されたセンサーの値をEV3から読み出すには、以下のようなシーケンスとなります。

###1, EV3からArduinoへのセンサー読み出しリクエスト
```c:EV3
char Wbuf[3] = {2, Arduino_ADDRESS << 1, 0x10 };
```
送信する内容を配列に格納します。

* 第一要素は送信する要素数(アドレスを含むので、データ数+1)
* 第二要素はArduinoのI2Cアドレス
* 第三要素からは送る任意のデータ列。

　ArduinoのI2Cアドレスは、0~127の7bit幅の適当な値です。最下位ビットにはW/R命令フラグが入りますので、1bit左シフトしておきます。
　データとして送っている0x10は、「カラーセンサーを読み取る」というコマンド的に使っている、本サンプル固有の適当な値です。

```c:EV3
sendI2CMsg(S1, Wbuf, 16);
```
配列のデータを送信します。

* 第一引数はArduinoを接続しているEV3のポート
* 第二引数は送信する内容の配列
* 第三引数については現在調査中です。~~受け取るバイト数と書いているのですが…わからない~~
とりあえず、readI2CReply(のちに記載)と同じにします。

###2, ArduinoからEV3への応答
```c:ARDUINO
// I2C通信で自分のアドレスが呼ばれた時の処理
void receiveEvent(int DataNum)
{
  // 受けた命令を保存
  getAdres = Wire.read();
}
```
　自分のアドレスに向けて通信があった場合、Wire.onReceive() で登録した receiveEvent 関数が実行されます。Wire.read() で、EV3から送信されたデータ列を1byteずつ受け取ります。

```c:ARDUINO
// 送信要請が来た時の処理
void requestEvent() 
{ 
  // 0x10が最後に受けた命令なら
  if (getAdres == 0x10) 
  {
    // DATASIZE個のデータを送信
    Wire.write(data, DATASIZE);
  }
}
```
次に、Wire.onRequest() で登録した requestEvent 関数が実行されます。
Wire.write(*d, size)で、EV3に渡したいデータを送信します。

* 第一引数はデータ配列のポインター
* 第二引数は送信するデータサイズ(byte)

**第二引数と readI2CReply() の第三引数は同じにしてください。**

###3, Arduinoからの応答をEV3で受け取る

```c:EV3
readI2CReply(S1, Rbuf,16);
```
Arduinoから送られてきたデータを受信します。

* 第一引数はArduinoを接続しているEV3のポート
* 第二引数は受信データを格納するれる配列(ポインタ)
* 第三引数は何byte受け取るか

です。

## 応用：複数のセンサーを使う方法
二つの方法が考えられます。

###方法1　全てのセンサーのデータをまとめて送受信する。
　シンプルな方法です。今回の場合、16要素あるsensor配列中、カラーセンサは0~7番目まで使います。残りの8~15番目は空いているので、そこに他のセンサーのデータを入れて送信します。センサーの数とデータサイズによって、配列のサイズを拡大します。

###方法2　読み出したいセンサーを指定して個別に通信する。
　センサー読み出しリクエストに入れているコマンド的な値を、センサーのセレクタとして活用します。今回は0x10が来たらカラーセンサの値を送信していますが、0x11が来たらジャイロ…、0x12が来たら加速度…と、いった感じに分けることが考えられます。

#注意すること
　~~**ホストPCとのシリアル通信をしているとI2C通信がうまくいかないことがあるようです。**ArduinoのUSBケーブルを外すか、シリアルモニタを閉じましょう~~
できました。しかし、うまくいかない場合は上記のことも試してみてください。

#余談
ArduinoはEV3ポートからの電力で動く。…いいね。
複数センサーの接続についてはまた別記事で書きます。

#最後に
　今回Qiitaを含め、初めて技術系の解説を書きました。
　読みにくい部分はご指摘していただくと幸いです。

#参考文献
* [Ｉ２Ｃ対応デジタルカラーセンサモジュール　Ｓ１１０５９－０２ＤＴ](https://akizukidenshi.com/catalog/g/gK-08316/)
* [Arduino-デジタルカラーセンサ「S11059-02DT」の使い方](https://spiceman.jp/arduino-s11059-program/)
* [EV3ねた その14　いよいよArduinoとの接続](https://blog.goo.ne.jp/mypapa8888/e/892c761a170926783d3a03cc459f9fd2)
* [NXT I2C Sensors (ROBTOT-C Arduinoリファレンス)](https://www.robotc.net/WebHelpArduino/index.htm)
* [第５回　Arduino入門 I2C通信編](https://monolizm.com/sab/pdf/%E7%AC%AC16%E5%9B%9E_%E3%83%97%E3%83%AC%E3%82%BC%E3%83%B3%E8%B3%87%E6%96%99(IC2%E9%80%9A%E4%BF%A1%E7%B7%A8).pdf)