EdisonとAPDS9960でジェスチャー認識

# 1. はじめに

　APDS9960はAVAGO Technologies社から発売されている光学式のジェスチャーセンサーモジュールです。センサの前で手を振ることで上下左右の動きと接近と遠ざかりの動作を検出することができます。また、光学素子はカラーセンサーになっており、目の前の物体が何色かを判定することもできます。サイズはとても小型でスマートフォンなどに搭載することを想定しているようです。
　今回はこのAPDS9960をEdisonから使いたいと思います。ネットワーク越しに手のひらの動きで指示を送り、様々なものをコントロールできるIoTコントローラーのようなものが作れるかもしれません。

図1 APDS9960(秋月電子ＡＰＤＳ９９６０使用光学式ジェスチャーセンサモジュールキット)
![IMG_2486.JPG](https://qiita-image-store.s3.amazonaws.com/0/3264/bff87409-2901-77f3-d487-060f341311ae.jpeg)

http://akizukidenshi.com/catalog/g/gK-09754/

# 2. センサの仕組み
　APDS9960はどのようにジェスチャーを認識するのでしょうか。APDS9960は４つの受光素子（フォトトランジスタ）と赤外線LEDが搭載されています。LEDから照射された赤外線が手で反射されて、4つの受光素子で受光します。この時、手が上下左右に動くと各受光素子で受光する光の量が変わります。４つの受光素子で受光する光量変化の組み合わせでジェスチャーを認識することができます。
　受光素子は図2のように配置されています。

図2 APDS9960の受光素子の配置
![diode.png](https://qiita-image-store.s3.amazonaws.com/0/3264/43227215-fd61-ab7b-45fe-40511783bfd2.png)


ここで手をセンサの上側から下側方向に動かしてみましょう。そうするとダイオードDから値が出力され、その後Uの出力が始まります。LRの出力タイミングはほぼ同時です。つまり４つのダイオードの信号波形の位相差を見ることで簡単に手の動いた方向を検知することができます。それぞれでのダイオードの出力はI2Cのアドレスが対応しています。それぞれU=0xFC D=0xFD L=0xFE R=0xFFです。
データシートに図とともに解説が載っていますので詳細はAPDS9960のデータシートFigure11を参照してください。

* データシートは以下のサイトからダウンロードできます。

http://www.avagotech.co.jp/products/optical-sensors/integrated-ambient-light-and-proximity-sensors/apds-9960

# 3. 回路の作成
　APDS9960は非常に小さな部品です。また部品に直接半田付けをするには難易度が高いです。そこで、秋月電子でパッケージ化されて販売されているＡＰＤＳ９９６０使用光学式ジェスチャーセンサモジュールキットを利用します(図1) 
  APDS9960はI2Cで接続されます。そのためEdisonとの接続は非常に簡単です。Intel Edison Kit for ArduinoやEagletボードの場合は直接I2Cで接続することができます。ただし、APDS9960は3.3V動作のため、Intel Edison Kit for Arduinoを使用する場合は5V電源に接続しないように気をつけてください。
　秋月のパッケージを利用する場合はプルアップのジャンパーを半田付けせず直接接続します。またBreakoutボードを利用する場合はI2Cの信号を3.3Vにレベル変換してください。LEDの電源は抵抗をつながなくても良さそうです。（パッケージの中に抵抗が入っている？要確認）

図4 Intel Edison Kit for Arduinoと接続
![ADPS_ブレッドボード.png](https://qiita-image-store.s3.amazonaws.com/0/3264/940042f1-1d4b-0023-6a37-a506e169f88f.png)

図が雑ですが接続は単純なので省略させていただきます。Frizingの部品を作れるようになりたい。

# 4. プログラム
コードはAPDS9960を制御するモジュールとメイン処理に分けました。APDS9960.jsだけでライブラリっぽく利用可能です。実際のセンサハンドリングの処理はAPDS9960.jsで記載します。

* main.js

```js:main.js

var mraa = require('mraa');
var gestureSensor = require('./APDS9960.js');

//setup sensor
var port = 6
var adp9960 = new gestureSensor.APDS9960(port);


adp9960.callback = function(result){
    if(result === 'UP'){
        gestureResult = new Buffer([0x0A]);
        countLeft = 0;
    }else if(result === 'DOWN'){
        gestureResult = new Buffer([0x0B]);
        countLeft = 0;
    }else if(result === 'LEFT'){
        gestureResult = new Buffer([0x0C]);
        countLeft += 1;
        console.log("%d",countLeft);
        
    }else if(result === 'RIGHT'){
        gestureResult = new Buffer([0x0D]);
        countLeft = 0;
    }else{
        gestureResult = new Buffer([0xFF]);
        countLeft = 0;
    }        
    console.log(result);
}

adp9960.getGesture();

```

* APDS9960.js

まず初期化の際にI2Cの設定を行います。センサのI2Cバス上のアドレスは0x39です。APDS9960はジェスチャー認識を行うためにはI2Cのレジスタを順番に初期化する必要があります。データシートにステート図などがありますのでそちらを参考にしてください。スリープモードやカラー検出、接近センサなどをONにすることができます。今回はsensorWakeUpの中でジェスチャー認識のための設定を行っています。各種設定後、0xABレジスタを0x01にする（最下位bitを1）にすることでジェスチャー認識が開始されます。その後、0xFC〜0XFFまでの4Byteのデータを読み取り、時系列に比較することで、ジェスチャーを認識させることができます。getGestureを参照ください。

```js:APDS9960.js
APDS9960.js
var mraa = require('mraa');

exports.APDS9960=function(port){
    this.sensor = new mraa.I2c(port);
    this.address = 0x39;
    this.isGestureFrame = false;
    this.firstFrameVirtical = 0;
    this.lastFrameVirtical = 0;
    this.firstFrameHorizon = 0;
    this.lastFrameHorizon = 0;
    this.init()
};

exports.APDS9960.prototype = {
    
    init:function(){
        this.sensor.address(this.address);
        this.sensorWakeUp();
        this.setOffsetGesture(0,0,0,0);
        this.startGestureRecog();
    },
    
    sensorWakeUp:function(){
        this.sensor.writeReg(0x80, 0x41);
        this.sensor.writeReg(0x90, 0x30);
        this.sensor.writeReg(0xA3,0x64);
    
    },
    setOffsetGesture:function(up,down,right,left){
        this.sensor.writeReg(0xA4,70);        //U MINUS OFFSET
        this.sensor.writeReg(0xA5,0);         //D MINUS OFFSET
        this.sensor.writeReg(0xA7,4);        //L MINUS OFFSET
        this.sensor.writeReg(0xA9,34); 
    },
    
    startGestureRecog:function(){
        this.sensor.writeReg(0xAB,0x01);
    },
    
    callback:function(result){
        console.log(result);
    },
    
    getGesture:function(){
        var sensor = this.sensor;
        var fifo = sensor.readReg(0xAE);
        
        if(fifo > 0 ){
            var up_new = sensor.readReg(0xFC);
            var down_new = sensor.readReg(0xFD);
            var left_new = sensor.readReg(0xFE);
            var right_new = sensor.readReg(0xFF);
            
            
            if(up_new > 0 || down_new > 0 || left_new > 0 || right_new > 0){
                var diffVirtical = down_new - up_new;
                var diffHorizon = left_new - right_new;
                //console.log("%d,%d,%d,%d",up_new, down_new, left_new, right_new);
                if(!this.isGestureFrame){
                 //is first frame
                    if(diffVirtical != 0){
                        this.firstFrameVirtical = diffVirtical;
                    }
                    if((diffHorizon) != 0){
                        this.firstFrameHorizon = diffHorizon;
                    }
                }else{
                    if(diffVirtical != 0){
                        this.lastFrameVirtical = diffVirtical;
                    }
                    if((diffHorizon) != 0){
                        this.lastFrameHorizon = diffHorizon;
                    }
                }
                this.isGestureFrame = true;
                //console.log("%d,%d,%d,%d",up_new, down_new, left_new, right_new);
                //console.log("Vietical:%d, Horizon:%d",(down_new-up_new),(left_new-right_new));
            }else{
              //  console.log("%d,%d,%d,%d",up_new, down_new, left_new, right_new);
                if(this.isGestureFrame){
                    var resultGesture = "FAILED RECOGNITION";
                    if(this.firstFrameVirtical >= 0 && 0 > this.lastFrameVirtical){
                        resultGesture = "DOWN";
                    }else if(this.firstFrameVirtical <= 0 && 0 < this.lastFrameVirtical){
                        resultGesture = "UP";    
                    }else if(this.firstFrameHorizon > 0 && 0 >= this.lastFrameHorizon){
                        resultGesture = "LEFT";
                    }else if(this.firstFrameHorizon <= 0 && 0 < this.lastFrameHorizon){
                        resultGesture = "RIGHT";
                    }
                    this.callback(resultGesture);
                    this.isGestureFrame = false;
                }
            }
        }
        setTimeout(this.getGesture.bind(this),10);
    }

};

```

# 5. まとめ

　今回はジェスチャーの認識機能のみを利用しましたが、センサ自体は非常に多機能でカラーや接近センサとしても利用できます。また、センサデータのFIFOやセンサ自体のパワー制御をすることもできます。
　また今回紹介したセンサデータ処理は力技でやっています。k-meansやHMMなどの機械学習手法を使い、データ処理してみるのもセンサデータの機械学習の練習として楽しいかもしれません。

　先日翔泳社様から「Intel Edisonで始めるIoTプロトタイピング」という本を発売しました。こういったセンサの使い方やBLE,WEBサービスなど幅広く書いていますのでご興味があればご覧いただければ幸いです。

http://amazon.co.jp/Intel-EdisonではじめるIoTプロトタイピング-河村-雅人/dp/4798143391