#初めに
今回、MINDSTORMS社のEV3で、専用品ではない各種センサーを使う方法を調べました。
EV3とセンサーの間にArduinoを挟むことで実現しています。
##使用機材
- EV3
- Arduino互換マイコン
- センサー(今回はカラーセンサーS11059-02DT)
##使用ライブラリ
- Arduino Software I2C
- [ArduinoWireLibrary](Wire Library) (上で代用する場合は不要。今回は使います)
##使用ツール
- ArduinoIDE
- ROBOT-C
#回路の作成
以下の回路図を参考に組み立ててください。
抵抗間違えていました。10KΩではなく100KΩです。
Arduino - S11059-02DT
組み立てたものがこちらになります。
#プログラミング
##Arduino Software I2Cのダウンロード
このライブラリはArduinoのI2C専用ピン(SCL,SDA)以外のピンでもI2C通信ができるようになるライブラリです。
センサーとArduinoの通信を担います。
Arduino Software I2C
ライブラリのダウンロード方法はこちらへ
標準ライブラリの"Wire.h"の関数と互換性があります。
詳しく知りたい場合はダウンロードフォルダ内のREADME.mdを見てください。
また、I2C通信にさらに詳しく知りたい場合はこちらへ
##Arduino側のプログラミング
例として、カラーセンサのS11059-02DTを使います。
Arduino側のメインプログラム
#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と通信するプログラム
#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);
}
}
センサーの値を取得するプログラム
#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を用いています。
#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);
}
}
うまくいけばこうなります。
#プログラムの説明
Arduinoでのセンサーの扱いについては、ネットに優れた情報が豊富にあるため省きます。それらを参考にしてください。
基本:センサー読み出しのシーケンス
Arduinoに接続されたセンサーの値をEV3から読み出すには、以下のようなシーケンスとなります。
###1, EV3からArduinoへのセンサー読み出しリクエスト
char Wbuf[3] = {2, Arduino_ADDRESS << 1, 0x10 };
送信する内容を配列に格納します。
- 第一要素は送信する要素数(アドレスを含むので、データ数+1)
- 第二要素はArduinoのI2Cアドレス
- 第三要素からは送る任意のデータ列。
ArduinoのI2Cアドレスは、0~127の7bit幅の適当な値です。最下位ビットにはW/R命令フラグが入りますので、1bit左シフトしておきます。
データとして送っている0x10は、「カラーセンサーを読み取る」というコマンド的に使っている、本サンプル固有の適当な値です。
sendI2CMsg(S1, Wbuf, 16);
配列のデータを送信します。
- 第一引数はArduinoを接続しているEV3のポート
- 第二引数は送信する内容の配列
- 第三引数については現在調査中です。
受け取るバイト数と書いているのですが…わからない
とりあえず、readI2CReply(のちに記載)と同じにします。
###2, ArduinoからEV3への応答
// I2C通信で自分のアドレスが呼ばれた時の処理
void receiveEvent(int DataNum)
{
// 受けた命令を保存
getAdres = Wire.read();
}
自分のアドレスに向けて通信があった場合、Wire.onReceive() で登録した receiveEvent 関数が実行されます。Wire.read() で、EV3から送信されたデータ列を1byteずつ受け取ります。
// 送信要請が来た時の処理
void requestEvent()
{
// 0x10が最後に受けた命令なら
if (getAdres == 0x10)
{
// DATASIZE個のデータを送信
Wire.write(data, DATASIZE);
}
}
次に、Wire.onRequest() で登録した requestEvent 関数が実行されます。
Wire.write(*d, size)で、EV3に渡したいデータを送信します。
- 第一引数はデータ配列のポインター
- 第二引数は送信するデータサイズ(byte)
第二引数と readI2CReply() の第三引数は同じにしてください。
###3, Arduinoからの応答を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を含め、初めて技術系の解説を書きました。
読みにくい部分はご指摘していただくと幸いです。
#参考文献