diff --git a/README.rst b/README.rst index d90fad3..6991996 100644 --- a/README.rst +++ b/README.rst @@ -14,10 +14,17 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_APDS9960/actions/ :alt: Build Status -The APDS9960 is a specialized chip that detects hand gestures, proximity -and ambient light color over I2C. Its available on -`Adafruit as a breakout `_. +The APDS-9960 is a specialized chip that detects hand gestures, proximity +and ambient light color over I2C. Its available from +`Adafruit as a breakout `_ and as a built-in sensor on +several Adafruit development boards. +* `Adafruit CLUE `_ +* `Adafruit Feather nRF52840 Sense `_ +* `Adafruit Proximity Trinkey `_ + +This driver provides easy access to proximity, gesture and color data from the APDS-9960 sensor +with a minimal footprint to allow it to work on all CircuitPython platforms. Installation and Dependencies ============================= @@ -26,13 +33,16 @@ This driver depends on: * `Adafruit CircuitPython `_ Please ensure all dependencies are available on the CircuitPython filesystem. + This is easily achieved by downloading `the Adafruit library and driver bundle `_. Installing from PyPI -------------------- -On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from PyPI `_. To install for current user: +On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from PyPI `_. + +To install for current user: .. code-block:: shell @@ -64,98 +74,165 @@ Usage Example i2c = board.I2C() int_pin = digitalio.DigitalInOut(board.D5) + int_pin.switch_to_input(pull=digitalio.Pull.UP) apds = APDS9960(i2c) - apds.enable_proximity = True - apds.proximity_interrupt_threshold = (0, 175) apds.enable_proximity_interrupt = True + apds.proximity_interrupt_threshold = (0, 175) + apds.enable_proximity = True while True: + if not int_pin.value: print(apds.proximity) apds.clear_interrupt() Hardware Set-up --------------- -Connect Vin to 3.3 V or 5 V power source, GND to ground, SCL and SDA to the appropriate pins. +If you're using a board with a built-in APDS-9960, no hardware setup will be required. + +If you're using a breakout board via the pin header, connect ``Vin`` to a 3.3 V or 5 V power source, +connect ``GND`` to ground, then connect ``SCL`` and ``SDA`` to the appropriate pins. + +Optionally, if you'd like to use the sensor's interrupt pin connect ``INT`` to any available +digital I/O pin. Basics ------ -Of course, you must import i2c bus device, board pins, and the library: +To get started, import ``board`` and, and this library: .. code:: python3 - import board - from adafruit_apds9960.apds9960 import APDS9960 - import digitalio + import board + from adafruit_apds9960.apds9960 import APDS9960 -To set-up the device to gather data, initialize the I2CDevice using SCL -and SDA pins. Then initialize the library. Optionally provide an interrupt -pin for proximity detection. +To set up the sensor to gather data, initialize the I2C bus via ``board.I2C()`` +then initialize the APDS-9960 library. .. code:: python3 - int_pin = digitalio.DigitalInOut(board.A1) - i2c = board.I2C() - apds = APDS9960(i2c) + i2c = board.I2C() + apds = APDS9960(i2c) + +Proximity +--------- + +To get a proximity result, enable the proximity engine then read the `proximity` value. + +This will return a value between 0 and 255, with higher values indicating that something is close +to the sensor. + +.. code:: python3 + + apds.enable_proximity = True + + while True: + print(apds.proximity) Gestures -------- -To get a gesture, see if a gesture is available first, then get the gesture Code +First, enable both the proximity and gesture engines. The gesture engine relies on the proximity +engine to determine when to start itself up and, as a result, proximity readings won't be reliable +while the gesture engine is enabled. + +To get a gesture, use the `gesture()` function to see if a gesture has been detected. If a value +greater than 0 is returned, a gesture has been detected. .. code:: python3 - gesture = apds.gesture() - if gesture == 1: - print("up") - if gesture == 2: - print("down") - if gesture == 3: - print("left") - if gesture == 4: - print("right") + # Uncomment and set the rotation if depending on how your sensor is mounted. + # apds.rotation = 270 # 270 for CLUE -Color Measurement ------------------ + apds.enable_proximity = True + apds.enable_gesture = True -To get a color measure, enable color measures, wait for color data, -then get the color data. + while True: + gesture = apds.gesture() + if gesture == 1: + print("up") + if gesture == 2: + print("down") + if gesture == 3: + print("left") + if gesture == 4: + print("right") + +Color/Light Measurement +----------------------- + +To get a color measurement, first enable the color/light engine, wait for color data to arrive, +then read the `color_data` values. .. code:: python3 - apds.enable_color = True + apds.enable_color = True + + while True: + while not apds.color_data_ready: + time.sleep(0.005) + + r, g, b, c = apds.color_data + print("r: {}, g: {}, b: {}, c: {}".format(r, g, b, c)) - while not apds.color_data_ready: - time.sleep(0.005) +Interrupt Pin +------------- - r, g, b, c = apds.color_data - print("r: {}, g: {}, b: {}, c: {}".format(r, g, b, c)) +This sensor has an interrupt pin can be asserted (pulled low) if proximity is detected outside of a +specified window of values. + +For boards with a built-in APDS-9960 this interupt pin will already be defined. For example, on the +Clue and Feather nRF52840 Sense boards this pin is mapped to ``board.PROXIMITY_LIGHT_INTERRUPT`` +and on the Proximity Trinkey it is mapped to ``board.INTERRUPT``. + +.. code:: python3 + + int_pin = digitalio.DigitalInOut(board.D5) + int_pin.switch_to_input(pull=digitalio.Pull.UP) Proximity Detection ---------------------- +------------------- + +With the interrupt pin set up we can define a threshold and enable the assertion of the sensor's +interrupt pin by the proximity engine before enabling the proximity engine itself. -To check for a object in proximity, see if a gesture is available first, then get the gesture Code +In this configuration, the sensor's interrupt pin will be asserted when an object is close to the +sensor. After checking on the interrupt it can be cleared using `clear_interrupt()` .. code:: python3 - apds.enable_proximity = True + apds.enable_proximity = True + + # set the interrupt threshold to fire when proximity reading goes above 175 + apds.proximity_interrupt_threshold = (0, 175) - # set the interrupt threshold to fire when proximity reading goes above 175 - apds.proximity_interrupt_threshold = (0, 175) + # assert interrupt pin on internal proximity interrupt + apds.enable_proximity_interrupt = True - # enable the proximity interrupt - apds.enable_proximity_interrupt = True + # enable the sensor's proximity engine + apds.enable_proximity = True - while True: - if not interrupt_pin.value: - print(apds.proximity) + while True: + if not interrupt_pin.value: + print(apds.proximity) + + # clear the interrupt + apds.clear_interrupt() + +Initiaization Options +---------------------- - # clear the interrupt - apds.clear_interrupt() +By default, when the driver is initialized, the APDS-9960 sensor's internal settings are reset and +sensible defaults are applied to several low-level settings that should work well for most use cases. + +If either the "reset" or "set defaults" behaviors (or both) aren't desired, they can be individually +disabled via init kwargs. + +.. code:: python3 + apds = APDS9960(i2c, reset=False, set_defaults=False) Documentation ============= diff --git a/adafruit_apds9960/apds9960.py b/adafruit_apds9960/apds9960.py index edea299..7670bd9 100644 --- a/adafruit_apds9960/apds9960.py +++ b/adafruit_apds9960/apds9960.py @@ -9,7 +9,7 @@ Driver class for the APDS9960 board. Supports gesture, proximity, and color detection. -* Author(s): Michael McWethy +* Author(s): Michael McWethy, Erik Hess Implementation Notes -------------------- @@ -19,13 +19,13 @@ * Adafruit `APDS9960 Proximity, Light, RGB, and Gesture Sensor `_ (Product ID: 3595) -* Adafruit `Adafruit CLUE +* Adafruit `CLUE `_ (Product ID: 4500) -* Adafruit `Adafruit Feather nRF52840 Sense +* Adafruit `Feather nRF52840 Sense `_ (Product ID: 4516) -* Adafruit `Adafruit Proximity Trinkey +* Adafruit `Proximity Trinkey `_ (Product ID: 5022) **Software and Dependencies:** @@ -36,8 +36,6 @@ * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice """ import time -from adafruit_register.i2c_bits import RWBits -from adafruit_register.i2c_bit import RWBit from adafruit_bus_device.i2c_device import I2CDevice from micropython import const @@ -51,72 +49,86 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_APDS9960.git" -# ADDRESS_DEF = const(0x39) -# INTEGRATION_TIME_DEF = const(0x01) -# GAIN_DEF = const(0x01) +# Only one address is possible for the APDS9960, no alternates are available +_APDS9960_I2C_ADDRESS = const(0x39) +_DEVICE_ID = const(0xAB) # APDS9960_RAM = const(0x00) -APDS9960_ENABLE = const(0x80) -APDS9960_ATIME = const(0x81) -# APDS9960_WTIME = const(0x83) -# APDS9960_AILTIL = const(0x84) -# APDS9960_AILTH = const(0x85) -# APDS9960_AIHTL = const(0x86) -# APDS9960_AIHTH = const(0x87) -APDS9960_PILT = const(0x89) -APDS9960_PIHT = const(0x8B) -APDS9960_PERS = const(0x8C) -# APDS9960_CONFIG1 = const(0x8D) -# APDS9960_PPULSE = const(0x8E) -APDS9960_CONTROL = const(0x8F) -# APDS9960_CONFIG2 = const(0x90) -APDS9960_ID = const(0x92) -APDS9960_STATUS = const(0x93) -APDS9960_CDATAL = const(0x94) -# APDS9960_CDATAH = const(0x95) -# APDS9960_RDATAL = const(0x96) -# APDS9960_RDATAH = const(0x97) -# APDS9960_GDATAL = const(0x98) -# APDS9960_GDATAH = const(0x99) -# APDS9960_BDATAL = const(0x9A) -# APDS9960_BDATAH = const(0x9B) -APDS9960_PDATA = const(0x9C) -# APDS9960_POFFSET_UR = const(0x9D) -# APDS9960_POFFSET_DL = const(0x9E) -# APDS9960_CONFIG3 = const(0x9F) -APDS9960_GPENTH = const(0xA0) -# APDS9960_GEXTH = const(0xA1) -APDS9960_GCONF1 = const(0xA2) -APDS9960_GCONF2 = const(0xA3) -# APDS9960_GOFFSET_U = const(0xA4) -# APDS9960_GOFFSET_D = const(0xA5) -# APDS9960_GOFFSET_L = const(0xA7) -# APDS9960_GOFFSET_R = const(0xA9) -APDS9960_GPULSE = const(0xA6) -APDS9960_GCONF3 = const(0xAA) -APDS9960_GCONF4 = const(0xAB) -APDS9960_GFLVL = const(0xAE) -APDS9960_GSTATUS = const(0xAF) -# APDS9960_IFORCE = const(0xE4) -# APDS9960_PICLEAR = const(0xE5) -# APDS9960_CICLEAR = const(0xE6) -APDS9960_AICLEAR = const(0xE7) -APDS9960_GFIFO_U = const(0xFC) +_APDS9960_ENABLE = const(0x80) +_APDS9960_ATIME = const(0x81) +# _APDS9960_WTIME = const(0x83) +# _APDS9960_AILTIL = const(0x84) +# _APDS9960_AILTH = const(0x85) +# _APDS9960_AIHTL = const(0x86) +# _APDS9960_AIHTH = const(0x87) +_APDS9960_PILT = const(0x89) +_APDS9960_PIHT = const(0x8B) +_APDS9960_PERS = const(0x8C) +# _APDS9960_CONFIG1 = const(0x8D) +# _APDS9960_PPULSE = const(0x8E) +_APDS9960_CONTROL = const(0x8F) +# _APDS9960_CONFIG2 = const(0x90) +_APDS9960_ID = const(0x92) +_APDS9960_STATUS = const(0x93) +_APDS9960_CDATAL = const(0x94) +# _APDS9960_CDATAH = const(0x95) +# _APDS9960_RDATAL = const(0x96) +# _APDS9960_RDATAH = const(0x97) +# _APDS9960_GDATAL = const(0x98) +# _APDS9960_GDATAH = const(0x99) +# _APDS9960_BDATAL = const(0x9A) +# _APDS9960_BDATAH = const(0x9B) +_APDS9960_PDATA = const(0x9C) +# _APDS9960_POFFSET_UR = const(0x9D) +# _APDS9960_POFFSET_DL = const(0x9E) +# _APDS9960_CONFIG3 = const(0x9F) +_APDS9960_GPENTH = const(0xA0) +_APDS9960_GEXTH = const(0xA1) +_APDS9960_GCONF1 = const(0xA2) +_APDS9960_GCONF2 = const(0xA3) +# _APDS9960_GOFFSET_U = const(0xA4) +# _APDS9960_GOFFSET_D = const(0xA5) +# _APDS9960_GOFFSET_L = const(0xA7) +# _APDS9960_GOFFSET_R = const(0xA9) +_APDS9960_GPULSE = const(0xA6) +# _APDS9960_GCONF3 = const(0xAA) +_APDS9960_GCONF4 = const(0xAB) +_APDS9960_GFLVL = const(0xAE) +_APDS9960_GSTATUS = const(0xAF) +# _APDS9960_IFORCE = const(0xE4) +# _APDS9960_PICLEAR = const(0xE5) +# _APDS9960_CICLEAR = const(0xE6) +_APDS9960_AICLEAR = const(0xE7) +_APDS9960_GFIFO_U = const(0xFC) # APDS9960_GFIFO_D = const(0xFD) # APDS9960_GFIFO_L = const(0xFE) # APDS9960_GFIFO_R = const(0xFF) +_BIT_MASK_ENABLE_EN = const(0x01) +_BIT_MASK_ENABLE_COLOR = const(0x02) +_BIT_MASK_ENABLE_PROX = const(0x04) +_BIT_MASK_ENABLE_PROX_INT = const(0x20) +_BIT_MASK_ENABLE_GESTURE = const(0x40) +_BIT_MASK_STATUS_AVALID = const(0x01) +_BIT_MASK_STATUS_GINT = const(0x04) +_BIT_MASK_GSTATUS_GFOV = const(0x02) +_BIT_MASK_GCONF4_GFIFO_CLR = const(0x04) + +_BIT_POS_PERS_PPERS = const(4) +_BIT_MASK_PERS_PPERS = const(0xF0) + +_BIT_POS_CONTROL_AGAIN = const(0) +_BIT_MASK_CONTROL_AGAIN = const(3) # pylint: disable-msg=too-many-instance-attributes class APDS9960: """ - APDS9900 provide basic driver services for the ASDS9960 breakout board + Provide basic driver services for the APDS9960 breakout board - :param ~busio.I2C i2c: The I2C bus the ASDS9960 is connected to - :param int address: The I2C device address. Defaults to :const:`0x39` - :param int integration_time: integration time. Defaults to :const:`0x01` - :param int gain: Device gain. Defaults to :const:`0x01` - :param int rotation: rotation of the device. Defaults to :const:`0` + :param ~busio.I2C i2c: The I2C bus the APDS9960 is connected to + :param int rotation: Rotation of the device. Defaults to :const:`0` + :param bool reset: If true, reset device on init. Defaults to :const:`True` + :param bool set_defaults: If true, set sensible defaults on init. Defaults to :const:`True` **Quickstart: Importing and using the APDS9960** @@ -136,89 +148,271 @@ class APDS9960: i2c = board.I2C() # uses board.SCL and board.SDA apds = APDS9960(i2c) - Now you have access to the :attr:`sensor.proximity` attribute + Now you have access to the :attr:`apds.proximity_enable` and :attr:`apds.proximity` + attributes .. code-block:: python + apds.proximity_enable = True proximity = apds.proximity + .. note:: There is no ``address`` argument because the APDS-9960 only has one address and + doesn't offer any option to configure alternative addresses. """ - _gesture_enable = RWBit(APDS9960_ENABLE, 6) - _gesture_valid = RWBit(APDS9960_GSTATUS, 0) - _gesture_mode = RWBit(APDS9960_GCONF4, 0) - _proximity_persistance = RWBits(4, APDS9960_PERS, 4) - def __init__( self, i2c: I2C, *, - address: int = 0x39, - integration_time: int = 0x01, - gain: int = 0x01, - rotation: int = 0 + rotation: int = 0, + reset: bool = True, + set_defaults: bool = True ): - self.buf129 = None - self.buf2 = bytearray(2) + self.rotation = rotation - self.i2c_device = I2CDevice(i2c, address) + self.buf129 = None # Gesture FIFO buffer, only instantiated if needed + self.buf4 = None # Gesture data processing buffer, only instantiated if needed + self.buf2 = bytearray(2) # I2C communication buffer - if self._read8(APDS9960_ID) != 0xAB: + self.i2c_device = I2CDevice(i2c, _APDS9960_I2C_ADDRESS) + + if self._read8(_APDS9960_ID) != _DEVICE_ID: raise RuntimeError() - self.enable_gesture = False - self.enable_proximity = False - self.enable_color = False - self._rotation = rotation - self.enable_proximity_interrupt = False - self.clear_interrupt() - - self.enable = False - time.sleep(0.010) - self.enable = True - time.sleep(0.010) - - self.color_gain = gain - self.integration_time = integration_time - self.gesture_dimensions = 0x00 # all - self.gesture_fifo_threshold = 0x01 # fifo 4 - self.gesture_gain = 0x02 # gain 4 - self.gesture_proximity_threshold = 50 - self._reset_counts() - - # gesture pulse length=0x2 pulse count=0x3 - self._write8(APDS9960_GPULSE, (0x2 << 6) | 0x3) + if reset: + # Disable prox, gesture, and color engines + self.enable_proximity = False + self.enable_gesture = False + self.enable_color = False + + # Reset basic config registers to power-on defaults + self.proximity_interrupt_threshold = (0, 0, 0) + self._write8(_APDS9960_GPENTH, 0) + self._write8(_APDS9960_GEXTH, 0) + self._write8(_APDS9960_GCONF1, 0) + self._write8(_APDS9960_GCONF2, 0) + self._write8(_APDS9960_GCONF4, 0) + self._write8(_APDS9960_GPULSE, 0) + self._write8(_APDS9960_ATIME, 255) + self._write8(_APDS9960_CONTROL, 1) + + # Clear all non-gesture interrupts + self.clear_interrupt() + + # Clear gesture FIFOs and interrupt + self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GFIFO_CLR, True) + + # Disable sensor and all functions/interrupts + self._write8(_APDS9960_ENABLE, 0) + time.sleep(0.025) # Sleeping could take at ~2-25 ms if engines were looping + + # Re-enable sensor and wait 10ms for the power on delay to finish + self.enable = True + time.sleep(0.010) + + if set_defaults: + # Trigger proximity interrupt at >= 5, PPERS: 4 cycles + self.proximity_interrupt_threshold = (0, 5, 4) + # Enter gesture engine at >= 5 proximity counts + self._write8(_APDS9960_GPENTH, 0x05) + # Exit gesture engine if all counts drop below 30 + self._write8(_APDS9960_GEXTH, 0x1E) + # GEXPERS: 2 (4 cycles), GEXMSK: 0 (default) GFIFOTH: 2 (8 datasets) + self._write8(_APDS9960_GCONF1, 0x82) + # GGAIN: 2 (4x), GLDRIVE: 100 mA (default), GGAIN: 1 (2x) + self._write8(_APDS9960_GCONF2, 0x41) + # GPULSE: 5 (6 pulses), GPLEN: 2 (16 us) + self._write8(_APDS9960_GPULSE, 0x85) + # ATIME: 256 (712ms color integration time, max count of 65535) + self.color_integration_time = 256 + # AGAIN: 1 (4x color gain) + self.color_gain = 1 ## BOARD - def _reset_counts(self) -> None: - """Gesture detection internal counts""" - self._saw_down_start = 0 - self._saw_up_start = 0 - self._saw_left_start = 0 - self._saw_right_start = 0 - - enable = RWBit(APDS9960_ENABLE, 0) - """Board enable. True to enable, False to disable""" - enable_color = RWBit(APDS9960_ENABLE, 1) - """Color detection enable flag. - True when color detection is enabled, else False""" - enable_proximity = RWBit(APDS9960_ENABLE, 2) - """Enable of proximity mode""" - gesture_fifo_threshold = RWBits(2, APDS9960_GCONF1, 6) - """Gesture fifo threshold value: range 0-3""" - gesture_gain = RWBits(2, APDS9960_GCONF2, 5) - """Gesture gain value: range 0-3""" - color_gain = RWBits(2, APDS9960_CONTROL, 0) - """Color gain value""" - enable_proximity_interrupt = RWBit(APDS9960_ENABLE, 5) - """Proximity interrupt enable flag. True if enabled, - False to disable""" - - ## GESTURE ROTATION + @property + def enable(self) -> bool: + """If ``True``, the sensor is enabled. + + If set to ``False``, the sensor will enter a low-power sleep state + + When enabled, the sensor's state machine will run through the following steps in sequence, + repeating from the top after all states are run through. + + #. **Idle State** + + - Will only remain in this state if all three sense engines are disabled. + + #. **Proximity Engine** *(if enabled)* + + - Will only run if `enable_proximity` is ``True``. + - Will run once, storing fresh data in the sensor's proximity data registers. If + proximity data is is lower than or exceeds the configured proximity thresholds an + internal persistence is incremented on each run as well. + + #. **Gesture Engine** *(if enabled)* + + - Will only run if `enable_gesture` is ``True`` and if entry threshold of `proximity` + is greater or equal to the gesture proximity entry threshold of 5 counts. + - Will continuously loop, storing new results in the sensor's gesture FIFO buffers, + until one of four conditions occur. + + - Exit threshold is met. *(all gesture measurements <= 30 counts)* + - The gesture engine or sensor are disabled. *(`enable_gesture` or `enable` + properties are set to ``False``)* + - The sensor is re-initalized by the driver + - The sensor is power cycled + + #. **Wait Timer** *(set to 0 by default)* + + - This driver does not set or make available the ``WAIT`` or ``WLONG`` registers that + control this function and, on intialization, leaves the timer at its power-on default + state of ``0``, effectively disabling this timer. + + #. **Color/Light Engine** *(if enabled)* + + - Will run start if `enable_color` is ``True``. + - Will run once, storing fresh data in the sensor's color data registers on each run. + + .. note:: Waking the sensor from its sleep state takes at least 7 ms. Disabling the sensor + and entering a sleep state can take as little as 2.78 ms, more typically about 25 ms, or + potentially quite a bit longer depending on what engines were enabled and what state was + active at the time the disable command was received. + + .. hint:: When in a sleep state the sensor's power usage drops to as little as 1-10 uA, + compared as much as 790 uA of power usage when enabled with proximity and/or gesture + engines running. While in a sleep state, the sensor will still listen for and respond + to I2C communication which can lead to minor increases in power usage. + """ + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN) + + @enable.setter + def enable(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_EN, value) + + ## Proximity Properties + @property + def enable_proximity(self) -> bool: + """If ``True``, the sensor's proximity engine is enabled.""" + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX) + + @enable_proximity.setter + def enable_proximity(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX, value) + + @property + def enable_proximity_interrupt(self) -> bool: + """If ``True``, the internal proximity interrupt asserts the sensor's interrupt pin. + + Internal proximity interrupt triggering is configured via `proximity_interrupt_threshold`. + + .. tip:: Using this interrupt will require attaching the sensor's ``INT`` pin to an + available digital I/O with an internal or external pull-up resistor. + + For boards with built-in sensors the pin is likely already mapped within ``board``. + + .. csv-table:: + :header: "Board", "Pin Mapping" + + "CLUE", "``board.PROXIMITY_LIGHT_INTERRUPT``" + "Feather nRF52840 Sense", "``board.PROXIMITY_LIGHT_INTERRUPT``" + "Proximity Trinkey", "``board.INTERRUPT``" + """ + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT) + + @enable_proximity_interrupt.setter + def enable_proximity_interrupt(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_PROX_INT, value) + + @property + def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: + """Tuple representing proximity engine low/high threshold and persistence, which determine + when the sensor's proximity interrupt is asserted. + + 1. Low Threshold (``PILT``) + 2. High Threshold (``PIHT``) *(optional)* + 3. Proximity Persistence (``PERS``) *(optional)* + + The first two items are the "low threshold" and "high threshold" values. These can be set + to any number between ``0`` and ``255``. If the proximity value is lower than the low + threshold or higher than the high threshold for enough cycles, an interrupt will be + asserted. + + The third item is the "persistence" value. This can be set to any value between ``0`` to + ``15``. This represents the number of 2.78 ms out-of-threshold cycles to wait for before + asserting the interrupt. This is basically a filter to prevent premature/false interrupts. + + .. hint:: For example, setting a low threshold of ``0`` and a high threshold of ``5`` will + cause the interrupt to be asserted very early when an object **enters the sensor's line + of sight**. Coversely, a low threshold of ``5`` and a high threshold of ``255`` will + trigger an interrupt only if an object **is removed from the sensor's line of sight**. + + .. hint:: Tuning the persistence value can be useful in some use cases but for most + situations the driver's default value of 4 should provide for stable results without + much delay in interrupt triggering. + """ + return ( + self._read8(_APDS9960_PILT), + self._read8(_APDS9960_PIHT), + self._get_bits(_APDS9960_PERS, _BIT_POS_PERS_PPERS, _BIT_MASK_PERS_PPERS), + ) + + @proximity_interrupt_threshold.setter + def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: + if setting_tuple and 0 <= setting_tuple[0] <= 255: + self._write8(_APDS9960_PILT, setting_tuple[0]) + if len(setting_tuple) > 1 and 0 <= setting_tuple[0] <= 255: + self._write8(_APDS9960_PIHT, setting_tuple[1]) + persist = 4 # default 4 + if len(setting_tuple) > 2 and 0 <= setting_tuple[0] <= 15: + persist = min(setting_tuple[2], 15) + self._set_bits( + _APDS9960_PERS, _BIT_POS_PERS_PPERS, _BIT_MASK_PERS_PPERS, persist + ) + + def clear_interrupt(self) -> None: + """Clears all non-gesture interrupts. + + This includes all of the following internal interrupts: + + * **Proximity Interrupt** (``PINT``) + * **Proximity Saturation Interrupt** (``STATUS``) + * **Color/Light Interrupt** (``STATUS``) + * **Color/Light Clear Saturation Interrupt** (``STATUS``) + """ + self._writecmdonly(_APDS9960_AICLEAR) + + ## Gesture Properties + @property + def enable_gesture(self) -> bool: + """If ``True``, the sensor's gesture engine is enabled. + + .. note:: The gesture engine will only operate if `enable_proximity` is also set to ``True`` + """ + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE) + + @enable_gesture.setter + def enable_gesture(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_GESTURE, value) + @property def rotation(self) -> int: - """Gesture rotation offset. Acceptable values are 0, 90, 180, 270.""" + """Clock-wise offset to apply to gesture results. + + Acceptable values are ``0``, ``90``, ``180``, ``270``. + + .. tip:: The sensor's "top" end is the one with the larger of the two circular windows. + + Some rotation examples for various boards with the APS-9960 built in: + + .. csv-table:: + :header: "Board", "Rotation" + + "CLUE", 270 + "Feather nRF52840 Sense, with USB port to the left", 270 + "Proximity Trinkey, plugged into right-side laptop USB port", 270 + "Proximity Trinkey, plugged into left-side laptop USB port", 90 + """ return self._rotation @rotation.setter @@ -228,49 +422,201 @@ def rotation(self, new_rotation: int) -> None: else: raise ValueError("Rotation value must be one of: 0, 90, 180, 270") - ## GESTURE DETECTION + ## Color/Light Properties @property - def enable_gesture(self) -> bool: - """Gesture detection enable flag. True to enable, False to disable. - Note that when disabled, gesture mode is turned off""" - return self._gesture_enable + def enable_color(self) -> bool: + """If ``True``, the sensor's color/light engine is enabled""" + return self._get_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR) - @enable_gesture.setter - def enable_gesture(self, enable_flag: bool) -> None: - if not enable_flag: - self._gesture_mode = False - self._gesture_enable = enable_flag - - def rotated_gesture(self, original_gesture: int) -> int: - """Applies rotation offset to the given gesture direction and returns the result""" - directions = [1, 4, 2, 3] - new_index = (directions.index(original_gesture) + self._rotation // 90) % 4 - return directions[new_index] - - def gesture(self) -> int: # pylint: disable-msg=too-many-branches - """Returns gesture code if detected. =0 if no gesture detected - =1 if an UP, =2 if a DOWN, =3 if an LEFT, =4 if a RIGHT + @enable_color.setter + def enable_color(self, value: bool) -> None: + self._set_bit(_APDS9960_ENABLE, _BIT_MASK_ENABLE_COLOR, value) + + @property + def color_data_ready(self) -> int: + """Color data ready flag. + + Returns ``0`` if no new data is ready, ``1`` if new data is ready. + + This flag is reset when `color_data` is read.""" + return self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_AVALID) + + @property + def color_gain(self) -> int: + """Color/light sensor gain value. + + This sets the gain multiplier for the ADC during color/light engine operations. + + .. csv-table:: + :header: "``color_gain``", "Gain Multiplier", "Note" + + 0, "1x", "Power-on Default" + 1, "4x", "Driver Default" + 2, "16x", "" + 3, "64x", "" + + .. tip:: To get useful, predictable `color_data` results it is important to tune this, + along with `color_integration_time`, to accommodate different lighting conditions, sensor + placements, material transparencies, expected object reflectivity, and environmental + conditions. + + For instance, measuring color of objects close to the sensor with bright, nearby + illumination (such as the white LEDs on the `Adafruit CLUE + `_) may work well with a `color_gain` of ``0`` + and a `color_integration_time` of ``72`` or lower. + + However, measuring the intensity and color temperature of ambient light through + difusion glass or plastic is likely to require experimenting with a wide range of + integration time and gain settings before useful data can be obtained.""" + return self._get_bits( + _APDS9960_CONTROL, _BIT_POS_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN + ) + + @color_gain.setter + def color_gain(self, value: int) -> None: + self._set_bits( + _APDS9960_CONTROL, _BIT_POS_CONTROL_AGAIN, _BIT_MASK_CONTROL_AGAIN, value + ) + + @property + def color_integration_time(self) -> int: + """Color/light sensor gain. + + Represents the integration time in number of 2.78 ms cycles for the ADC during color/light + engine operations. This also effectively sets the maxmium value returned for each channel + by `color_data`. + + .. csv-table:: + :header: "``color_integration_time``", "Time", "Max Count", "Note" + + 1, "2.78 ms", 1025, "Power-on Default" + 10, "27.8 ms", 10241, "" + 37, "103 ms", 37889, "" + 72, "200 ms", 65535, "" + 256, "712 ms", 65535, "Driver Default" """ - # buffer to read of contents of device FIFO buffer - if not self.buf129: - self.buf129 = bytearray(129) + return 256 - self._read8(_APDS9960_ATIME) - buffer = self.buf129 - buffer[0] = APDS9960_GFIFO_U - if not self._gesture_valid: - return 0 + @color_integration_time.setter + def color_integration_time(self, value: int) -> None: + self._write8(_APDS9960_ATIME, 256 - value) + + ## PROXIMITY + @property + def proximity(self) -> int: + """Proximity sensor data. + + The proximity engine returns a number between ``0`` and ``255`` which represents the + intensity of the reflected IR light detected from the sensor's internal LEDs, which pulse + continously during proximity engine operation. - time_mark = 0.0 - gesture_received = 0 - while True: + A value of ``0`` indicates no reflected IR light was received. This typically indicates + that no object(s) were in the sensor's line of sight and within detectable range of its IR + LED pulses. - up_down_diff = 0 - left_right_diff = 0 - gesture_received = 0 - time.sleep(0.030) # 30 ms + A value of ``255`` indicates that the maximum detectable amount of reflected IR light was + received. This typically indicates that an object was detected very close to the sensor. - n_recs = self._read8(APDS9960_GFLVL) - if n_recs: + .. caution:: Will always return ``0`` if `enable_proximity` is not set ``True``. + + .. note:: The sensor itself offers a very wide variety of configuration options for tuning + the proximity engine, such as the LEDs (pulse count/length, drive power) and the + photosensors (gain, offsets, masking). However, this driver does not make those readily + available in order to keep file size and memory footprint to a minimum, which is + critical for its use on more constrained platforms. + """ + return self._read8(_APDS9960_PDATA) + + ## GESTURE DETECTION + # pylint: disable-msg=too-many-branches,too-many-locals,too-many-statements + def gesture(self) -> int: + """Gesture sensor data. + + This checks the sensor for new gesture engine results and, if they are present, retrieves + and processes the results to determine what, if any, gesture can be deduced from the sensor + data. + + Returns a gesture code indicating the direction of the gesture. Before returning the code, + the `rotation` value is used to "rotate" the result as intended. + + .. csv-table:: + :header: "Code", "Direction" + + 0, "No gesture detected" + 1, "Up" + 2, "Down" + 3, "Left" + 4, "Right" + + .. caution:: Will always return ``0`` if `enable_proximity` and `enable_gesture` are not set + to ``True``. + + The data returned by the sensor is a continuous stream of four proximtiy measurements + constrained to up/down/left/right dimensions by using four directionally-aligned sensors. + The sensor itself doesn't include any logic to determine the gesture, leaving that work to + the implementer. + + This driver implements an algorithm that reliably detects simple gestures in most scenarios + while remaining small and efficient enough to work within the resource constraints of as + many CircuitPython boards/platforms as possible. + + .. tip:: Detecting gestures with this driver's algorithm requires actively, continously + polling for a gesture, with as little time as possible between `gesture()` calls. Even + with continous polling, however, its possible that gestures may go undetected by + `gesture()` calls if they occurred between or on the edges of `gesture()` method + execution. + + .. warning:: If gesture data becomes available from the sensor, this driver will + continuously pull in that new data and analyze it until the sensor's gesture engine + exits and the sensor's FIFO buffers are clear. This allows for much more reliable + gesture detection by comparing the "first" to the "last" detected state at the cost of + blocking until the FIFOs are all clear. This will only happen if all four gesture values + drop below ``30``. + + As a result, if an object is close to the sensor when `gesture()` is called, the method + will not return until it moves away. + + .. note:: The sensor itself offers a very wide variety of configuration options for tuning + the gesture engine, such as the LEDs (pulse count/length, drive power), the photosensors + (gain, offsets, masking), the gesture engine's entry/exit thresholds, wait time, and + more. However, this driver does not make those readily available in order to keep file + size and memory footprint to a minimum, which is critical for its use on more + constrained platforms.""" + # If FIFOs have overflowed we're already way too late, so clear those FIFOs and wait + if self._get_bit(_APDS9960_GSTATUS, _BIT_MASK_GSTATUS_GFOV): + self._set_bit(_APDS9960_GCONF4, _BIT_MASK_GCONF4_GFIFO_CLR, True) + wait_cycles = 0 + # Don't wait forever though, just enough to see if a gesture is happening + while ( + not self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_GINT) + and wait_cycles <= 30 + ): + time.sleep(0.003) + wait_cycles += 1 + + # Only start retrieval if there are datasets to retrieve + frame = [] + datasets_available = self._read8(_APDS9960_GFLVL) + if ( + self._get_bit(_APDS9960_STATUS, _BIT_MASK_STATUS_GINT) + and datasets_available > 0 + ): + if not self.buf129: + self.buf129 = bytearray(129) + + buffer = self.buf129 + buffer[0] = _APDS9960_GFIFO_U + + if not self.buf4: + self.buf4 = bytearray(4) + + buffer_dataset = self.buf4 + + # Retrieve new data until our FIFOs are truly empty + while True: + dataset_count = self._read8(_APDS9960_GFLVL) + if dataset_count == 0: + break with self.i2c_device as i2c: i2c.write_then_readinto( @@ -278,136 +624,141 @@ def gesture(self) -> int: # pylint: disable-msg=too-many-branches buffer, out_end=1, in_start=1, - in_end=min(129, 1 + n_recs * 4), + in_end=min(129, 1 + (dataset_count * 4)), ) - upp, down, left, right = buffer[1:5] - - if abs(upp - down) > 13: - up_down_diff = upp - down - if abs(left - right) > 13: - left_right_diff = left - right - - if up_down_diff != 0: - if up_down_diff < 0: - # either leading edge of down movement - # or trailing edge of up movement - if self._saw_up_start: - gesture_received = 0x01 # up - else: - self._saw_down_start += 1 - elif up_down_diff > 0: - # either leading edge of up movement - # or trailing edge of down movement - if self._saw_down_start: - gesture_received = 0x02 # down - else: - self._saw_up_start += 1 - - if left_right_diff != 0: - if left_right_diff < 0: - # either leading edge of right movement - # trailing edge of left movement - if self._saw_left_start: - gesture_received = 0x03 # left - else: - self._saw_right_start += 1 - elif left_right_diff > 0: - # either leading edge of left movement - # trailing edge of right movement - if self._saw_right_start: - gesture_received = 0x04 # right + # Unpack data stream into more usable U/D/L/R datasets for analysis + idx = 0 + for i in range(dataset_count): + rec = i + 1 + idx = 1 + ((rec - 1) * 4) + + buffer_dataset[0] = buffer[idx] + buffer_dataset[1] = buffer[idx + 1] + buffer_dataset[2] = buffer[idx + 2] + buffer_dataset[3] = buffer[idx + 3] + + # Drop fully-saturated and fully-zero to conserve memory + # Filter to remove useless (saturated, empty, low-count) datasets + if ( + (not all(val == 255 for val in buffer_dataset)) + and (not all(val == 0 for val in buffer_dataset)) + and (all(val >= 30 for val in buffer_dataset)) + ): + if len(frame) < 2: + frame.append(tuple(buffer_dataset)) else: - self._saw_left_start += 1 + frame[1] = tuple(buffer_dataset) - # saw a leading or trailing edge; start timer - if up_down_diff or left_right_diff: - time_mark = time.monotonic() + # Wait a very short time to see if new FIFO data has arrived before we drop out + time.sleep(0.03) - # finished when a gesture is detected or ran out of time (300ms) - if gesture_received or time.monotonic() - time_mark > 0.300: - self._reset_counts() - break - if gesture_received != 0: - if self._rotation != 0: - return self.rotated_gesture(gesture_received) - return gesture_received - - @property - def gesture_dimensions(self) -> int: - """Gesture dimension value: range 0-3""" - return self._read8(APDS9960_GCONF3) + # If we only got one useful frame, that's not enough to make a solid guess + if len(frame) < 2: + return 0 - @gesture_dimensions.setter - def gesture_dimensions(self, dims: int) -> None: - self._write8(APDS9960_GCONF3, dims & 0x03) + # We should have a dataframe with two tuples now, a "first" and "last" entry. + # Time to process the dataframe! + + # Determine our up/down and left/right ratios along with our first/last deltas + f_r_ud = ((frame[0][0] - frame[0][1]) * 100) // (frame[0][0] + frame[0][1]) + f_r_lr = ((frame[0][2] - frame[0][3]) * 100) // (frame[0][2] + frame[0][3]) + + l_r_ud = ((frame[1][0] - frame[1][1]) * 100) // (frame[1][0] + frame[1][1]) + l_r_lr = ((frame[1][2] - frame[1][3]) * 100) // (frame[1][2] + frame[1][3]) + + delta_ud = l_r_ud - f_r_ud + delta_lr = l_r_lr - f_r_lr + + # Make our first guess at what gesture we saw, if any + state_ud = 0 + state_lr = 0 + + if delta_ud >= 30: + state_ud = 1 + elif delta_ud <= -30: + state_ud = -1 + + if delta_lr >= 30: + state_lr = 1 + elif delta_lr <= -30: + state_lr = -1 + + # Make our final decision based on our first guess and, if required, the delta data + gesture_found = 0 + + # Easy cases + if state_ud == -1 and state_lr == 0: + gesture_found = 1 + elif state_ud == 1 and state_lr == 0: + gesture_found = 2 + elif state_ud == 0 and state_lr == -1: + gesture_found = 3 + elif state_ud == 0 and state_lr == 1: + gesture_found = 4 + + # Not so easy cases + if gesture_found == 0: + if state_ud == -1 and state_lr == 1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 1 + else: + gesture_found = 4 + elif state_ud == 1 and state_lr == -1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 2 + else: + gesture_found = 3 + elif state_ud == -1 and state_lr == -1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 1 + else: + gesture_found = 3 + elif state_ud == 1 and state_lr == 1: + if abs(delta_ud) > abs(delta_lr): + gesture_found = 2 + else: + gesture_found = 3 + + if gesture_found != 0: + if self._rotation != 0: + # If we need to rotate our gesture, lets do that before returning + dir_lookup = [1, 4, 2, 3] + idx = (dir_lookup.index(gesture_found) + self._rotation // 90) % 4 + return dir_lookup[idx] - @property - def color_data_ready(self) -> int: - """Color data ready flag. zero if not ready, 1 is ready""" - return self._read8(APDS9960_STATUS) & 0x01 + return gesture_found + ## COLOR @property def color_data(self) -> Tuple[int, int, int, int]: - """Tuple containing r, g, b, c values""" - return ( - self._color_data16(APDS9960_CDATAL + 2), - self._color_data16(APDS9960_CDATAL + 4), - self._color_data16(APDS9960_CDATAL + 6), - self._color_data16(APDS9960_CDATAL), - ) + """Tuple containing red, green, blue, and clear light intensity values detected by the + sensor during the latest color/light engine run. - ### PROXIMITY - @property - def proximity_interrupt_threshold(self) -> Tuple[int, int, int]: - """Tuple containing low and high threshold - followed by the proximity interrupt persistance. - When setting the proximity interrupt threshold values using a tuple of - zero to three values: low threshold, high threshold, persistance. - persistance defaults to 4 if not provided""" - return ( - self._read8(APDS9960_PILT), - self._read8(APDS9960_PIHT), - self._proximity_persistance, - ) + Each value is a 16-bit integer with a possible value of ``0`` to ``65535``. - @proximity_interrupt_threshold.setter - def proximity_interrupt_threshold(self, setting_tuple: Tuple[int, ...]) -> None: - if setting_tuple: - self._write8(APDS9960_PILT, setting_tuple[0]) - if len(setting_tuple) > 1: - self._write8(APDS9960_PIHT, setting_tuple[1]) - persist = 4 # default 4 - if len(setting_tuple) > 2: - persist = min(setting_tuple[2], 7) - self._proximity_persistance = persist + .. caution:: Will always return ``(0, 0, 0, 0)`` if `enable_color` is not set to ``True``. - @property - def gesture_proximity_threshold(self) -> int: - """Proximity threshold value: range 0-255""" - return self._read8(APDS9960_GPENTH) + .. tip:: To get useful, predictable `color_data` results it is important to tune + `color_gain` and `color_integration_time`, to accommodate different lighting conditions, + sensor placements, plastic/glass transparencies, expected object reflectivity, and + environmental conditions. - @gesture_proximity_threshold.setter - def gesture_proximity_threshold(self, thresh: int) -> None: - self._write8(APDS9960_GPENTH, thresh & 0xFF) + For instance, measuring color of objects close to the sensor with bright, nearby + illumination (such as the white LEDs on the `Adafruit Clue + `_) may work well with a `color_gain` of ``0`` + and a `color_integration_time` of ``72``. - @property - def proximity(self) -> int: - """Proximity value: range 0-255""" - return self._read8(APDS9960_PDATA) - - def clear_interrupt(self) -> None: - """Clear all interrupts""" - self._writecmdonly(APDS9960_AICLEAR) - - @property - def integration_time(self) -> int: - """Proximity integration time: range 0-255""" - return self._read8(APDS9960_ATIME) - - @integration_time.setter - def integration_time(self, int_time: int) -> None: - self._write8(APDS9960_ATIME, int_time & 0xFF) + However, measuring the intensity and color temperature of ambient light through + difusion glass or plastic is likely to require experimenting with a wide range of + `color_gain` and `color_integration_time` settings before useful data can be obtained. + """ + return ( + self._color_data16(_APDS9960_CDATAL + 2), + self._color_data16(_APDS9960_CDATAL + 4), + self._color_data16(_APDS9960_CDATAL + 6), + self._color_data16(_APDS9960_CDATAL), + ) # method for reading and writing to I2C def _write8(self, command: int, abyte: int) -> None: @@ -433,6 +784,45 @@ def _read8(self, command: int) -> int: i2c.write_then_readinto(buf, buf, out_end=1, in_end=1) return buf[0] + def _get_bit(self, register: int, mask: int) -> bool: + """Gets a single bit value from the I2C device's register""" + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + return bool(buf[1] & mask) + + def _set_bit(self, register: int, mask: int, value: bool) -> None: + """Sets a single bit value in the I2C device's register""" + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + if value: + buf[1] |= mask + else: + buf[1] &= ~mask + with self.i2c_device as i2c: + i2c.write(buf, end=2) + + def _get_bits(self, register: int, pos: int, mask: int) -> int: + """Sets a multi-bit value in the I2C device's register""" + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + return (buf[1] & mask) >> pos + + def _set_bits(self, register: int, pos: int, mask: int, value: int) -> None: + """Sets a multi-bit value in the I2C device's register""" + buf = self.buf2 + buf[0] = register + with self.i2c_device as i2c: + i2c.write_then_readinto(buf, buf, out_end=1, in_start=1) + buf[1] = (buf[1] & ~mask) | (value << pos) + with self.i2c_device as i2c: + i2c.write(buf, end=2) + def _color_data16(self, command: int) -> int: """Sends a command and reads 2 bytes of data from the I2C device The returned data is low byte first followed by high byte""" diff --git a/docs/examples.rst b/docs/examples.rst index e3285ce..3151df1 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -3,35 +3,35 @@ Simple test Ensure your device works with this simple test. -.. literalinclude:: ../examples/apds9960_color_simpletest.py - :caption: examples/apds9960_color_simpletest.py +.. literalinclude:: ../examples/apds9960_simpletest.py + :caption: examples/apds9960_simpletest.py :linenos: -Gesture Example ---------------- +Proximity Example +----------------- -Show how to use the device with simple gestures +Example illustrating proximity detection -.. literalinclude:: ../examples/apds9960_gesture_simpletest.py - :caption: examples/apds9960_gesture_simpletest.py +.. literalinclude:: ../examples/apds9960_proximity_simpletest.py + :caption: examples/apds9960_proximity_simpletest.py :linenos: -Proximity Example ------------------ +Gesture Example +--------------- -Example showing proximity feature +Example illustrating gesture detection -.. literalinclude:: ../examples/apds9960_proximity_simpletest.py - :caption: examples/apds9960_proximity_simpletest.py +.. literalinclude:: ../examples/apds9960_gesture_simpletest.py + :caption: examples/apds9960_gesture_simpletest.py :linenos: Color Example --------------- -Example showing how to get RGB values +Example illustrating color detection .. literalinclude:: ../examples/apds9960_color_simpletest.py :caption: examples/apds9960_color_simpletest.py diff --git a/examples/apds9960_color_simpletest.py b/examples/apds9960_color_simpletest.py index 1ecad2d..cdb4f20 100644 --- a/examples/apds9960_color_simpletest.py +++ b/examples/apds9960_color_simpletest.py @@ -12,8 +12,6 @@ while True: - # create some variables to store the color data in - # wait for color data to be ready while not apds.color_data_ready: time.sleep(0.005) diff --git a/examples/apds9960_proximity_simpletest.py b/examples/apds9960_proximity_simpletest.py index cc70f59..dac893f 100644 --- a/examples/apds9960_proximity_simpletest.py +++ b/examples/apds9960_proximity_simpletest.py @@ -7,12 +7,18 @@ i2c = board.I2C() int_pin = digitalio.DigitalInOut(board.D5) +int_pin.switch_to_input(pull=digitalio.Pull.UP) apds = APDS9960(i2c) -apds.enable_proximity = True +# set the interrupt threshold to fire when proximity reading goes above 175 apds.proximity_interrupt_threshold = (0, 175) + +# assert the interrupt pin when the proximity interrupt is triggered apds.enable_proximity_interrupt = True +# enable the sensor's proximity engine +apds.enable_proximity = True + while True: # print the proximity reading when the interrupt pin goes low if not int_pin.value: