APDS-9960でカラー・近接・ジェスチャーを1枚で取得するマルチセンサーを紹

目次

はじめに

「手をかざすと反応する」──そんな未来感のある仕組みを、たった1つの小さなモジュールで実現できるのが APDS-9960 です。
しかもこのモジュール、ジェスチャー検出だけではありません。周囲の明るさや対象物の色(R/G/B)まで読み取れる多機能センサー。工作に組み込めば、ちょっとした未来デバイスに早変わりします。

APDS-9960とは何か?センサーの機能と原理

APDS-9960は様々なことができます。

  • RGB+クリアフォトダイオードで「色」を拾う
  • 赤外LED+受光部で「距離(近接)」を測る
  • 受光部を上下左右に分けて「手の動き」を読み取る

これだけが指先サイズの基板に収まっているのだから驚きです。

ジェスチャー検出


センサーの前を手が通り過ぎたときに「どの方向から光が遮られたか」を見ています。たとえばスマホで通話中に顔を近づけると画面が消えるのは、近接センサーが「赤外線の反射が強くなった=顔が近い」と判断しているから。それと同じ仕組みで、APDS-9960は手の動きを上下左右に判別しています。

色検出

色検出は少しカメラに似ています。R(赤)G(緑)B(青)それぞれのフォトダイオードが光を受け、その強さを「積分時間」と呼ばれる一定時間のあいだ溜め込みます。光が強ければ多くの電荷が溜まり、弱ければ少ししか溜まりません。その結果をAD変換して数値として出してくれるので、マイコンからは「赤が12000、緑が13500、青が11000」といった具合に読み取れるわけです。

色検出の弱点

環境光(蛍光灯や日光)が強すぎると、センサーが拾う色が埋もれてしまいます。
対策としては、白色LEDで対象を照らし、遮光フードで余計な光を遮ると、検出精度が上がります。

日常での使われ方

スマートフォンの近接センサー
通話中に顔が近づいたことを検知し、タッチパネルを無効化する。

ディスプレイの自動輝度調整
周囲の明るさを検出して画面を見やすく制御。

ジェスチャ入力
手をかざして左右にスワイプするだけで操作ができる。

組込みIoT機器
簡易的な色判定(赤いカードと青いカードの識別など)や、近接をトリガーとした操作パネル。

Raspberry Pi Picoとの接続方法と配線

Raspberry Pi PicoAPDS-9960
VSYS(39番)Vcc
GND(38番)GND
GP0(1番)SDA
GP1(2番)SLC

MicroPythonで動かしてみよう

ライブラリが必要です。以下のconst.py、device.py、exceptions.pyをapds9960というフォルダに格納して使ってください。有志の方がgithubで公開していただいているものになります。

const.py

# APDS9960 i2c address
APDS9960_I2C_ADDR = 0x39

# APDS9960 gesture parameters
APDS9960_GESTURE_THRESHOLD_OUT = 10
APDS9960_GESTURE_SENSITIVITY_1 = 50
APDS9960_GESTURE_SENSITIVITY_2 = 20

# APDS9960 device IDs
APDS9960_DEV_ID = [0xab, 0x9c, 0xa8, -0x55]

# APDS9960 times
APDS9960_TIME_FIFO_PAUSE = 0.03

# APDS9960 register addresses
APDS9960_REG_ENABLE = 0x80
APDS9960_REG_ATIME = 0x81
APDS9960_REG_WTIME = 0x83
APDS9960_REG_AILTL = 0x84
APDS9960_REG_AILTH = 0x85
APDS9960_REG_AIHTL = 0x86
APDS9960_REG_AIHTH = 0x87
APDS9960_REG_PILT = 0x89
APDS9960_REG_PIHT = 0x8b
APDS9960_REG_PERS = 0x8c
APDS9960_REG_CONFIG1 = 0x8d
APDS9960_REG_PPULSE = 0x8e
APDS9960_REG_CONTROL = 0x8f
APDS9960_REG_CONFIG2 = 0x90
APDS9960_REG_ID = 0x92
APDS9960_REG_STATUS = 0x93
APDS9960_REG_CDATAL = 0x94
APDS9960_REG_CDATAH = 0x95
APDS9960_REG_RDATAL = 0x96
APDS9960_REG_RDATAH = 0x97
APDS9960_REG_GDATAL = 0x98
APDS9960_REG_GDATAH = 0x99
APDS9960_REG_BDATAL = 0x9a
APDS9960_REG_BDATAH = 0x9b
APDS9960_REG_PDATA = 0x9c
APDS9960_REG_POFFSET_UR = 0x9d
APDS9960_REG_POFFSET_DL = 0x9e
APDS9960_REG_CONFIG3 = 0x9f
APDS9960_REG_GPENTH = 0xa0
APDS9960_REG_GEXTH = 0xa1
APDS9960_REG_GCONF1 = 0xa2
APDS9960_REG_GCONF2 = 0xa3
APDS9960_REG_GOFFSET_U = 0xa4
APDS9960_REG_GOFFSET_D = 0xa5
APDS9960_REG_GOFFSET_L = 0xa7
APDS9960_REG_GOFFSET_R = 0xa9
APDS9960_REG_GPULSE = 0xa6
APDS9960_REG_GCONF3 = 0xaA
APDS9960_REG_GCONF4 = 0xaB
APDS9960_REG_GFLVL = 0xae
APDS9960_REG_GSTATUS = 0xaf
APDS9960_REG_IFORCE = 0xe4
APDS9960_REG_PICLEAR = 0xe5
APDS9960_REG_CICLEAR = 0xe6
APDS9960_REG_AICLEAR = 0xe7
APDS9960_REG_GFIFO_U = 0xfc
APDS9960_REG_GFIFO_D = 0xfd
APDS9960_REG_GFIFO_L = 0xfe
APDS9960_REG_GFIFO_R = 0xff

# APDS9960 bit fields
APDS9960_BIT_PON = 0b00000001
APDS9960_BIT_AEN = 0b00000010
APDS9960_BIT_PEN = 0b00000100
APDS9960_BIT_WEN = 0b00001000
APSD9960_BIT_AIEN =0b00010000
APDS9960_BIT_PIEN = 0b00100000
APDS9960_BIT_GEN = 0b01000000
APDS9960_BIT_GVALID = 0b00000001
APDS9960_BIT_AVALID = 0b00000001
APDS9960_BIT_PVALID = 0b00000010
APDS9960_BIT_GINT = 0b00000100
APDS9960_BIT_AINT = 0b00010000
APDS9960_BIT_PINT = 0b00100000
APDS9960_BIT_PGSAT = 0b01000000
APDS9960_BIT_CPSAT = 0b10000000

