APDS-9960は、明るさと色、近接やジェスチャ操作の検出ができるセンサである。今回はこれをジェスチャの検出のみに使い、速い動きまで取りこぼしなく検出できるようにすることを目指す。
APDS-9960の動作について
図はデータシートにある、APDS-9960の動作を示したものである。一般的な使い方としては、
- 近接の検出
↓ - 近接する物体があればジェスチャ検出
↓ - 明るさと色の検出
という動作を繰り返すようになっている。したがって物体が近くになければ、ジェスチャ検出のモードには入らない形になる。
今回の目的としては、できるだけ速くジェスチャ検出を繰り返したい。そこで、ジェスチャ検出のみを繰り返すという少し特殊な使い方をしている。
ジェスチャ検出について
こちらの図もデータシートから引用したものである。ジェスチャの検出では、APDS-9960上の4か所のフォトダイオードで検出した、それぞれの近接データが使われる。各位置での4つのデータがセットでFIFOに格納されていくので、その値の大小関係や変化を見て、ジェスチャの判定をするようになっている。今回はまず、4つのデータセットを継続的に読み出すところまでやってみるようにした。
APDS-9960の制御について
APDS-9960の制御には、ESP32を使用した。VSCode、PlatformIO、Arduinoフレームワークの組み合わせで使っている。Arduinoライブラリがいくつか存在しているが、前述のように今回の使い方は特殊なため、ライブラリを使って設定しようとすると結構面倒なことになりそうだった。そこでライブラリはWireのみを使用して、後は直接レジスタを設定する形にしている。
ジェスチャ検出のみで使用する場合に必要なコードが、以下のものである。ループ処理では、FIFOに新しいデータが入っているかを確認し、FIFOからデータを読み出してバッファに入れている。
/* Your code... */
#include <Arduino.h>
#include <Wire.h>
#define APDS_ADDRESS 0x39
#define APDS_ENABLE 0x80
#define APDS_PPULSE 0x8E
#define APDS_CONTROL 0x8F
#define APDS_ID 0x92
#define APDS_STATUS 0x93
#define APDS_GCONF1 0xA2
#define APDS_GCONF2 0xA3
#define APDS_GPULSE 0xA6
#define APDS_GCONF4 0xAB
#define APDS_GFLVL 0xAE
#define APDS_GSTATUS 0xAF
#define APDS_GFIFO 0xFC
#define GWTIME_0 0x00
#define GWTIME_2_8 0x01
#define GWTIME_5_6 0x02
#define GWTIME_8_4 0x03
#define GLDRIVE_100 0x00
#define GLDRIVE_50 0x08
#define GGAIN_1 0x00
#define GGAIN_2 0x20
#define GGAIN_4 0x40
#define GGAIN_8 0x60
#define GCONF2_VAL (GWTIME_0+GLDRIVE_100+GGAIN_1)
#define GPLEN_4us 0x00
#define GPLEN_8us 0x40
#define GPLEN_16us 0x80
#define GPLEN_32us 0xC0
#define GPULSE_S (-1)
#define GPULSE_VAL (GPLEN_8us+GPULSE_S+16)
#define GFIFO_CLR 0x04
#define GMODE 0x01
#define GCONF4_VAL GMODE
#define LDRIVE_100 0x00
#define LDRIVE_50 0x40
#define PGAIN_1 0x00
#define PGAIN_2 0x04
#define PGAIN_4 0x08
#define PGAIN_8 0x0C
#define CONTROL_VAL (LDRIVE_100+PGAIN_1)
#define GEN 0x40
#define PEN 0x04
#define PON 0x01
#define TEST_NUM 200
unsigned char dataBuf[TEST_NUM][4];
unsigned char apdsReadByte( int address, int reg ){
Wire.beginTransmission(address);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(address,1);
unsigned char ret = Wire.read();
return ret;
}
int apdsReadBlock( int address, int reg, int len, unsigned char *data ){
int i = 0;
Wire.beginTransmission(address);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(address,len);
while (Wire.available()){
if (i==len){
return 0;
}
*(data+i) = Wire.read();
i++;
}
return i;
}
void apdsWriteReg( int address, int reg, unsigned char val ){
Wire.beginTransmission(APDS_ADDRESS);
Wire.write(reg);
Wire.write(val);
Wire.endTransmission(true);
}
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000);
int clk = Wire.getClock();
Serial.printf("I2C clock %d\n", clk);
int id = apdsReadByte(APDS_ADDRESS,0x92);
Serial.printf("ID:0x%02X\n", id);
apdsWriteReg(APDS_ADDRESS,APDS_ENABLE,0x00);
apdsWriteReg(APDS_ADDRESS,APDS_GPULSE,GPULSE_VAL);
apdsWriteReg(APDS_ADDRESS,APDS_GCONF2,GCONF2_VAL);
apdsWriteReg(APDS_ADDRESS,APDS_GCONF4,GCONF4_VAL);
apdsWriteReg(APDS_ADDRESS,APDS_CONTROL,CONTROL_VAL);
apdsWriteReg(APDS_ADDRESS,APDS_ENABLE,GEN+PEN+PON);
}
void loop() {
apdsWriteReg(APDS_ADDRESS,APDS_GCONF4,GFIFO_CLR+GMODE);
int start = millis();
for (int i=0; i<TEST_NUM; i++){
while(1){
int size = apdsReadByte(APDS_ADDRESS,APDS_GFLVL);
if (size>0){
apdsReadBlock( APDS_ADDRESS, APDS_GFIFO, 4, dataBuf[i]);
break;
}
}
}
int end = millis();
for (int i=0;i<10;i++){
Serial.printf("%d,%d,%d,%d\n", dataBuf[i][0], dataBuf[i][1], dataBuf[i][2], dataBuf[i][3]);
}
Serial.printf("Time: %dms\n", (end-start));
double ave = (double)(end-start)/TEST_NUM;
Serial.printf("Average: %.2fms\n", ave);
delay(5000);
}
テスト結果
上記のコードで動作させたときのシリアル出力の結果が上の図である。ウェイトなどを入れない場合、約3msの間隔で、上下左右の4つのデータを取得し続けることができた。実際にはウェイトで時間間隔を調整して、必要なレートで取得する形になるだろう。