今回は以前から気になっていた秋月電子で販売されているこちらのジェスチャーセンサー APDS-9960 を使ってみました。ネットの情報も少なく、Arduinoのライブラリを使うだけしか書いてなかったりしたので、自分でデータシートを頼りに実験してみました。
まず商品ページを見たらデータシートが無いじゃないか!と思ったら説明書に地味にリンクが書いてありました。そこに書くんだったら商品ページにもリンク貼っておいてくれよ秋月さんと思いつつデータシートを入手。データシートはこちら。
当然英語ですがこれ位の量なら頑張って読もうと思える範囲で助かりました。
とは言いつつ1からデータシートを基にプログラムを書くのは難しいので秋月のサンプルプログラムも入手。Arduino用ですが、C言語みたいなものなので参考にはなるでしょう。
まずこのセンサーの仕組みを説明すると、このように赤外線LEDと U,D,L,R の4つのフォトダイオードが搭載されていて
その上を物体が通過するとこのような信号が得られます。
この立ち上がりのタイミングで方向を検知するという仕組みです。
ではサンプルプログラムを読み解いていきましょう。これがメインループですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void loop() { work = I2C_READ(0xAE); //READ GESTUR FIFO LEVEL REGISTER if(work != 0) //IF FIFO HAS SOME DATA { DATA_U = I2C_READ(0xFC); DATA_D = I2C_READ(0xFD); DATA_L = I2C_READ(0xFE); DATA_R = I2C_READ(0xFF); if((DATA_U > NOISE_LEVEL) && (DATA_D > NOISE_LEVEL)&& (DATA_L> NOISE_LEVEL) && (DATA_R > NOISE_LEVEL)) //NOISE CANCEL { DATA_SYORI(); // PHASE_COUNTER++; // DISP_FLAG = 1; // } else { if(DISP_FLAG) { DISP_FLAG = 0; DISP_DIR(); } RESET_VARIABLE(); } } } |
ノイズというのは物体が検知されていないときのことですね。PHASE_COUNTERという変数で検知からの時間を計っているようです。
続いてデータ処理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
void DATA_SYORI(void) { if (DATA_U > OLD_U) //IF NEW_DATA > OLD_DATA_BUFFER(APROACH TO PEAK) { OLD_U = DATA_U; //SAVE NEW_DATA TO OLD_DATA_BUFFER U_PEAK = PHASE_COUNTER; //PEAK_PHASE RENEWAL U_PEAK_END_FLAG = 0; //STILL PEAK or APROACH TO PEAK } else { U_PEAK_END_FLAG = 1; //PEAK WAS GONE } //************************** if (DATA_D > OLD_D) { OLD_D = DATA_D; D_PEAK = PHASE_COUNTER; D_PEAK_END_FLAG = 0; } else { D_PEAK_END_FLAG = 1; } //************************** if (DATA_L > OLD_L) { OLD_L = DATA_L; L_PEAK = PHASE_COUNTER; L_PEAK_END_FLAG = 0; } else { L_PEAK_END_FLAG = 1; } //************************* if (DATA_R > OLD_R) { OLD_R = DATA_R; R_PEAK = PHASE_COUNTER; R_PEAK_END_FLAG = 0; } else { R_PEAK_END_FLAG = 1; } //************************** if(U_PEAK_END_FLAG && D_PEAK_END_FLAG && L_PEAK_END_FLAG && R_PEAK_END_FLAG) //IF ALL PEAK WAS GONE { DECIDE_FLAG = 0; if ((U_PEAK > D_PEAK) & (U_PEAK >= L_PEAK) & (U_PEAK >= R_PEAK)) //U_PEAK WAS LAST { SERIAL_STRING = "DOWN"; DECIDE_FLAG = 1; } if ((D_PEAK > U_PEAK) & (D_PEAK >= L_PEAK) & (D_PEAK >= R_PEAK)) //D_PEAK WAS LAST { SERIAL_STRING = "UP"; DECIDE_FLAG = 1; } if ((L_PEAK >= U_PEAK) & (L_PEAK >= D_PEAK) & (L_PEAK > R_PEAK)) //L_PEAK WAS LAST { SERIAL_STRING = "RIGHT"; DECIDE_FLAG =1; } if ((R_PEAK >= U_PEAK) & (R_PEAK >= D_PEAK) & (R_PEAK > L_PEAK)) //R_PEAK WAS LAST { SERIAL_STRING = "LEFT"; DECIDE_FLAG = 1; } if (!DECIDE_FLAG)SERIAL_STRING = "NONE"; //CAN'T DECIDE } } |
同じ処理が何度も出てきてまとめたくなりますね。内容としては、ピークを見つけてそのタイミングを記録し、最もタイミングの遅かった方向を判断するという感じです。
さて、大体内容が分かったので、このサンプルをベースにプログラムを書いていきましょう。
まず最初に手を加えたのはノイズの処理です。サンプルではノイズ状態のとき毎回 RESET_VARIABLE() を呼び出していますが、これは無駄です。これを改善する方法がデータシートにありました。
During operation, the Gesture engine is entered when its enable bit, GEN, and the operating mode bit, GMODE, are both set. GMODE can be set/reset manually, via I²C, or becomes set when proximity results, PDATA, is greater or equal to the gesture proximity entry threshold, GPENTH.
ジェスチャーエンジンの ON/OFF を近接センサーの値で制御できるようです。物体が近づいたときに GMODE = 1 とする閾値の GPENTH と、離れたときに GMODE = 0 とする閾値の GEXTH を設定します。閾値は適当に 10 としておきました。
次に見直したのは 赤外線LED の設定です。サンプルでは駆動電流 100 mA、ブースト 300 % となっていますが、これでテストしてみると、各方向の受信データが頭打ちしてしまいました。
使用条件にもよるでしょうが、今回はセンサーから 2 cm ぐらいでの使用を想定しているので、それに合わせて調整した結果、駆動電流 25 mA 、ブースト 100 % としました。
続いて遅延時間がサンプルでは 14 ms となっているのですが、サンプリング周期が短いほどピークのズレも検出しやすくなると思いますので、遅延を 2.8 ms としました。
このとき、マイコンのクロックが遅いと処理が追い付かなかったりしたので、FIFO が一杯になっていないか確認した方が良いと思います。
あとは各方向のデータを配列にして繰り返し処理にしたり、ピーク検出後物体が離れるまで処理をスキップするようにしてプログラムが完成しました。
ジェスチャーの方向を読み取って LED を点灯させるプログラムです。少々誤作動もありますが、今回は許容範囲内とします。
以下テスト動画とソースコード、回路図です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
#pragma config FEXTOSC = OFF // FEXTOSC External Oscillator mode Selection bits (Oscillator not enabled) #pragma config RSTOSC = HFINT1 // Power-up default value for COSC bits (HFINTOSC (1MHz)) #pragma config CLKOUTEN = OFF // Clock Out Enable bit (CLKOUT function is disabled; I/O or oscillator function on OSC2) #pragma config CSWEN = ON // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed) #pragma config FCMEN = ON // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is enabled) #pragma config MCLRE = ON // Master Clear Enable bit (MCLR/VPP pin function is MCLR; Weak pull-up enabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config WDTE = OFF // Watchdog Timer Enable bits (WDT disabled; SWDTEN is ignored) #pragma config LPBOREN = OFF // Low-power BOR enable bit (ULPBOR disabled) #pragma config BOREN = OFF // Brown-out Reset Enable bits (Brown-out Reset disabled) #pragma config BORV = LOW // Brown-out Reset Voltage selection bit (Brown-out voltage (Vbor) set to 2.45V) #pragma config PPS1WAY = ON // PPSLOCK bit One-Way Set Enable bit (The PPSLOCK bit can be cleared and set only once; PPS registers remain locked after one clear/set cycle) #pragma config STVREN = ON // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a Reset) #pragma config DEBUG = OFF // Debugger enable bit (Background debugger disabled) #pragma config WRT = OFF // User NVM self-write protection bits (Write protection off) #pragma config LVP = OFF // Low Voltage Programming Enable bit (High Voltage on MCLR/VPP must be used for programming.) #pragma config CP = OFF // User NVM Program Memory Code Protection bit (User NVM code protection disabled) #pragma config CPD = OFF // Data NVM Memory Code Protection bit (Data NVM code protection disabled) #include <xc.h> #include <stdio.h> #include "i2c.h" #define _XTAL_FREQ 16000000 bit Gesture_End = 0; char UDLR[4]; char UDLR_Old[4]; char UDLR_Peak[4]; char PhaseCounter; char Direction; char UDLR_PeakEndFlag; void init(){ OSCFRQ = 0x06; //3 : 4MHz/ 4 : 8MHz/ 6 : 16MHz/ 7 : 32MHz OSCCON1bits.NDIV = 0x0; //Clock Divider 1/1 TRISA = 0b00001000; TRISC = 0b00110000; ANSELA = 0b00000000; ANSELC = 0b00000000; WPUA = 0b00000000; WPUC = 0b00000000; PORTA = 0b00000000; PORTC = 0b00000000; } void APDS_Send(char address, char data){ I2C_Master_Start(); I2C_Master_Write(0x72); //SlaveAdress + Write I2C_Master_Write(address); //RegisterAdress I2C_Master_Write(data); I2C_Master_Stop(); } void APDS_Read(){ I2C_Master_Start(); I2C_Master_Write(0x72); //SlaveAdress + Write I2C_Master_Write(0xFC); //Adress I2C_Master_RepeatedStart(); I2C_Master_Write(0x73); //SlaveAdress + Read UDLR[0] = I2C_Master_Read(0); UDLR[1] = I2C_Master_Read(0); UDLR[2] = I2C_Master_Read(0); UDLR[3] = I2C_Master_Read(1); I2C_Master_Stop(); } char APDS_FIFOCheck(){ char check = 0; I2C_Master_Start(); I2C_Master_Write(0x72); //SlaveAdress + Write I2C_Master_Write(0xAE); //Adress I2C_Master_RepeatedStart(); I2C_Master_Write(0x73); //SlaveAdress + Read check = I2C_Master_Read(1); I2C_Master_Stop(); return check; } char APDS_GMODECheck(){ char check = 0; I2C_Master_Start(); I2C_Master_Write(0x72); //SlaveAdress + Write I2C_Master_Write(0xAB); //Adress I2C_Master_RepeatedStart(); I2C_Master_Write(0x73); //SlaveAdress + Read check = I2C_Master_Read(1); I2C_Master_Stop(); return check & 0x01; } void APDS_ResetVariable(){ for(char i=0; i<4; i++) UDLR_Old[i] = 0; UDLR_PeakEndFlag = 0; PhaseCounter = 0; } void APDS_Init(){ APDS_Send(0x80,0x45); //POWER ON, GESTURE ENABLE, PROXIMITY DETECT ENALBE,AEN=0 APDS_Send(0xA3,0x71); //Gain x8, LED Drive 25mA, Wait Time 2.8mS APDS_Send(0xAB,0x00); //GIEN off(INTERRUPT DISABLE), GMODE OFF APDS_Send(0xA0,10); // Enter Threshold APDS_Send(0xA1,10); // Exit Threshold APDS_ResetVariable(); } char APDS_Process(){ for(char i=0; i<4; i++){ if(UDLR[i] > UDLR_Old[i]){ // Aproaching to Peak UDLR_Old[i] = UDLR[i]; UDLR_Peak[i] = PhaseCounter; } else{ // Peak Was Gone UDLR_PeakEndFlag = (UDLR_PeakEndFlag & ~(1 << i)) + (1 << i); } } if(UDLR_PeakEndFlag == 0b1111){ // All Peak Was Gone /* Find Last Peak */ if((UDLR_Peak[0] > UDLR_Peak[1]) && (UDLR_Peak[0] >= UDLR_Peak[2]) && (UDLR_Peak[0] >= UDLR_Peak[3])) Direction = 0; else if((UDLR_Peak[1] > UDLR_Peak[0]) && (UDLR_Peak[1] >= UDLR_Peak[2]) && (UDLR_Peak[1] >= UDLR_Peak[3])) Direction = 1; else if((UDLR_Peak[2] > UDLR_Peak[3]) && (UDLR_Peak[2] >= UDLR_Peak[0]) && (UDLR_Peak[2] >= UDLR_Peak[1])) Direction = 2; else if((UDLR_Peak[3] > UDLR_Peak[2]) && (UDLR_Peak[3] >= UDLR_Peak[0]) && (UDLR_Peak[3] >= UDLR_Peak[1])) Direction = 3; else Direction = 4; return 0; // Process Complete } return 1; // Still Processing } int main() { init(); I2C_Master_Init(100000); APDS_Init(); while(1){ if(APDS_FIFOCheck() != 0){ // Data Exist APDS_Read(); if(Gesture_End == 0){ // Before Gesture End PhaseCounter++; if(APDS_Process() == 0){ // Processs & If Complete switch(Direction){ case 0: // Up PORTA = 0b00010000; break; case 1: // Down PORTA = 0b00000001; break; case 2: // Left PORTA = 0b00000010; break; case 3: // Right PORTA = 0b00100000; break; default: // Error PORTA = 0b00000000; break; } APDS_ResetVariable(); Gesture_End = 1; } } } else if((APDS_GMODECheck() == 0) && (Gesture_End == 1)) // Reset Gesture_End Gesture_End = 0; } } |
I2Cライブラリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include<xc.h> #define _XTAL_FREQ 16000000 void I2C_Master_Init(const unsigned long c){ SSP2CON1 = 0x28; //SSP2 Module as Master SSP2CON2 = 0x00; SSP2CON3 = 0x00; SSP2ADD = (_XTAL_FREQ/(4*c))-1; //Setting Clock Speed SSP2STAT = 0x80; SSP2CLKPPS = 0x14; //PPS Settings SSP2DATPPS = 0x15; RC4PPS = 0x1a; RC5PPS = 0x1b; } void I2C_Master_Wait(){ while ((SSP2STAT & 0x04) || (SSP2CON2 & 0x1F)); //Transmit is in progress } void I2C_Master_Start(){ I2C_Master_Wait(); SSP2CON2bits.SEN = 1; //Initiate start condition } void I2C_Master_RepeatedStart(){ I2C_Master_Wait(); SSP2CON2bits.RSEN = 1; //Initiate repeated start condition } void I2C_Master_Stop(){ I2C_Master_Wait(); SSP2CON2bits.PEN = 1; //Initiate stop condition } void I2C_Master_Write(unsigned d){ I2C_Master_Wait(); SSP2BUF = d; //Write data to SSP2BUF } unsigned short I2C_Master_Read(unsigned short a){ unsigned short temp; I2C_Master_Wait(); SSP2CON2bits.RCEN = 1; I2C_Master_Wait(); temp = SSP2BUF; //Read data from SSP2BUF I2C_Master_Wait(); SSP2CON2bits.ACKDT = a; //Acknowledge bit SSP2CON2bits.ACKEN = 1; //Acknowledge sequence return temp; }</xc.h> |
1 2 3 4 5 6 7 |
void I2C_Master_Init(const unsigned long c); void I2C_Master_Wait(); void I2C_Master_Start(); void I2C_Master_RepeatedStart(); void I2C_Master_Stop(); void I2C_Master_Write(unsigned d); unsigned short I2C_Master_Read(unsigned short a); |
次回はこのセンサーの他の機能である近接センサーやカラーセンサーを試してみたいと思います。
次回↓