# APDS9960 modes
APDS9960_MODE_POWER = 0
APDS9960_MODE_AMBIENT_LIGHT = 1
APDS9960_MODE_PROXIMITY = 2
APDS9960_MODE_WAIT = 3
APDS9960_MODE_AMBIENT_LIGHT_INT = 4
APDS9960_MODE_PROXIMITY_INT = 5
APDS9960_MODE_GESTURE = 6
APDS9960_MODE_ALL = 7

# LED Drive values
APDS9960_LED_DRIVE_100MA = 0
APDS9960_LED_DRIVE_50MA = 1
APDS9960_LED_DRIVE_25MA = 2
APDS9960_LED_DRIVE_12_5MA = 3

# Proximity Gain (PGAIN) values
APDS9960_PGAIN_1X = 0
APDS9960_PGAIN_2X = 1
APDS9960_PGAIN_4X = 2
APDS9960_PGAIN_8X = 3

# ALS Gain (AGAIN) values
APDS9960_AGAIN_1X = 0
APDS9960_AGAIN_4X = 1
APDS9960_AGAIN_16X = 2
APDS9960_AGAIN_64X = 3

# Gesture Gain (GGAIN) values
APDS9960_GGAIN_1X = 0
APDS9960_GGAIN_2X = 1
APDS9960_GGAIN_4X = 2
APDS9960_GGAIN_8X = 3

# LED Boost values
APDS9960_LED_BOOST_100 = 0
APDS9960_LED_BOOST_150 = 1
APDS9960_LED_BOOST_200 = 2
APDS9960_LED_BOOST_300 = 3    

# Gesture wait time values
APDS9960_GWTIME_0MS = 0
APDS9960_GWTIME_2_8MS = 1
APDS9960_GWTIME_5_6MS = 2
APDS9960_GWTIME_8_4MS = 3
APDS9960_GWTIME_14_0MS = 4
APDS9960_GWTIME_22_4MS = 5
APDS9960_GWTIME_30_8MS = 6
APDS9960_GWTIME_39_2MS = 7

# Default values
APDS9960_DEFAULT_ATIME = 219                            # 103ms
APDS9960_DEFAULT_WTIME = 246                            # 27ms
APDS9960_DEFAULT_PROX_PPULSE = 0x87                     # 16us, 8 pulses
APDS9960_DEFAULT_GESTURE_PPULSE = 0x89                  # 16us, 10 pulses
APDS9960_DEFAULT_POFFSET_UR = 0                         # 0 offset
APDS9960_DEFAULT_POFFSET_DL = 0                         # 0 offset
APDS9960_DEFAULT_CONFIG1 = 0x60                         # No 12x wait (WTIME) factor
APDS9960_DEFAULT_LDRIVE = APDS9960_LED_DRIVE_100MA
APDS9960_DEFAULT_PGAIN = APDS9960_PGAIN_4X
APDS9960_DEFAULT_AGAIN = APDS9960_AGAIN_4X
APDS9960_DEFAULT_PILT = 0                               # Low proximity threshold
APDS9960_DEFAULT_PIHT = 50                              # High proximity threshold
APDS9960_DEFAULT_AILT = 0xffff                          # Force interrupt for calibration
APDS9960_DEFAULT_AIHT = 0
APDS9960_DEFAULT_PERS = 0x11                            # 2 consecutive prox or ALS for int.
APDS9960_DEFAULT_CONFIG2 = 0x01                         # No saturation interrupts or LED boost  
APDS9960_DEFAULT_CONFIG3 = 0                            # Enable all photodiodes, no SAI
APDS9960_DEFAULT_GPENTH = 40                            # Threshold for entering gesture mode
APDS9960_DEFAULT_GEXTH = 30                             # Threshold for exiting gesture mode    
APDS9960_DEFAULT_GCONF1 = 0x40                          # 4 gesture events for int., 1 for exit
APDS9960_DEFAULT_GGAIN = APDS9960_GGAIN_4X
APDS9960_DEFAULT_GLDRIVE = APDS9960_LED_DRIVE_100MA
APDS9960_DEFAULT_GWTIME = APDS9960_GWTIME_2_8MS
APDS9960_DEFAULT_GOFFSET = 0                            # No offset scaling for gesture mode
APDS9960_DEFAULT_GPULSE = 0xc9                          # 32us, 10 pulses
APDS9960_DEFAULT_GCONF3 = 0                             # All photodiodes active during gesture
APDS9960_DEFAULT_GIEN = 0                               # Disable gesture interrupts

# gesture directions
APDS9960_DIR_NONE = 0
APDS9960_DIR_LEFT = 1
APDS9960_DIR_RIGHT = 2
APDS9960_DIR_UP = 3
APDS9960_DIR_DOWN = 4
APDS9960_DIR_NEAR = 5
APDS9960_DIR_FAR = 6
APDS9960_DIR_ALL = 7

# state definitions
APDS9960_STATE_NA = 0
APDS9960_STATE_NEAR = 1
APDS9960_STATE_FAR = 2
APDS9960_STATE_ALL = 3

device.py

from apds9960.const import *
from apds9960.exceptions import *

from time import sleep

