やりたいこと
Groveシステムの I2C静電容量タッチセンサのデータを、Node.js + johnny-five で得ることで、遠隔地にある何かに誰かが触れたことを検出できるようにする。
きっかけは、書籍「二足歩行ロボット 工作&プログラミング(リックテレコム)」で、このタッチセンサを2足歩行ロボットRapiroに組み込んでいるのを見たことです。書籍では、センサ開発元(Seeed Studio)が提供しているライブラリとサンプルコードを使って Arduino IDE でセンシングを実装していました。ここで、もし Node.js + johnny-five で実装できれば、ネットワーク越しに遠隔監視できると考えました。
I2C(アイ・スクエア・シー)のこともよく分かっていない私ですが、上述のArduino用ライブラリとサンプルコードを解読して、johnny-five でセンサのデータを取得することができましたので、報告です。
方法
機材
- Arduino UNO R3
- Arduino IDE で StandardFirmataを書込済
- Grove ベースシールド
- Grove I2C タッチセンサ
- ベースシールドの I2C コネクタに接続
- 4個のセンサ電極(feeler)が付属
- USBケーブル(Arduino と PC の接続用)
- PC(Windows10、Node.js が動けばなんでもよい)
実装したコード
例として C:\Users\username\touch\app.js として作成
'use strict';
const five = require('johnny-five'); // johnny-fiveモジュールを使う
const arduino = new five.Board({ // arduinoを接続
port: 'COM5' // COMポート番号(環境による)
});
const MPR121ADDR = 0x5A; // MPR121静電容量タッチセンサコントローラのアドレス
// MPR121静電容量タッチセンサコントローラの初期化関数
function mpr121Config(device) {
// Section A: dataがベースラインより大きい時のフィルタリング
device.i2cWrite(MPR121ADDR, 0x2B, 0x01); // MHD_R
device.i2cWrite(MPR121ADDR, 0x2C, 0x01); // NHD_R
device.i2cWrite(MPR121ADDR, 0x2D, 0x00); // NCL_R
device.i2cWrite(MPR121ADDR, 0x2E, 0x00); // FDL_R
// Section B: dataがベースラインより小さい時のフィルタリング
device.i2cWrite(MPR121ADDR, 0x2F, 0x01); // MHD_F
device.i2cWrite(MPR121ADDR, 0x30, 0x01); // NHD_F
device.i2cWrite(MPR121ADDR, 0x31, 0xFF); // NCL_L
device.i2cWrite(MPR121ADDR, 0x32, 0x02); // FDL_L
// Section C: 各電極(ELE0-11)の閾値(T:Touch,R:Release)の設定
device.i2cWrite(MPR121ADDR, 0x41, 0x0F); // ELE0_T
device.i2cWrite(MPR121ADDR, 0x42, 0x0A); // ELE0_R
device.i2cWrite(MPR121ADDR, 0x43, 0x0F); // ELE1_T
device.i2cWrite(MPR121ADDR, 0x44, 0x0A); // ELE1_R
device.i2cWrite(MPR121ADDR, 0x45, 0x0F); // ELE2_T
device.i2cWrite(MPR121ADDR, 0x46, 0x0A); // ELE2_R
device.i2cWrite(MPR121ADDR, 0x47, 0x0F); // ELE3_T
device.i2cWrite(MPR121ADDR, 0x48, 0x0A); // ELE3_R
// device.i2cWrite(MPR121ADDR, 0x49, 0x0F); // ELE4_T // 電極4以降も使う場合
// device.i2cWrite(MPR121ADDR, 0x4A, 0x0A); // ELE4_R
// device.i2cWrite(MPR121ADDR, 0x4B, 0x0F); // ELE5_T
// device.i2cWrite(MPR121ADDR, 0x4C, 0x0A); // ELE5_R
// device.i2cWrite(MPR121ADDR, 0x4D, 0x0F); // ELE6_T
// device.i2cWrite(MPR121ADDR, 0x4E, 0x0A); // ELE6_R
// device.i2cWrite(MPR121ADDR, 0x4F, 0x0F); // ELE7_T
// device.i2cWrite(MPR121ADDR, 0x50, 0x0A); // ELE7_R
// device.i2cWrite(MPR121ADDR, 0x51, 0x0F); // ELE8_T
// device.i2cWrite(MPR121ADDR, 0x52, 0x0A); // ELE8_R
// device.i2cWrite(MPR121ADDR, 0x53, 0x0F); // ELE9_T
// device.i2cWrite(MPR121ADDR, 0x54, 0x0A); // ELE9_R
// device.i2cWrite(MPR121ADDR, 0x55, 0x0F); // ELE10_T
// device.i2cWrite(MPR121ADDR, 0x56, 0x0A); // ELE10_R
// device.i2cWrite(MPR121ADDR, 0x57, 0x0F); // ELE11_T
// device.i2cWrite(MPR121ADDR, 0x58, 0x0A); // ELE11_R
// Section D: フィルタの設定
device.i2cWrite(MPR121ADDR, 0x5D, 0x04); // よく分かりません
// Section E: 電極の設定
device.i2cWrite(MPR121ADDR, 0x5E, 0x0C); // 12個全ての電極を利用
}
// arduinoの準備ができたら
arduino.on('ready', function() {
this.i2cConfig(); // I2Cを使うためのjohnny-fiveのおまじない
mpr121Config(this); // MPR121静電容量タッチセンサコントローラを初期設定
// センサの状態の読み込みとタッチの判定
this.i2cRead(MPR121ADDR, 1, function(bytes){ // センサを4個以上使う場合は2バイト読む?
if((bytes & 0x01) == 0x01) { // ch0を判定
console.log('ch0 is touched');
}
if((bytes & 0x02) == 0x02) { // ch1を判定
console.log('ch1 is touched');
}
if((bytes & 0x04) == 0x04) { // ch2を判定
console.log('ch2 is touched');
}
if((bytes & 0x08) == 0x08) { // ch3を判定
console.log('ch3 is touched');
}
});
});
実行と動作確認
- Node.js コマンドプロンプト にて johnny-five をインストール
C:\Users\username> npm install johnny-five
- Node.js コマンドプロンプト で以下を実行
C:\Users\username\touch> node app.js
- タッチセンサに触れた時に 'ch x is touched'( x はch番号)とコンソールに表示されればOK
- 同時に複数のセンサに触れても大丈夫です
解説
- このセンサには、MPR121というコントローラが使われており、そのI2Cアドレスは 0x5A だそうで、データの読み書き(i2cRead( ) と i2cWrite( ))にはそのアドレスを用います(コード中の MPR121ADDR)
- 初期設定の関数(mpr121Config( ) { })の中身は、開発元のサンプルコードを参考にしており、内容については私は理解できていません
- なお、センサ電極(feeler)を5個以上使う場合は、コメントアウトした部分も使います
- i2cRead(address, bytesToRead, handler(arrayOfBytes)) は、繰り返し、指定addressの値をbytesToReadぶん読み、データarrayOfBytesを伴ってhandlerをコールします
- 各チャンネルのセンサに個別に触れると以下のデータ(bytes)が得られます
ch | bytes | ビット列 |
---|---|---|
0 | 0x01 | □□□■ |
1 | 0x02 | □□■□ |
2 | 0x04 | □■□□ |
3 | 0x08 | ■□□□ |
(□=0, ■=1 以下同様) |
- 複数同時に触れると、それぞれの bytes が足し合わされた(OR された)値が返ってきます
chの組み合わせ | bytes | ビット列 |
---|---|---|
0, 1 | 0x03 | □□■■ |
0, 2 | 0x05 | □■□■ |
0, 3 | 0x09 | ■□□■ |
1, 2 | 0x06 | □■■□ |
1, 3 | 0x0A | ■□■□ |
2, 3 | 0x0C | ■■□□ |
0, 1, 2 | 0x07 | □■■■ |
0, 1, 3 | 0x0B | ■□■■ |
0, 2, 3 | 0x0D | ■■□■ |
1, 2, 3 | 0x0E | ■■■□ |
0, 1, 2, 3 | 0x0F | ■■■■ |
- 各chに触れているか否かの判定は、戻り値 bytes と所望のchの値との AND が、所望のchの値になるかどうかを見ればよいことになります
- 例えば、0x0F(■■■■) & 0x01(□□□■) は 0x01(□□□■) となり、ch0に触れられて__いる__ことがわかる
- 同様に、0x0E(■■■□) & 0x01(□□□■) は 0x00(□□□□) となり、ch0に触れられて__いない__ことがわかる
if((bytes & 0x01) == 0x01) { // ch0を判定
console.log('ch0 is touched');
}
if((bytes & 0x02) == 0x02) { // ch1を判定
console.log('ch1 is touched');
}
if((bytes & 0x04) == 0x04) { // ch2を判定
console.log('ch2 is touched');
}
if((bytes & 0x08) == 0x08) { // ch3を判定
console.log('ch3 is touched');
}
- 今回のコードでは、4つのセンサ分(1バイト)しかデータを取っていません
- センサ電極(feeler)を追加購入してもっとたくさんつなげますが、その際は、以下の i2cRead(address, bytesToRead, handler(arrayOfBytes)) で複数バイト読んできて…(あとはご自身でご想像・お試しください)
// センサの状態の読み込みとタッチの判定
this.i2cRead(MPR121ADDR, 1, function(bytes){ ... }
所感や今後
よく分からないことだらけでしたが、サンプルコード(C++)を頑張って解読して、試行錯誤の末、なんとか JavaScript(johnny-five)に移植することができました。
これで、書籍「二足歩行ロボット 工作&プログラミング(リックテレコム)」の内容をほぼすべて JavaScript(Node.js)に書き換えられそうな気がしてきました。さらに、socket.io と組み合わせることで、遠隔でのセンシングが可能になります。