Source code for isl29125

# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`isl29125`
================================================================================

CircuitPython driver for the ISL29125 Sensor


* Author(s): Jose D. Montoya

Implementation Notes
--------------------


* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register

"""

from micropython import const
from adafruit_bus_device import i2c_device
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct
from adafruit_register.i2c_bits import RWBits

try:
    from busio import I2C
except ImportError:
    pass

__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/CircuitPython_isl29125.git"

_I2C_ADDR = const(0x44)
_REG_WHOAMI = const(0x00)
_CONFIG1 = const(0x01)
_CONFIG2 = const(0x02)
_CONFIG3 = const(0x03)
_FLAG_REGISTER = const(0x08)

# Operation Modes
POWERDOWN = const(0b000)
GREEN_ONLY = const(0b001)
RED_ONLY = const(0b010)
BLUE_ONLY = const(0b11)
STANDBY = const(0b100)  # No ADC Conversion
RED_GREEN_BLUE = const(0b101)
GREEN_RED = const(0b110)
GREEN_BLUE = const(0b111)
operation_values = (
    POWERDOWN,
    GREEN_ONLY,
    RED_ONLY,
    BLUE_ONLY,
    STANDBY,
    RED_GREEN_BLUE,
    GREEN_RED,
    GREEN_BLUE,
)

# Sensing Range
LUX_375 = const(0b0)
LUX_10K = const(0b1)
sensing_range_values = (LUX_375, LUX_10K)

# ADC Resolution
RES_16BITS = const(0b0)
RES_12BITS = const(0b1)

# Interrupt
NO_INTERRUPT = const(0b00)
GREEN_INTERRUPT = const(0b01)
RED_INTERRUPT = const(0b10)
BLUE_INTERRUPT = const(0b11)
interrupt_values = (NO_INTERRUPT, GREEN_INTERRUPT, RED_INTERRUPT, BLUE_INTERRUPT)

# Persistent Control
IC1 = const(0b00)
IC2 = const(0b01)
IC4 = const(0b10)
IC8 = const(0b11)
persistent_control_values = (IC1, IC2, IC4, IC8)


[docs] class ISL29125: """Driver for the ISL29125 Light Sensor connected over I2C. :param ~busio.I2C i2c_bus: The I2C bus the ISL29125 is connected to. :param int address: The I2C device address. Defaults to :const:`0x44` :raises RuntimeError: if the sensor is not found **Quickstart: Importing and using the device** Here is an example of using the :class:`ISL29125` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import circuitpython_isl29125.isl29125 as isl29125 Once this is done you can define your `board.I2C` object and define your sensor object .. code-block:: python i2c = board.I2C() # uses board.SCL and board.SDA isl = isl29125.ISL29125(i2c) Now you have access to the :attr:`colors` attribute .. code-block:: python red, green, blue = isl.colors """ _device_id = ROUnaryStruct(_REG_WHOAMI, "B") _conf_reg = UnaryStruct(_CONFIG1, "B") _conf_reg2 = UnaryStruct(_CONFIG2, "B") _conf_reg3 = UnaryStruct(_CONFIG3, "B") _low_threshold = UnaryStruct(0x04, "h") _high_threshold = UnaryStruct(0x06, "h") _flag_register = UnaryStruct(0x08, "B") _green = ROUnaryStruct(0x09, "h") _red = ROUnaryStruct(0x0B, "h") _blue = ROUnaryStruct(0x0D, "h") _operation_mode = RWBits(3, _CONFIG1, 0) _rgb_sensing_range = RWBits(1, _CONFIG1, 3) _adc_resolution = RWBits(1, _CONFIG1, 3) _ir_compensation = RWBits(1, _CONFIG2, 7) _ir_compensation_value = RWBits(6, _CONFIG2, 0) _interrupt_threshold_status = RWBits(2, _CONFIG3, 0) _interrupt_persistent_control = RWBits(2, _CONFIG3, 2) _interrupt_triggered_status = RWBits(1, _FLAG_REGISTER, 0) _brown_out = RWBits(1, _FLAG_REGISTER, 2) def __init__(self, i2c_bus: I2C, address: int = _I2C_ADDR) -> None: self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != 0x7D: raise RuntimeError("Failed to find the ISL29125") self._conf_reg = 0x0D # 0xBF Datasheet recommendation to max out IR compensation value. # It makes High range reach more than 10,000lux. self._conf_reg2 = 0xBF self.clear_register_flag() # Setting the brownout to 0 according to datasheet recommendation self._brown_out = 0 @property def green(self): """Green property""" return self._green @property def red(self): """red property""" return self._red @property def blue(self): """blue property""" return self._blue @property def colors(self): """colors property""" return self._red, self._green, self._blue @property def operation_mode(self) -> str: """The device has various RGB operating modes. The device powers up on a disable mode. All operating modes are in continuous ADC conversion. The following bits are used to enable the operating mode +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`isl29125.POWERDOWN` | :py:const:`0b000` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.GREEN_ONLY` | :py:const:`0b001` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.RED_ONLY` | :py:const:`0b010` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.BLUE_ONLY` | :py:const:`0b011` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.STANDBY` | :py:const:`0b100` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.RED_GREEN_BLUE` | :py:const:`0b101` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.GREEN_RED` | :py:const:`0b110` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.GREEN_BLUE` | :py:const:`0b111` | +----------------------------------------+-------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl.operation_mode = isl29125.BLUE_ONLY """ values = ( "POWERDOWN", "GREEN_ONLY", "RED_ONLY", "BLUE_ONLY", "STANDBY", "RED_GREEN_BLUE", "GREEN_RED", "GREEN_BLUE", ) return values[self._operation_mode] @operation_mode.setter def operation_mode(self, value: int) -> None: if value not in operation_values: raise ValueError("Value must be a valid operation mode setting") self._operation_mode = value @property def sensing_range(self) -> str: """The Full Scale RGB Range has two different selectable ranges at bit 3. The range determines the ADC resolution (12 bits and 16 bits). Each range has a maximum allowable lux value. Higher range values offer better resolution and wider lux value +----------------------------------------+----------------------------------+ | Mode | Value | +========================================+==================================+ | :py:const:`isl29125.LUX_375` | :py:const:`0b0` 375 lux | +----------------------------------------+----------------------------------+ | :py:const:`isl29125.LUX_10K` | :py:const:`0b1` 10000 lux | +----------------------------------------+----------------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl.operation_mode = isl29125.LUX_375 """ values = ("LUX_375", "LUX_10K") return values[self._rgb_sensing_range] @sensing_range.setter def sensing_range(self, value: int) -> None: if value not in sensing_range_values: raise ValueError("Value must be a valid sensing range setting") self._rgb_sensing_range = value @property def adc_resolution(self) -> str: """ADC’s resolution and the number of clock cycles per conversion is determined by this bit. Changing the resolution of the ADC, changes the number of clock cycles of the ADC which in turn changes the integration time. Integration time is the period the ADC samples the photodiode current signal for a measurement +----------------------------------------+----------------------------------+ | Mode | Value | +========================================+==================================+ | :py:const:`isl29125.RES_12BITS` | :py:const:`0b0` 16 bits | +----------------------------------------+----------------------------------+ | :py:const:`isl29125.RES_16BITS` | :py:const:`0b1` 12 bits | +----------------------------------------+----------------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl.operation_mode = isl29125.RES_12BITS """ values = ("RES_12BITS", "RES_16BITS") return values[self._adc_resolution] @adc_resolution.setter def adc_resolution(self, value: int) -> None: if value not in (0, 1): raise ValueError("Value must be a valid adc resolution setting") self._adc_resolution = value @property def ir_compensation(self) -> int: """The device provides a programmable active IR compensation which allows fine-tuning of residual infrared components from the output which allows optimizing the measurement variation between differing IR-content light sources. +----------------------------------------+----------------------------------+ | Mode | Value | +========================================+==================================+ | :py:const:`isl29125.IR_ON` | :py:const:`0b1` | +----------------------------------------+----------------------------------+ | :py:const:`isl29125.IR_OFF` | :py:const:`0b0` | +----------------------------------------+----------------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl.ir_compensation = isl29125.IR_ON """ return self._ir_compensation @ir_compensation.setter def ir_compensation(self, value: int) -> None: if value not in (0, 1): raise ValueError("Value must be a valid ir compensation setting") self._ir_compensation = value @property def ir_compensation_value(self) -> int: """The effective IR compensation is from 106 to 169 in the CONF2 register. Consult datasheet for detailed IR filtering calibration with the following values: | * BIT5: 32 | * BIT4: 16 | * BIT3: 8 | * BIT2: 4 | * BIT1: 2 | * BIT0: 1 Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl._ir_compensation_value = 48 """ return self._ir_compensation_value @ir_compensation_value.setter def ir_compensation_value(self, value: int) -> None: if value not in (1, 2, 4, 8, 16, 32): raise ValueError("Value must be a valid ir compensation setting") self._ir_compensation_value = value @property def interrupt_threshold(self) -> str: """The interrupt_threshold is the status bits for light intensity detection. The property:`interrupt_triggered` is set to logic HIGH when the light intensity crosses the interrupt thresholds window (register address 0x04 - 0x07) +----------------------------------------+----------------------------------+ | Value | Value | +========================================+==================================+ | :py:const:`isl29125.NO_INTERRUPT` | :py:const:`0b00` | +----------------------------------------+----------------------------------+ | :py:const:`isl29125.GREEN_INTERRUPT` | :py:const:`0b01` | +----------------------------------------+----------------------------------+ | ::py:const:`isl29125.RED_INTERRUPT` | :py:const:`0b10` | +----------------------------------------+----------------------------------+ | :py:const:`isl29125.BLUE_INTERRUPT` | :py:const:`0b11` | +----------------------------------------+----------------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl.interrupt_threshold = isl29125.BLUE_INTERRUPT """ values = ("No Interrupt", "Green Interrupt", "Red Interrupt", "Blue Interrupt") return values[self._interrupt_threshold_status] @interrupt_threshold.setter def interrupt_threshold(self, value) -> None: if value not in interrupt_values: raise ValueError("Value must be a valid interrupt threshold Value") self._interrupt_threshold_status = value @property def high_threshold(self) -> int: """ The interrupt threshold level is a 16-bit number (Low Threshold-1 and Low Threshold-2). The lower interrupt threshold registers are used to set the lower trigger point for interrupt generation. If the ALS value crosses below or is equal to the lower threshold, an interrupt is asserted on the interrupt pin (LOW) and the interrupt status bit (HIGH). """ return self._high_threshold @high_threshold.setter def high_threshold(self, value: int) -> None: self._high_threshold = value @property def low_threshold(self) -> int: """ The interrupt threshold level is a 16-bit number (Low Threshold-1 and Low Threshold-2). The lower interrupt threshold registers are used to set the lower trigger point for interrupt generation. If the ALS value crosses below or is equal to the lower threshold, an interrupt is asserted on the interrupt pin (LOW) and the interrupt status bit (HIGH). """ return self._low_threshold @low_threshold.setter def low_threshold(self, value: int) -> None: self._low_threshold = value @property def interrupt_triggered(self) -> int: """Is set to high when the interrupt threshold have been triggered (out of threshold window) and logic low when not yet triggered. +----------------------------------------+----------------------------------+ | Value | Value | +========================================+==================================+ | :py:const:`0b0` | Interrupt is cleared or | | | not triggered yet | +----------------------------------------+----------------------------------+ | :py:const:`0b1` | interrupt is triggered | +----------------------------------------+----------------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) print(isl.interrupt_triggered) """ return self._interrupt_triggered_status @property def persistent_control(self) -> int: """To minimize interrupt events due to 'transient' conditions, an interrupt persistence option is available. IN the event of transient condition an 'X-consecutive' number of interrupt must happen before the interrupt flag and pint (INT) pin gets driven low. The interrupt is active-low and remains asserted until clear_register_flag is called +----------------------------------------+-------------------------+ | Mode | Value | +========================================+=========================+ | :py:const:`isl29125.IC1` | :py:const:`0b000` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.IC2` | :py:const:`0b001` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.IC4` | :py:const:`0b010` | +----------------------------------------+-------------------------+ | :py:const:`isl29125.IC8` | :py:const:`0b011` | +----------------------------------------+-------------------------+ Example --------------------- .. code-block:: python i2c = board.I2C() isl = isl29125.ISL29125(i2c) isl.persistent_control = isl29125.IC4 """ values = ("IC1", "IC2", "IC4", "IC8") return values[self._interrupt_persistent_control] @persistent_control.setter def persistent_control(self, value: int) -> None: if value not in persistent_control_values: raise ValueError("Value must be a valid persistent control value") self._interrupt_persistent_control = value
[docs] def clear_register_flag(self): """Clears the flag register performing a read action""" return self._flag_register