class APDS9960:
    class GestureData:
        def __init__(self):
            self.u_data = [0] * 32
            self.d_data = [0] * 32
            self.l_data = [0] * 32
            self.r_data = [0] * 32
            self.index = 0
            self.total_gestures = 0
            self.in_threshold = 0
            self.out_threshold = 0

    def __init__(self, bus, address=APDS9960_I2C_ADDR, valid_id=APDS9960_DEV_ID):
        # I2C stuff
        self.address = address
        self.bus = bus

        # instance variables for gesture detection
        self.gesture_ud_delta_ = 0
        self.gesture_lr_delta_ = 0

        self.gesture_ud_count_ = 0
        self.gesture_lr_count_ = 0

        self.gesture_near_count_ = 0
        self.gesture_far_count_ = 0

        self.gesture_state_ = 0
        self.gesture_motion_ = APDS9960_DIR_NONE

        self.gesture_data_ = APDS9960.GestureData()

        # check device id
        self.dev_id = self._read_byte_data(APDS9960_REG_ID)
        if not self.dev_id in valid_id:
            raise ADPS9960InvalidDevId(self.dev_id, valid_id)

        # disable all features
        self.setMode(APDS9960_MODE_ALL, False)

        # set default values for ambient light and proximity registers
        self._write_byte_data(APDS9960_REG_ATIME, APDS9960_DEFAULT_ATIME)
        self._write_byte_data(APDS9960_REG_WTIME, APDS9960_DEFAULT_WTIME)
        self._write_byte_data(APDS9960_REG_PPULSE, APDS9960_DEFAULT_PROX_PPULSE)
        self._write_byte_data(APDS9960_REG_POFFSET_UR, APDS9960_DEFAULT_POFFSET_UR)
        self._write_byte_data(APDS9960_REG_POFFSET_DL, APDS9960_DEFAULT_POFFSET_DL)
        self._write_byte_data(APDS9960_REG_CONFIG1, APDS9960_DEFAULT_CONFIG1)
        self.setLEDDrive(APDS9960_DEFAULT_LDRIVE)
        self.setProximityGain(APDS9960_DEFAULT_PGAIN)
        self.setAmbientLightGain(APDS9960_DEFAULT_AGAIN)
        self.setProxIntLowThresh(APDS9960_DEFAULT_PILT)
        self.setProxIntHighThresh(APDS9960_DEFAULT_PIHT)
        self.setLightIntLowThreshold(APDS9960_DEFAULT_AILT)
        self.setLightIntHighThreshold(APDS9960_DEFAULT_AIHT)

        self._write_byte_data(APDS9960_REG_PERS, APDS9960_DEFAULT_PERS)
        self._write_byte_data(APDS9960_REG_CONFIG2, APDS9960_DEFAULT_CONFIG2)
        self._write_byte_data(APDS9960_REG_CONFIG3, APDS9960_DEFAULT_CONFIG3)

        # set default values for gesture sense registers
        self.setGestureEnterThresh(APDS9960_DEFAULT_GPENTH)
        self.setGestureExitThresh(APDS9960_DEFAULT_GEXTH)
        self._write_byte_data(APDS9960_REG_GCONF1, APDS9960_DEFAULT_GCONF1)

        self.setGestureGain(APDS9960_DEFAULT_GGAIN)
        self.setGestureLEDDrive(APDS9960_DEFAULT_GLDRIVE)
        self.setGestureWaitTime(APDS9960_DEFAULT_GWTIME)
        self._write_byte_data(APDS9960_REG_GOFFSET_U, APDS9960_DEFAULT_GOFFSET)
        self._write_byte_data(APDS9960_REG_GOFFSET_D, APDS9960_DEFAULT_GOFFSET)
        self._write_byte_data(APDS9960_REG_GOFFSET_L, APDS9960_DEFAULT_GOFFSET)
        self._write_byte_data(APDS9960_REG_GOFFSET_R, APDS9960_DEFAULT_GOFFSET)
        self._write_byte_data(APDS9960_REG_GPULSE, APDS9960_DEFAULT_GPULSE)
        self._write_byte_data(APDS9960_REG_GCONF3, APDS9960_DEFAULT_GCONF3)
        self.setGestureIntEnable(APDS9960_DEFAULT_GIEN)


    def getMode(self):
        return self._read_byte_data(APDS9960_REG_ENABLE)

    def setMode(self, mode, enable=True):
        # read ENABLE register
        reg_val = self.getMode()

        if mode < 0 or mode > APDS9960_MODE_ALL:
            raise ADPS9960InvalidMode(mode)

        # change bit(s) in ENABLE register */
        if mode == APDS9960_MODE_ALL:
            if enable:
                reg_val = 0x7f
            else:
                reg_val = 0x00
        else:
            if enable:
                reg_val |= (1 << mode)
            else:
                reg_val &= ~(1 << mode)

        # write value to ENABLE register
        self._write_byte_data(APDS9960_REG_ENABLE, reg_val)


    # start the light (R/G/B/Ambient) sensor
    def enableLightSensor(self, interrupts=True):
        self.setAmbientLightGain(APDS9960_DEFAULT_AGAIN)
        self.setAmbientLightIntEnable(interrupts)
        self.enablePower()
        self.setMode(APDS9960_MODE_AMBIENT_LIGHT, True)

    # stop the light sensor
    def disableLightSensor(self):
        self.setAmbientLightIntEnable(False)
        self.setMode(APDS9960_MODE_AMBIENT_LIGHT, False)


    # start the proximity sensor
    def enableProximitySensor(self, interrupts=True):
        self.setProximityGain(APDS9960_DEFAULT_PGAIN)
        self.setLEDDrive(APDS9960_DEFAULT_LDRIVE)
        self.setProximityIntEnable(interrupts)
        self.enablePower()
        self.setMode(APDS9960_MODE_PROXIMITY, True)

    # stop the proximity sensor
    def disableProximitySensor(self):
        self.setProximityIntEnable(False)
        self.setMode(APDS9960_MODE_PROXIMITY, False)


    # start the gesture recognition engine
    def enableGestureSensor(self, interrupts=True):
        self.resetGestureParameters()
        self._write_byte_data(APDS9960_REG_WTIME, 0xff)
        self._write_byte_data(APDS9960_REG_PPULSE, APDS9960_DEFAULT_GESTURE_PPULSE)
        self.setLEDBoost(APDS9960_LED_BOOST_300)
        self.setGestureIntEnable(interrupts)
        self.setGestureMode(True)
        self.enablePower()
        self.setMode(APDS9960_MODE_WAIT, True)
        self.setMode(APDS9960_MODE_PROXIMITY, True)
        self.setMode(APDS9960_MODE_GESTURE, True)

    # stop the gesture recognition engine
    def disableGestureSensor(self):
        self.resetGestureParameters()
        self.setGestureIntEnable(False)
        self.setGestureMode(False)
        self.setMode(APDS9960_MODE_GESTURE, False)


    # check if there is a gesture available
    def isGestureAvailable(self):
        val = self._read_byte_data(APDS9960_REG_GSTATUS)

        # shift and mask out GVALID bit
        val &= APDS9960_BIT_GVALID

        return (val == APDS9960_BIT_GVALID)


    # processes a gesture event and returns best guessed gesture
    def readGesture(self):
        fifo_level = 0
        bytes_read = 0
        fifo_data = []

        # make sure that power and gesture is on and data is valid
        if not (self.getMode() & 0b01000001) or not self.isGestureAvailable():
            return APDS9960_DIR_NONE

        # keep looping as long as gesture data is valid
        while(self.isGestureAvailable()):
            # read the current FIFO level
            fifo_level = self._read_byte_data(APDS9960_REG_GFLVL)

            # if there's stuff in the FIFO, read it into our data block
            if fifo_level > 0:
                fifo_data = []
                for i in range(0, fifo_level):
                    fifo_data += self._read_i2c_block_data(APDS9960_REG_GFIFO_U, 4)

                # if at least 1 set of data, sort the data into U/D/L/R
                if len(fifo_data) >= 4:
                    for i in range(0, len(fifo_data), 4):
                        self.gesture_data_.u_data[self.gesture_data_.index] = fifo_data[i + 0]
                        self.gesture_data_.d_data[self.gesture_data_.index] = fifo_data[i + 1]
                        self.gesture_data_.l_data[self.gesture_data_.index] = fifo_data[i + 2]
                        self.gesture_data_.r_data[self.gesture_data_.index] = fifo_data[i + 3]
                        self.gesture_data_.index += 1
                        self.gesture_data_.total_gestures += 1

                    # filter and process gesture data, decode near/far state
                    if self.processGestureData():
                        if self.decodeGesture():
                            #***TODO: U-Turn Gestures
                            pass

                    # reset data
                    self.gesture_data_.index = 0
                    self.gesture_data_.total_gestures = 0

            # wait some time to collect next batch of FIFO data
            sleep(APDS9960_TIME_FIFO_PAUSE)

        # determine best guessed gesture and clean up
        sleep(APDS9960_TIME_FIFO_PAUSE)
        self.decodeGesture()
        motion = self.gesture_motion_

        self.resetGestureParameters()
        return motion


    # turn the APDS-9960 on
    def enablePower(self):
        self.setMode(APDS9960_MODE_POWER, True)


    def disablePower(self):
        # turn the APDS-9960 off
        self.setMode(APDS9960_MODE_POWER, False)



    # *******************************************************************************
    # ambient light and color sensor controls
    # *******************************************************************************

    # check if there is new light data available
    def isLightAvailable(self):
        val = self._read_byte_data(APDS9960_REG_STATUS)

        # shift and mask out AVALID bit
        val &= APDS9960_BIT_AVALID

        return (val == APDS9960_BIT_AVALID)

    # reads the ambient (clear) light level as a 16-bit value
    def readAmbientLight(self):
        # read value from clear channel, low byte register
        l = self._read_byte_data(APDS9960_REG_CDATAL)

        # read value from clear channel, high byte register
        h = self._read_byte_data(APDS9960_REG_CDATAH)

        return l + (h << 8)

    # reads the red light level as a 16-bit value
    def readRedLight(self):
        # read value from red channel, low byte register
        l = self._read_byte_data(APDS9960_REG_RDATAL)

        # read value from red channel, high byte register
        h = self._read_byte_data(APDS9960_REG_RDATAH)

        return l + (h << 8)

    # reads the green light level as a 16-bit value
    def readGreenLight(self):
        # read value from green channel, low byte register
        l = self._read_byte_data(APDS9960_REG_GDATAL)

        # read value from green channel, high byte register
        h = self._read_byte_data(APDS9960_REG_GDATAH)

        return l + (h << 8)

    # reads the blue light level as a 16-bit value
    def readBlueLight(self):
        # read value from blue channel, low byte register
        l = self._read_byte_data(APDS9960_REG_BDATAL)

        # read value from blue channel, high byte register
        h = self._read_byte_data(APDS9960_REG_BDATAH)

        return l + (h << 8)


    # *******************************************************************************
    # Proximity sensor controls
    # *******************************************************************************

    # reads the proximity level as an 8-bit value
    def readProximity(self):
        return self._read_byte_data(APDS9960_REG_PDATA)


    # *******************************************************************************
    # High-level gesture controls
    # *******************************************************************************

    # resets all the parameters in the gesture data member
    def resetGestureParameters(self):
        self.gesture_data_.index = 0
        self.gesture_data_.total_gestures = 0

        self.gesture_ud_delta_ = 0
        self.gesture_lr_delta_ = 0

        self.gesture_ud_count_ = 0
        self.gesture_lr_count_ = 0

        self.gesture_near_count_ = 0
        self.gesture_far_count_ = 0

        self.gesture_state_ = 0
        self.gesture_motion_ = APDS9960_DIR_NONE


    def processGestureData(self):
        """Processes the raw gesture data to determine swipe direction

            Returns:
                bool: True if near or far state seen, False otherwise.
        """
        u_first = 0
        d_first = 0
        l_first = 0
        r_first = 0
        u_last = 0
        d_last = 0
        l_last = 0
        r_last = 0

        # if we have less than 4 total gestures, that's not enough
        if self.gesture_data_.total_gestures <= 4:
            return False

        # check to make sure our data isn't out of bounds
        if self.gesture_data_.total_gestures <= 32 and self.gesture_data_.total_gestures > 0:
            # find the first value in U/D/L/R above the threshold
            for i in range(0, self.gesture_data_.total_gestures):
                if self.gesture_data_.u_data[i] > APDS9960_GESTURE_THRESHOLD_OUT and \
                    self.gesture_data_.d_data[i] > APDS9960_GESTURE_THRESHOLD_OUT and \
                    self.gesture_data_.l_data[i] > APDS9960_GESTURE_THRESHOLD_OUT and \
                    self.gesture_data_.r_data[i] > APDS9960_GESTURE_THRESHOLD_OUT:

                    u_first = self.gesture_data_.u_data[i]
                    d_first = self.gesture_data_.d_data[i]
                    l_first = self.gesture_data_.l_data[i]
                    r_first = self.gesture_data_.r_data[i]
                    break

            # if one of the _first values is 0, then there is no good data
            if u_first == 0 or  d_first == 0 or l_first == 0 or r_first == 0:
                return False

            # find the last value in U/D/L/R above the threshold
            for i in reversed(range(0, self.gesture_data_.total_gestures)):
                if self.gesture_data_.u_data[i] > APDS9960_GESTURE_THRESHOLD_OUT and \
                    self.gesture_data_.d_data[i] > APDS9960_GESTURE_THRESHOLD_OUT and \
                    self.gesture_data_.l_data[i] > APDS9960_GESTURE_THRESHOLD_OUT and \
                    self.gesture_data_.r_data[i] > APDS9960_GESTURE_THRESHOLD_OUT:

                    u_last = self.gesture_data_.u_data[i]
                    d_last = self.gesture_data_.d_data[i]
                    l_last = self.gesture_data_.l_data[i]
                    r_last = self.gesture_data_.r_data[i]
                    break

            # calculate the first vs. last ratio of up/down and left/right
            ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first)
            lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first)
            ud_ratio_last = ((u_last - d_last) * 100) / (u_last + d_last)
            lr_ratio_last = ((l_last - r_last) * 100) / (l_last + r_last)

            # determine the difference between the first and last ratios
            ud_delta = ud_ratio_last - ud_ratio_first
            lr_delta = lr_ratio_last - lr_ratio_first

            # accumulate the UD and LR delta values
            self.gesture_ud_delta_ += ud_delta
            self.gesture_lr_delta_ += lr_delta

            # determine U/D gesture
            if self.gesture_ud_delta_ >= APDS9960_GESTURE_SENSITIVITY_1:
                self.gesture_ud_count_ = 1
            elif self.gesture_ud_delta_ <= -APDS9960_GESTURE_SENSITIVITY_1:
                self.gesture_ud_count_ = -1
            else:
                self.gesture_ud_count_ = 0

            # determine L/R gesture
            if self.gesture_lr_delta_ >= APDS9960_GESTURE_SENSITIVITY_1:
                self.gesture_lr_count_ = 1
            elif self.gesture_lr_delta_ <= -APDS9960_GESTURE_SENSITIVITY_1:
                self.gesture_lr_count_ = -1
            else:
                self.gesture_lr_count_ = 0

            # determine Near/Far gesture
            if self.gesture_ud_count_ == 0 and self.gesture_lr_count_ == 0:
                if abs(ud_delta) < APDS9960_GESTURE_SENSITIVITY_2 and \
                    abs(lr_delta) < APDS9960_GESTURE_SENSITIVITY_2:

                    if ud_delta == 0 and lr_delta == 0:
                        self.gesture_near_count_ += 1
                    elif ud_delta != 0 or lr_delta != 0:
                        self.gesture_far_count_ += 1

                    if self.gesture_near_count_ >= 10 and self.gesture_far_count_ >= 2:
                        if ud_delta == 0 and lr_delta == 0:
                            self.gesture_state_ = APDS9960_STATE_NEAR
                        elif ud_delta != 0 and lr_delta != 0:
                            self.gesture_state_ = APDS9960_STATE_FAR
                        return True
            else:
                if abs(ud_delta) < APDS9960_GESTURE_SENSITIVITY_2 and \
                    abs(lr_delta) < APDS9960_GESTURE_SENSITIVITY_2:

                        if ud_delta == 0 and lr_delta == 0:
                            self.gesture_near_count_ += 1

                        if self.gesture_near_count_ >= 10:
                            self.gesture_ud_count_ = 0
                            self.gesture_lr_count_ = 0
                            self.gesture_ud_delta_ = 0
                            self.gesture_lr_delta_ = 0

        return False

    def decodeGesture(self):
        """Determines swipe direction or near/far state.
        """

        # return if near or far event is detected
        if self.gesture_state_ == APDS9960_STATE_NEAR:
            self.gesture_motion_ = APDS9960_DIR_NEAR
            return True

        if self.gesture_state_ == APDS9960_STATE_FAR:
            self.gesture_motion_ = APDS9960_DIR_FAR
            return True

        # determine swipe direction
        if self.gesture_ud_count_ == -1 and self.gesture_lr_count_ == 0:
            self.gesture_motion_ = APDS9960_DIR_UP
        elif self.gesture_ud_count_ == 1 and self.gesture_lr_count_ == 0:
            self.gesture_motion_ = APDS9960_DIR_DOWN
        elif self.gesture_ud_count_ == 0 and self.gesture_lr_count_ == 1:
            self.gesture_motion_ = APDS9960_DIR_RIGHT
        elif self.gesture_ud_count_ == 0 and self.gesture_lr_count_ == -1:
            self.gesture_motion_ = APDS9960_DIR_LEFT
        elif self.gesture_ud_count_ == -1 and self.gesture_lr_count_ == 1:
            if abs(self.gesture_ud_delta_) > abs(self.gesture_lr_delta_):
                self.gesture_motion_ = APDS9960_DIR_UP
            else:
                self.gesture_motion_ = APDS9960_DIR_DOWN
        elif self.gesture_ud_count_ == 1 and self.gesture_lr_count_ == -1:
            if abs(self.gesture_ud_delta_) > abs(self.gesture_lr_delta_):
                self.gesture_motion_ = APDS9960_DIR_DOWN
            else:
                self.gesture_motion_ = APDS9960_DIR_LEFT
        elif self.gesture_ud_count_ == -1 and self.gesture_lr_count_ == -1:
            if abs(self.gesture_ud_delta_) > abs(self.gesture_lr_delta_):
                self.gesture_motion_ = APDS9960_DIR_UP
            else:
                self.gesture_motion_ = APDS9960_DIR_LEFT
        elif self.gesture_ud_count_ == 1 and self.gesture_lr_count_ == 1:
            if abs(self.gesture_ud_delta_) > abs(self.gesture_lr_delta_):
                self.gesture_motion_ = APDS9960_DIR_DOWN
            else:
                self.gesture_motion_ = APDS9960_DIR_RIGHT
        else:
            return False

        return True


    # *******************************************************************************
    # Getters and setters for register values
    # *******************************************************************************

    def getProxIntLowThresh(self):
        """Returns the lower threshold for proximity detection
        """
        return self._read_byte_data(APDS9960_REG_PILT)

    def setProxIntLowThresh(self, threshold):
        """Sets the lower threshold for proximity detection.
        """
        self._write_byte_data(APDS9960_REG_PILT, threshold)


    def getProxIntHighThresh(self):
        """Returns the high threshold for proximity detection.
        """
        return self._read_byte_data(APDS9960_REG_PIHT)

    def setProxIntHighThresh(self, threshold):
        """Sets the high threshold for proximity detection.
        """
        self._write_byte_data(APDS9960_REG_PIHT, threshold)


    def getLEDDrive(self):
        """Returns LED drive strength for proximity and ALS.

            Value    LED Current
              0        100 mA
              1         50 mA
              2         25 mA
              3         12.5 mA

            Returns:
                int: the value of the LED drive strength
        """
        val = self._read_byte_data(APDS9960_REG_CONTROL)

        # shift and mask out LED drive bits
        return (val >> 6) & 0b00000011

    def setLEDDrive(self, drive):
        """Sets LED drive strength for proximity and ALS.

            Value    LED Current
              0        100 mA
              1         50 mA
              2         25 mA
              3         12.5 mA

            Args:
                drive (int): value for the LED drive strength
        """
        val = self._read_byte_data(APDS9960_REG_CONTROL)

        # set bits in register to given value
        drive &= 0b00000011
        drive = drive << 6
        val &= 0b00111111
        val |= drive

        self._write_byte_data(APDS9960_REG_CONTROL, val)


    def getProximityGain(self):
        """Returns receiver gain for proximity detection.

            Value    Gain
              0       1x
              1       2x
              2       4x
              3       8x

            Returns:
                int: the value of the proximity gain
        """
        val = self._read_byte_data(APDS9960_REG_CONTROL)

        # shift and mask out PDRIVE bits
        return (val >> 2) & 0b00000011

    def setProximityGain(self, drive):
        """Returns receiver gain for proximity detection.

            Value    Gain
              0       1x
              1       2x
              2       4x
              3       8x

            Args:
                drive (int): value for the proximity gain
        """
        val = self._read_byte_data(APDS9960_REG_CONTROL)

        # set bits in register to given value
        drive &= 0b00000011
        drive = drive << 2
        val &= 0b11110011
        val |= drive

        self._write_byte_data(APDS9960_REG_CONTROL, val)


    def getAmbientLightGain(self):
        """Returns receiver gain for the ambient light sensor (ALS).

            Value    Gain
              0       1x
              1       4x
              2       16x
              3       64x

            Returns:
                int: the value of the ALS gain
        """
        val = self._read_byte_data(APDS9960_REG_CONTROL)

        # shift and mask out ADRIVE bits
        return (val & 0b00000011)

    def setAmbientLightGain(self, drive):
        """Sets the receiver gain for the ambient light sensor (ALS).

            Value    Gain
              0       1x
              1       4x
              2       16x
              3       64x

            Args:
                drive (int): value for the ALS gain
        """
        val = self._read_byte_data(APDS9960_REG_CONTROL)

        # set bits in register to given value
        drive &= 0b00000011
        val &= 0b11111100
        val |= drive

        self._write_byte_data(APDS9960_REG_CONTROL, val)


    def getLEDBoost(self):
        """Get the current LED boost value.

            Value    Gain
              0        100%
              1        150%
              2        200%
              3        300%

            Returns:
                int: the LED boost value
        """
        val = self._read_byte_data(APDS9960_REG_CONFIG2)

        # shift and mask out LED_BOOST bits
        return (val >> 4) & 0b00000011

    def setLEDBoost(self, boost):
        """Sets the LED current boost value.

            Value    Gain
              0        100%
              1        150%
              2        200%
              3        300%

            Args:
                boost (int): value for the LED boost
        """
        val = self._read_byte_data(APDS9960_REG_CONFIG2)

        # set bits in register to given value
        boost &= 0b00000011
        boost = boost << 4
        val &= 0b11001111
        val |= boost

        self._write_byte_data(APDS9960_REG_CONFIG2, val)


    def getProxGainCompEnable(self):
        """Gets proximity gain compensation enable.

            Returns:
                bool: True if compensation is enabled, False if not
        """
        val = self._read_byte_data(APDS9960_REG_CONFIG3)

        # Shift and mask out PCMP bits
        val = (val >> 5) & 0b00000001
        return val == 1

    def setProxGainCompEnable(self, enable):
        """Sets the proximity gain compensation enable.

            Args:
                enable (bool): True to enable compensation, False to disable
        """
        val = self._read_byte_data(APDS9960_REG_CONFIG3)

        # set bits in register to given value
        val &= 0b11011111
        if enable:
            val |= 0b00100000

        self._write_byte_data(APDS9960_REG_CONFIG3, val)


    def getProxPhotoMask(self):
        """Gets the current mask for enabled/disabled proximity photodiodes.

            Bit    Photodiode
             3       UP
             2       DOWN
             1       LEFT
             0       RIGHT

            1 = disabled, 0 = enabled

            Returns:
                int: Current proximity mask for photodiodes.
        """
        val = self._read_byte_data(APDS9960_REG_CONFIG3)

        # mask out photodiode enable mask bits
        return val & 0b00001111

    def setProxPhotoMask(self, mask):
        """Sets the mask for enabling/disabling proximity photodiodes.

            Bit    Photodiode
             3       UP
             2       DOWN
             1       LEFT
             0       RIGHT

            1 = disabled, 0 = enabled

            Args:
                mask (int): 4-bit mask value
        """
        val = self._read_byte_data(APDS9960_REG_CONFIG3)

        # set bits in register to given value
        mask &= 0b00001111
        val &= 0b11110000
        val |= mask

        self._write_byte_data(APDS9960_REG_CONFIG3, val)


    def getGestureEnterThresh(self):
        """Gets the entry proximity threshold for gesture sensing.

            Returns:
                int: current entry proximity threshold
        """
        return self._read_byte_data(APDS9960_REG_GPENTH)

    def setGestureEnterThresh(self, threshold):
        """Sets the entry proximity threshold for gesture sensing.

            Args:
                threshold (int): threshold proximity value needed to start gesture mode
        """
        self._write_byte_data(APDS9960_REG_GPENTH, threshold)


    def getGestureExitThresh(self):
        """Gets the exit proximity threshold for gesture sensing.

            Returns:
                int: current exit proximity threshold
        """
        return self._read_byte_data(APDS9960_REG_GEXTH)

    def setGestureExitThresh(self, threshold):
        """Sets the exit proximity threshold for gesture sensing.

            Args:
                threshold (int): threshold proximity value needed to end gesture mode
        """
        self._write_byte_data(APDS9960_REG_GEXTH, threshold)


    def getGestureGain(self):
        """Gets the gain of the photodiode during gesture mode.

            Value    Gain
              0       1x
              1       2x
              2       4x
              3       8x

            Returns:
                int: the current photodiode gain
        """
        val = self._read_byte_data(APDS9960_REG_GCONF2)

        # shift and mask out PDRIVE bits
        return (val >> 5) & 0b00000011

    def setGestureGain(self, gain):
        """Sets the gain of the photodiode during gesture mode.

            Value    Gain
              0       1x
              1       2x
              2       4x
              3       8x

            Args:
                gain (int): the value for the photodiode gain
        """
        val = self._read_byte_data(APDS9960_REG_GCONF2)

        # set bits in register to given value
        gain &= 0b00000011
        gain = gain << 5
        val &= 0b10011111
        val |= gain

        self._write_byte_data(APDS9960_REG_GCONF2, val)


    def getGestureLEDDrive(self):
        """Gets the drive current of the LED during gesture mode.

            Value    LED Current
              0        100 mA
              1         50 mA
              2         25 mA
              3         12.5 mA

            Returns:
                int: the LED drive current value
        """
        val = self._read_byte_data(APDS9960_REG_GCONF2)

        # shift and mask out LED drive bits
        return (val >> 3) & 0b00000011

    def setGestureLEDDrive(self, drive):
        """Sets LED drive strength for proximity and ALS.

            Value    LED Current
              0        100 mA
              1         50 mA
              2         25 mA
              3         12.5 mA

            Args:
                drive (int): value for the LED drive current
        """
        val = self._read_byte_data(APDS9960_REG_GCONF2)

        # set bits in register to given value
        drive &= 0b00000011
        drive = drive << 3
        val &= 0b11100111
        val |= drive

        self._write_byte_data(APDS9960_REG_GCONF2, val)


    def getGestureWaitTime(self):
        """Gets the time in low power mode between gesture detections.

            Value    Wait time
              0          0 ms
              1          2.8 ms
              2          5.6 ms
              3          8.4 ms
              4         14.0 ms
              5         22.4 ms
              6         30.8 ms
              7         39.2 ms

            Returns:
                int: the current wait time between gestures
        """
        val = self._read_byte_data(APDS9960_REG_GCONF2)

        # shift and mask out LED drive bits
        return val & 0b00000111

    def setGestureWaitTime(self, time):
        """Sets the time in low power mode between gesture detections.

            Value    Wait time
              0          0 ms
              1          2.8 ms
              2          5.6 ms
              3          8.4 ms
              4         14.0 ms
              5         22.4 ms
              6         30.8 ms
              7         39.2 ms

            Args:
                time (int): value for the wait time
        """
        val = self._read_byte_data(APDS9960_REG_GCONF2)

        # set bits in register to given value
        time &= 0b00000111
        val &= 0b11111000
        val |= time

        self._write_byte_data(APDS9960_REG_GCONF2, val)


    def getLightIntLowThreshold(self):
        """Gets the low threshold for ambient light interrupts.

            Returns:
                int: threshold current low threshold stored on the APDS9960
        """
        return self._read_byte_data(APDS9960_REG_AILTL) | (self._read_byte_data(APDS9960_REG_AILTH) << 8)

    def setLightIntLowThreshold(self, threshold):
        """Sets the low threshold for ambient light interrupts.

            Args:
                threshold (int): low threshold value for interrupt to trigger
        """
        # break 16-bit threshold into 2 8-bit values
        self._write_byte_data(APDS9960_REG_AILTL, threshold & 0x00ff)
        self._write_byte_data(APDS9960_REG_AILTH, (threshold & 0xff00) >> 8)


    def getLightIntHighThreshold(self):
        """Gets the high threshold for ambient light interrupts.

            Returns:
                int: threshold current low threshold stored on the APDS9960
        """
        return self._read_byte_data(APDS9960_REG_AIHTL) | (self._read_byte_data(APDS9960_REG_AIHTH) << 8)

    def setLightIntHighThreshold(self, threshold):
        """Sets the high threshold for ambient light interrupts.

            Args:
                threshold (int): high threshold value for interrupt to trigger
        """
        # break 16-bit threshold into 2 8-bit values
        self._write_byte_data(APDS9960_REG_AIHTL, threshold & 0x00ff)
        self._write_byte_data(APDS9960_REG_AIHTH, (threshold & 0xff00) >> 8)


    def getProximityIntLowThreshold(self):
        """Gets the low threshold for proximity interrupts.

            Returns:
                int: threshold current low threshold stored on the APDS9960
        """
        return self._read_byte_data(APDS9960_REG_PILT)

    def setProximityIntLowThreshold(self, threshold):
        """Sets the low threshold for proximity interrupts.

            Args:
                threshold (int): low threshold value for interrupt to trigger
        """
        self._write_byte_data(APDS9960_REG_PILT, threshold)


    def getProximityIntHighThreshold(self):
        """Gets the high threshold for proximity interrupts.

            Returns:
                int: threshold current high threshold stored on the APDS9960
        """
        return self._read_byte_data(APDS9960_REG_PIHT)

    def setProximityIntHighThreshold(self, threshold):
        """Sets the high threshold for proximity interrupts.

            Args:
                threshold (int): high threshold value for interrupt to trigger
        """
        self._write_byte_data(APDS9960_REG_PIHT, threshold)


    def getAmbientLightIntEnable(self):
        """Gets if ambient light interrupts are enabled or not.

            Returns:
                bool: True if interrupts are enabled, False if not
        """
        val = self._read_byte_data(APDS9960_REG_ENABLE)
        return (val >> 4) & 0b00000001 == 1

    def setAmbientLightIntEnable(self, enable):
        """Turns ambient light interrupts on or off.

            Args:
                enable (bool): True to enable interrupts, False to turn them off
        """
        val = self._read_byte_data(APDS9960_REG_ENABLE)

        # set bits in register to given value
        val &= 0b11101111
        if enable:
            val |= 0b00010000

        self._write_byte_data(APDS9960_REG_ENABLE, val)


    def getProximityIntEnable(self):
        """Gets if proximity interrupts are enabled or not.

            Returns:
                bool: True if interrupts are enabled, False if not
        """
        val = self._read_byte_data(APDS9960_REG_ENABLE)
        return (val >> 5) & 0b00000001 == 1

    def setProximityIntEnable(self, enable):
        """Turns proximity interrupts on or off.

            Args:
                enable (bool): True to enable interrupts, False to turn them off
        """
        val = self._read_byte_data(APDS9960_REG_ENABLE)

        # set bits in register to given value
        val &= 0b11011111
        if enable:
            val |= 0b00100000

        self._write_byte_data(APDS9960_REG_ENABLE, val)


    def getGestureIntEnable(self):
        """Gets if gesture interrupts are enabled or not.

            Returns:
                bool: True if interrupts are enabled, False if not
        """
        val = self._read_byte_data(APDS9960_REG_GCONF4)
        return (val >> 1) & 0b00000001 == 1

    def setGestureIntEnable(self, enable):
        """Turns gesture-related interrupts on or off.

            Args:
                enable (bool): True to enable interrupts, False to turn them off
        """
        val = self._read_byte_data(APDS9960_REG_GCONF4)

        # set bits in register to given value
        val &= 0b11111101
        if enable:
            val |= 0b00000010

        self._write_byte_data(APDS9960_REG_GCONF4, val)


    def clearAmbientLightInt(self):
        """Clears the ambient light interrupt.
        """
        self._read_byte_data(APDS9960_REG_AICLEAR)


    def clearProximityInt(self):
        """Clears the proximity interrupt.
        """
        self._read_byte_data(APDS9960_REG_PICLEAR)


    def getGestureMode(self):
        """Tells if the gesture state machine is currently running.

            Returns:
                bool: True if gesture state machine is running, False if not
        """
        val = self._read_byte_data(APDS9960_REG_GCONF4)
        return val & 0b00000001 == 1

    def setGestureMode(self, enable):
        """Turns gesture-related interrupts on or off.

            Args:
                enable (bool): True to enter gesture state machine, False to turn them off
        """
        val = self._read_byte_data(APDS9960_REG_GCONF4)

        # set bits in register to given value
        val &= 0b11111110
        if enable:
            val |= 0b00000001

        self._write_byte_data(APDS9960_REG_GCONF4, val)


    # *******************************************************************************
    # Raw I2C Reads and Writes
    # *******************************************************************************

    def _read_byte_data(self, cmd):
        return self.bus.read_byte_data(self.address, cmd)

    def _write_byte_data(self, cmd, val):
        return self.bus.write_byte_data(self.address, cmd, val)


    def _read_i2c_block_data(self, cmd, num):
        return self.bus.read_i2c_block_data(self.address, cmd, num)



