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つのデータを取得し続けることができた。実際にはウェイトで時間間隔を調整して、必要なレートで取得する形になるだろう。