class uAPDS9960(APDS9960):
    """
    APDS9960 for MicroPython

    sensor = uAPDS9960(bus=I2C_instance,
                       address=APDS9960_I2C_ADDR, valid_id=APDS9960_DEV_ID)
    """
    def _read_byte_data(self, cmd):
        return self.bus.readfrom_mem(self.address, cmd, 1)[0]

    def _write_byte_data(self, cmd, val):
        self.bus.writeto_mem(self.address, cmd, bytes([val]))

    def _read_i2c_block_data(self, cmd, num):
        return self.bus.readfrom_mem(self.address, cmd, num)

exceptions.py

class ADPS9960InvalidDevId(ValueError):
    def __init__(self, id, valid_ids):
        Exception.__init__(self, "Device id 0x{} is not a valied one (valid: {})!".format(format(id, '02x'), ', '.join(["0x{}".format(format(i, '02x')) for i in valid_ids])))

class ADPS9960InvalidMode(ValueError):
    def __init__(self, mode):
        Exception.__init__(self, "Feature mode {} is invalid!".format(mode))

以下が、モジュールを動かすためのコードです。

gesture.py

from time import sleep

from machine import Pin, I2C

from apds9960.const import *
from apds9960 import uAPDS9960 as APDS9960

bus = I2C(sda=Pin(0), scl=Pin(1))

apds = APDS9960(bus)

dirs = {
    APDS9960_DIR_NONE: "none",
    APDS9960_DIR_LEFT: "left",
    APDS9960_DIR_RIGHT: "right",
    APDS9960_DIR_UP: "up",
    APDS9960_DIR_DOWN: "down",
    APDS9960_DIR_NEAR: "near",
    APDS9960_DIR_FAR: "far",
}

apds.setProximityIntLowThreshold(50)

print("Gesture Test")
print("============")
apds.enableGestureSensor()

while True:
    sleep(0.5)
    if apds.isGestureAvailable():
        motion = apds.readGesture()
        print("Gesture={}".format(dirs.get(motion, "unknown")))
from machine import Pin, I2C
from time import sleep
from apds9960.const import *
from apds9960 import uAPDS9960 as APDS9960

# I2C初期化
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
apds = APDS9960(i2c)
apds.enableLightSensor(interrupts=False)

while True:
    if apds.isLightAvailable():
        c = apds.readAmbientLight()
        r = apds.readRedLight()
        g = apds.readGreenLight()
        b = apds.readBlueLight()

        denom = max(c, 1)
        rn, gn, bn = r/denom, g/denom, b/denom
        print(f"C:{c} R:{r} G:{g} B:{b} norm=({rn:.2f},{gn:.2f},{bn:.2f})")
    sleep(0.1)

ホワイトバランス補正の工夫

カラーセンサーをそのまま使うと、環境光やセンサーの分光特性のせいで「青をかざしたのに赤っぽく出る」といったことが起きます。
そこでおすすめなのが ホワイトバランス補正。カメラで「白を白く見せる」調整と同じです。

1. 白い紙を基準にする

  1. センサーの前に真っ白な紙を置く
  2. R/G/B の値を読み取る(例: R=12000, G=13500, B=11000)
  3. このとき R=G=B になるのが理想なので、各値に係数を掛けて揃えます

例:

  • Gを基準とすると
    • R係数 = G/R = 13500/12000 ≈ 1.125
    • B係数 = G/B = 13500/11000 ≈ 1.227
  • 以後の計測では
    • R’ = R × 1.125
    • G’ = G
    • B’ = B × 1.227

こうすると「白い紙は白」となり、他の色も相対的に自然になります。
以下、プログラムに落とす場合の例です。

# 白紙で取得した補正係数
R_GAIN = 1.125
G_GAIN = 1.0
B_GAIN = 1.227

def read_color():
    c = apds.readAmbientLight()
    r = apds.readRedLight() * R_GAIN
    g = apds.readGreenLight() * G_GAIN
    b = apds.readBlueLight() * B_GAIN
    return c, r, g, b

活用アイデアと実用シーン

  • 手をかざして色を変える照明
  • 色付きカードで操作するガジェット
  • タッチレス入力のインターフェース
  • 「赤ボール」「青ボール」を判定するロボット

身近な材料と組み合わせるだけで、ちょっと未来的なおもちゃや便利グッズが作れます。

まとめ

APDS-9960 は ジェスチャー・近接・カラーを全部こなす多機能センサーです。
環境光に弱いというクセはあるものの、LED照明と遮光フードで工夫すればしっかり働いてくれます。

色を判別して別のモジュール(サーボモータなど)を動かすなど様々なことに使えると思います。
是非試してみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次